我试图在PHP yi 2项目中使用gridstack.js和chart.js做一个 Jmeter 板
我已经给出了一个按钮,用户可以在其中选择KPI图表,所选图表将添加到gridstack.js的网格中
问题一:如果我以递增的顺序加载图表:图表1,图表2,图表3.
问题二:一旦网格被保存,然后被加载。因此,如果用户想在加载的网格中再添加一个图表,以便他/她可以更新保存的网格=>一个新的网格被创建,加载的网格消失。
我100%确定问题出在initializeChart函数上。我已经根据网上的资源尝试了很多方法,但都是徒劳的。我尝试过的一些方法是:
1.将initializeChart函数与图表数据分离,并根据用户请求调用图表数据。
1.我还尝试在initializeChart函数中使用if else语句、if elseif语句和switch语句
但是一旦我修改了initializeChart函数,甚至一个图都没有被初始化。而目前,如果以增量顺序调用,它们正在被初始化。
下面是我的完整代码。请帮助我解决这个问题。
注意事项:我已经删除了一些图表数据,以适应限制。所有的图表数据都是虚构的,所以你可以复制粘贴任何图表数据,并重新命名,使其工作。
P.S.任何帮助都将非常感谢。提前感谢!
<?php
/** @var yii\web\View $this */
use yii\helpers\Html;
$this->title = 'About';
$this->params['breadcrumbs'][] = $this->title;
?>
<?php
$this->registerJsFile('https://cdn.jsdelivr.net/npm/chart.js', ['position' => \yii\web\View::POS_HEAD]);
?>
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
.grid-stack {
background: lightslategray;
}
.grid-stack-item-content {
text-align: start;
background-color: whitesmoke;
}
.grid-stack-item {
min-height: 80px;
}
.grid-stack-item-removing {
opacity: 0.8;
filter: blur(5px);
}
#trash {
background: rgba(255, 0, 0, 0.6);
}
</style>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<script type="module" src="https://unpkg.com/[email protected]/dist/ionicons/ionicons.esm.js"></script>
<script nomodule="" src="https://unpkg.com/[email protected]/dist/ionicons/ionicons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/gridstack.min.js"
integrity="sha512-V4/oQa+3p3Nu7kSK1DLdTvD3+03q/2MMvszJ84JLroRjUvZ82YcplQ7IJX6XiZXuJtESbUBL+gcGF03F5D+n5Q=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/dd-base-impl.min.js"
integrity="sha512-mhZn+o/RSWCNMh6NpFW4pTDzGcqwhL2xH2RdRHOQ5dnj5HDrlPwMuHZdAwB8ZOXZcIhR6IgzXUh4DuG13PsPDg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/dd-draggable.min.js"
integrity="sha512-a41HfVO6a33kYNNTiVMwzCOgNo0RLhmEhKm0K1EiBludHHsjXbyUXcs3jMQpLs1mipx3yR2e5aInebX0e3dNTg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/dd-droppable.min.js"
integrity="sha512-0MXd7MHHvnDPlwTnGHxxPSk83jr3n04ozR/9D7WpKf7fr7kva7/Syfd8Obki4tIx0NOSzkn8wvlR6CrAFPWRWQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/dd-element.min.js"
integrity="sha512-zvWjOI2JNrhq7K/mfZu0zg6wHRd7iWDF5g7cc4QC6ZbGpIXlc6C1bFoDcj9KqQkU2G56x81qq+NQKRVNbDc2LQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/dd-gridstack.min.js"
integrity="sha512-rfwTdhmMbnmMNjp+H46Bfm5bEQOPMDJ8cWKIzP3JyLWCxo9nlrsjqyfGMe3sAocY7JFm3rH13VH2ZJtPj61Gjw=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/dd-manager.min.js"
integrity="sha512-7c2iOKYLmwWFiZ0bdYJ1p8EvDwJFbFBvKTfVgLBCpPlLiXlITMumR/9aymNLEGqfVNzQjg+hJKcATL3PvZhY9A=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/dd-resizable-handle.min.js"
integrity="sha512-47UKx0R1s2WYC47SoxCO+OC256GghdA8sRVGTOzb4ehV1SnEBFS1+lujNb3bWW5abegJ+yVGKs8V93iqv4E3hg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/dd-resizable.min.js"
integrity="sha512-qfVbqxjrtBLIYkAVlicMMiLKO9Jl27UpU4YARHuKvKDUzrar97zKlkdpcXNgBtNaXsZ0NQMSwz0vg9Lxs4ezyA=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/dd-touch.min.js"
integrity="sha512-qjqVUYIO1ooxh8d3mhry2CoKxkqekgZU778GJNfD1bZxUlvUrpl/YMjKvzwwhmWPef/eSvbfHJ0Rj3HAZfJarg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/gridstack-all.min.js"
integrity="sha512-gq8R31XSsOjp58K9qre4n5inuaUuAkz36qmdVx9Y1VpGtzmx06JlnVGLEDMISZH1Kh/M2X4Iip40WPD94kQW2w=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/gridstack-engine.min.js"
integrity="sha512-wHYVJ9RRSpsxBgtDstxRb35Dlh6zXCxwzU3fTLyD81ltBG/ZJi9x82A9heWjcX6ykZ+ga8EkCFkKymUYhpwT0g=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/gridstack-extra.min.css"
integrity="sha512-287EQpO1sItRDNvuCUARDlhpQs3qLRCMaidpOKp5BFu6EgcX3XxB92jmTvdXWW57Q9ImHcYqIHKx12EATT3sPA=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/gridstack.min.css"
integrity="sha512-RQiZSqRhRB5xAwqOKws34d14fPNaxoy59RNAUl2KXtT+5XfmspdKoNo1C2hR1bunSSLd4wNOz1bYcZjvuzN0hg=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/types.min.js"
integrity="sha512-Or1RaobYJlngoRviBZqCv5kO0PbmajRZ+1uPrcWYWkNQPTU/wgg+IQPtIliaPiVh7/qhJv+qK9oZPsaTsMEiUg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/10.0.1/utils.min.js"
integrity="sha512-C+//7CsrykaET+j6aiHKpZ12zf+POQPB8t6SQ2Q0HOvadZ0vBuBfTT27/w06JhqJfXdntps6dbaLhKQfMPZ+vg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
</head>
<body>
<div class="site-about mb-0 pb-0">
<h1>GridStackJs</h1>
<div class="col-12">
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<ion-icon name="add-circle"></ion-icon>Add KPI
</button>
<div class="dropdown-menu">
<a class="dropdown-item add-chart" href="#" data-chart="occupancyChart">Occupancy</a>
<a class="dropdown-item add-chart" href="#" data-chart="roomsChart">Rooms</a>
<a class="dropdown-item add-chart" href="#" data-chart="contractsChart">Contracts</a>
<a class="dropdown-item add-chart" href="#" data-chart="employeeChart">Employee</a>
<a class="dropdown-item add-chart" href="#" data-chart="revenueChart">Revenue</a>
<a class="dropdown-item add-chart" href="#" data-chart="invoicesChart">Invoices</a>
<a class="dropdown-item add-chart" href="#" data-chart="paymentChart">Payment</a>
<a class="dropdown-item add-chart" href="#" data-chart="visitorsChart">Visitors</a>
<a class="dropdown-item add-chart" href="#" data-chart="maintenanceChart">Maintenance</a>
</div>
</div>
<a onclick="saveFullGrid()" class="btn btn-secondary" href="#">Save Full Grid</a>
<a onclick="loadFullGrid()" class="btn btn-secondary" href="#">Load Full Grid</a>
<a onclick="clearGrid()" class="btn btn-secondary" href="#">Reset</a>
<a id="trash" class="btn btn-danger" href="#"><ion-icon name="trash"></ion-icon>Drag here to remove!</a>
<br /><br />
<div id="gridCont" class="grid-stack"></div>
<hr />
<textarea id="saved-data" style="width: 100%" cols="100" rows="20" readonly="readonly"></textarea>
</div>
<script type="text/javascript">
let grid = GridStack.init({
cellHeight: 70,
acceptWidgets: true,
removable: '#trash', // drag-out delete class
float: true,
})
GridStack.setupDragIn('.newWidget', { appendTo: 'body', helper: 'clone' });
let advanceItems = [
{ id: 'defaultWidget', x: 0, y: 0, w: 5, h: 4, content: '<p class="px-4 mx-4 mt-4 pt-4"><em><b>Add KPIs as per your requirement<br/> from the dropdown-menu</b></em></p>' },
];
advanceItems.forEach((n, i) => {
n.id = String(i);
n.content = `<button class="btn btn-outline-secondary" onclick="removeWidget(this.parentElement.parentElement)">X</button><br> ${n.content ? n.content : ''}`;
});
let advanceItemsFull;
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.dropdown-item').forEach(function (item) {
item.addEventListener('click', function () {
const chartType = this.innerText.toLowerCase();
const chartId = chartType + 'Chart';
const newChart = `<canvas id="${chartId}"></canvas>`;
// Check if the chart already exists in advanceItems
const itemContent = advanceItems.find(item => item.content);
console.log("NEW CHART ", newChart);
console.log("SELECTED item.content", itemContent);
const existingChart = advanceItems.find(item => item.content.includes(newChart));
if (!existingChart) {
// Calculate new x and y coordinates based on existing items
const newX = advanceItems.length % 2 === 0 ? 8 : 0; // Alternates between 0 and 8
const newY = Math.floor(advanceItems.length / 2) * 2;
advanceItems.push({
id: chartId, // Add an id property to uniquely identify the chart
x: newX,
y: newY,
w: 6,
h: 4,
content: newChart,
});
advanceItems.forEach((n, i) => {
n.id = String(i);
if (!n.content.includes('removeWidget')) {
n.content = `<button class="btn btn-outline-secondary" onclick="removeWidget(this.parentElement.parentElement)">X</button><br> ${n.content ? n.content : ''}`;
}
});
// Reload the grid to reflect the changes
grid.load(advanceItems);
const newItem = advanceItems.find(item => item.id === chartId);
if (newItem) {
initializeChart(newItem.content);
}
// Initialize the chart outside the grid load callback
initializeChart(newChart);
}
});
});
});
function addEvents(grid, id) {
let g = id !== undefined ? "grid" + id + " " : "";
grid
.on("added removed change", function (event, items) {
let str = "";
items.forEach(function (item) {
str += " (" + item.x + "," + item.y + " " + item.w + "x" + item.h + ")";
});
console.log(
g + event.type + " " + items.length + " items (x,y w h):" + str
);
})
.on("enable", function (event) {
let grid = event.target;
console.log(g + "enable");
})
.on("disable", function (event) {
let grid = event.target;
console.log(g + "disable");
})
.on("dragstart", function (event, el) {
let n = el.gridstackNode;
let x = el.getAttribute("gs-x"); // verify node (easiest) and attr are the same
let y = el.getAttribute("gs-y");
console.log(
g +
"dragstart " +
(n.content || "") +
" pos: (" +
n.x +
"," +
n.y +
") = (" +
x +
"," +
y +
")"
);
})
.on("drag", function (event, el) {
let n = el.gridstackNode;
let x = el.getAttribute("gs-x"); // verify node (easiest) and attr are the same
let y = el.getAttribute("gs-y");
})
.on("dragstop", function (event, el) {
let n = el.gridstackNode;
let x = el.getAttribute("gs-x"); // verify node (easiest) and attr are the same
let y = el.getAttribute("gs-y");
console.log(
g +
"dragstop " +
(n.content || "") +
" pos: (" +
n.x +
"," +
n.y +
") = (" +
x +
"," +
y +
")"
);
})
.on("dropped", function (event, previousNode, newNode) {
if (previousNode) {
console.log(g + "dropped - Removed widget from grid:", previousNode);
}
if (newNode) {
console.log(g + "dropped - Added widget in grid:", newNode);
}
})
.on("resizestart", function (event, el) {
let n = el.gridstackNode;
let rec = el.getBoundingClientRect();
console.log(
`${g} resizestart ${n.content || ""} size: (${n.w}x${n.h
}) = (${Math.round(rec.width)}x${Math.round(rec.height)})px`
);
})
.on("resize", function (event, el) {
let n = el.gridstackNode;
let rec = el.getBoundingClientRect();
console.log(
`${g} resize ${n.content || ""} size: (${n.w}x${n.h}) = (${Math.round(
rec.width
)}x${Math.round(rec.height)})px`
);
})
.on("resizestop", function (event, el) {
let n = el.gridstackNode;
let rec = el.getBoundingClientRect();
console.log(
`${g} resizestop ${n.content || ""} size: (${n.w}x${n.h
}) = (${Math.round(rec.width)}x${Math.round(rec.height)})px`
);
});
}
grid.load(advanceItems);
addEvents(grid);
GridStack.setupDragIn('.newWidget', { appendTo: 'body', helper: 'clone' });
initializeChart(advanceItems);
function initializeChart(chartId) {
console.log("in the initializeChart function", chartId, typeof (chartId))
const ctxOccupancy = document.getElementById('occupancyChart');
new Chart(ctxOccupancy, {
type: 'bar',
data: {
labels: ['Building 1', 'Building 2', 'Building 3', 'Building 4', 'Building 5'],
datasets: [
{
label: 'Employees',
data: [50, 30, 45, 25, 60],
backgroundColor: 'rgba(75, 192, 192, 0.7)',
},
{
label: 'Contractors',
data: [20, 15, 25, 10, 30],
backgroundColor: 'rgba(255, 99, 132, 0.7)',
},
{
label: 'Individual Tenants',
data: [10, 5, 15, 8, 20],
backgroundColor: 'rgba(255, 206, 86, 0.7)',
},
]
},
options: {
scales: {
x: {
stacked: true,
},
y: {
stacked: true,
beginAtZero: true,
}
},
plugins: {
legend: {
display: true,
position: 'bottom',
},
title: {
display: true,
text: 'Occupancy',
},
},
responsive: true,
maintainAspectRatio: true,
layout: {
padding: {
top: 20,
}
}
}
});
const ctxEmployee = document.getElementById('employeeChart');
new Chart(ctxEmployee, {
type: 'bubble',
data: {
datasets: [
{
label: 'Employee Distribution',
data: [
{ x: 1, y: 3, r: 10 },
{ x: 2, y: 2, r: 8 },
{ x: 3, y: 4, r: 12 },
{ x: 4, y: 5, r: 15 },
{ x: 5, y: 2, r: 8 },
],
backgroundColor: 'rgba(75, 192, 192, 0.7)',
},
]
},
options: {
scales: {
x: {
beginAtZero: true,
},
y: {
beginAtZero: true,
}
},
plugins: {
legend: {
display: true,
position: 'bottom',
},
title: {
display: true,
text: 'Employee Distribution',
},
},
responsive: true,
maintainAspectRatio: true,
layout: {
padding: {
top: 20,
}
}
}
});
const ctxRevenue = document.getElementById('revenueChart');
new Chart(ctxRevenue, {
type: 'pie',
data: {
labels: ['Employees', 'Contractors', 'Individual Tenants'],
datasets: [{
data: [60, 25, 15],
backgroundColor: ['rgba(75, 192, 192, 0.7)', 'rgba(255, 99, 132, 0.7)', 'rgba(255, 206, 86, 0.7)'],
}]
},
options: {
plugins: {
legend: {
display: true,
position: 'bottom',
},
title: {
display: true,
text: 'Revenue',
},
},
responsive: true,
maintainAspectRatio: true,
layout: {
padding: {
top: 20,
}
}
}
});
}
grid.on('added removed change', function (e, advanceItems) {
let str = '';
advanceItems.forEach(function (item) { str += ' (x,y)=' + item.x + ',' + item.y; });
// console.log(e.type + ' ' + advanceItems.length + ' advanceItems:' + str);
});
function saveFullGrid() {
try {
// Save the positions of the widget (x, y) coordinates and the content of the widgets (HTML content)
advanceItemsFull = grid.save(true, true);
// Stringify and store the advanceItemsFull object in localStorage
localStorage.setItem('gridData', JSON.stringify(advanceItemsFull));
console.log("advanceItems after Saving => ", advanceItemsFull);
console.log("saveFullGrid => ", JSON.stringify(advanceItemsFull, null, ' '));
} catch (error) {
console.error('Error saving full grid:', error);
// Provide user feedback or handle the error accordingly
}
}
function loadFullGrid() {
try {
if (!advanceItemsFull) {
let localStorageGridData = localStorage.getItem("gridData");
// Parse the JSON string retrieved from localStorage
advanceItemsFull = JSON.parse(localStorageGridData);
// Check if parsing was successful and if it has the expected structure
if (advanceItemsFull && advanceItemsFull.children) {
grid.removeAll();
grid.load(advanceItemsFull.children);
advanceItemsFull.children.forEach((item) => {
initializeChart(item.content);
});
} else {
console.error('Invalid data format in localStorage.');
}
} else {
// Your existing code for the case when advanceItemsFull is already defined
grid.removeAll();
grid.load(advanceItemsFull.children);
advanceItemsFull.children.forEach((item) => {
initializeChart(item.content);
});
}
} catch (error) {
console.error('Error loading full grid:', error);
// Provide user feedback or handle the error accordingly
}
}
// Reset function
function clearGrid() {
grid.removeAll();
advanceItems = [
{ x: 0, y: 0, w: 5, h: 4, content: '<p class="px-4 mx-4 mt-4 pt-4"><em><b>Add KPIs as per your requirement<br/> from the dropdown-menu</b></em></p>' },
];
advanceItems.forEach((n, i) => {
n.id = String(i);
n.content = `<button class="btn btn-outline-secondary" onclick="removeWidget(this.parentElement.parentElement)">X</button><br> ${n.content ? n.content : ''}`;
});
grid.load(advanceItems);
}
function removeWidget(el) {
// TEST removing from DOM first like Angular/React/Vue would do
console.log("el", el);
el.remove();
grid.removeWidget(el, true);
}
</script>
</div>
</body>
</html>
<?php
$this->registerJsFile('https://cdn.jsdelivr.net/npm/chart.js', ['position' => \yii\web\View::POS_HEAD]);
?>
字符串
1条答案
按热度按时间niknxzdl1#
我设法解决了图表初始化问题。基本上在我下面的函数中,我将选定的图表添加到advanceItems数组中,并且具有用于图表初始化的单个函数。
我为每个图表的初始化创建了单独的函数,并从advanceItems数组中的canvas元素中提取了选定的图表id,并为选定的每个canvasElement调用该函数。
字符串
单独的图表功能如下:
型