From 8ea1084eb4db86d3d070da1beb97ebb4b227c1a8 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 14 May 2024 11:10:15 +0000 Subject: [PATCH] Zooming fix --- public/scripts/chart_loader.js | 19 ++- public/scripts/charts/bar_chart.js | 129 +++++++-------- public/scripts/charts/chart.js | 3 + public/scripts/charts/point_chart.js | 14 +- public/scripts/charts/primitives.js | 2 +- public/scripts/charts/smooth_line_chart.js | 79 +++++----- public/scripts/charts/stacked_chart.js | 4 +- public/scripts/edit_chart.js | 174 +++------------------ public/scripts/edit_chart.js.old | 153 ++++++++++++++++++ public/scripts/table.js | 23 +-- public/scripts/zoom_manager.js | 2 +- public/styles/edit_chart.css | 51 ++++-- src/Api/Controller/ChartRestController.php | 22 +-- src/Controller/ChartController.php | 35 ++--- src/Form/Type/ChartType.php | 17 +- src/Form/Type/ColumnType.php | 5 +- src/Form/Type/MetadataType.php | 51 +++--- templates/chart.html.twig | 2 +- templates/edit.html.twig | 65 ++++---- templates/header.html.twig | 2 +- templates/list.html.twig | 16 ++ 21 files changed, 481 insertions(+), 387 deletions(-) mode change 100755 => 100644 public/scripts/edit_chart.js create mode 100755 public/scripts/edit_chart.js.old create mode 100644 templates/list.html.twig diff --git a/public/scripts/chart_loader.js b/public/scripts/chart_loader.js index 363709d..0f748c2 100644 --- a/public/scripts/chart_loader.js +++ b/public/scripts/chart_loader.js @@ -23,11 +23,11 @@ class ChartLoader { } /** Function for loading chart data from the server - * @param {String} code - The code used to identify the chart data to load + * @param {String} id - The id used to identify the chart **/ - loadData(code) { + loadData(id) { // Fetch chart data from the server - fetch("/api/charts/" + code, { + fetch("/api/charts/" + id, { method: 'GET', headers: { 'Content-Type': 'json' @@ -198,7 +198,7 @@ class ChartLoader { // Update chart and redraw chart.updateBounds() - chart.draw() + chart.draw(false) this.addInteractivity() } }) @@ -241,7 +241,7 @@ class ChartLoader { window.addEventListener("resize", e => { // Resize the chart canvas and redraw the chart chart.resizeCanvas(this.parent, this.legend.offsetHeight) - chart.draw() + chart.draw(false) // Reapply interactivity this.addInteractivity() @@ -259,7 +259,7 @@ class ChartLoader { // Update chart and redraw chart.updateBounds() - chart.draw() + chart.draw(false) this.addInteractivity() this.mouseMoveFunc(e, chart) @@ -272,7 +272,7 @@ class ChartLoader { */ async addInteractivity() { setTimeout(() => { - //console.time("2") + console.time("2") // Set dimensions of effect canvas this.effectCanvas.width = this.canvas.width this.effectCanvas.height = this.canvas.height @@ -281,7 +281,7 @@ class ChartLoader { this.detectionCanvas.height = this.canvas.height // Draw detection map on the detection canvas this.chart.drawDetectionMap(this.detectionCanvas.getContext("2d")) - //console.timeEnd("2") + console.timeEnd("2") }, 0) } @@ -291,6 +291,7 @@ class ChartLoader { * @param {Array} data - The data to visualize on the chart. */ drawChart(chartSettings, data) { + console.time("1") let zoomManager = new ZoomManager(chartSettings.horizontalZoom, chartSettings.verticalZoom) // Choose the correct type of chart @@ -332,6 +333,8 @@ class ChartLoader { this.chart.draw() + console.timeEnd("1") + this.addInteractivity() this.addListeners(this.chart) } diff --git a/public/scripts/charts/bar_chart.js b/public/scripts/charts/bar_chart.js index 91d1652..1c1fec6 100644 --- a/public/scripts/charts/bar_chart.js +++ b/public/scripts/charts/bar_chart.js @@ -17,83 +17,90 @@ class BarChart extends Chart { /** * Draws the bar chart on the canvas. + * @param {Boolean} async - Says if the chart should be drawn synchronously or asynchronously */ - draw() { + draw(async = true) { // Calculate the number of bars/categories in the chart this.clear() let barCount = this.data.length // Draw the axis without displaying axis values this.drawAxis(false) + // Define function for async + let fn = () => { + // Divide the space into equal section + // Calculate section size + let size = this.zoomBounds.width / this.dataLen + // Callculate inner size with margin + let innerSize = size * 0.8 + // Width of each chart + let bar_width = innerSize * 0.7 / barCount // this.zoom.scaleX - // Divide the space into equal section - // Calculate section size - let size = this.zoomBounds.width / this.dataLen - // Callculate inner size with margin - let innerSize = size * 0.8 - // Width of each chart - let bar_width = innerSize * 0.7 / barCount + // Iterate over each data point to draw the bars + for (let i = 0; i < this.dataLen; i++) { + // Counter to determine the position of each bar within a data point + let num = 0 - // Iterate over each data point to draw the bars - for (let i = 0; i < this.dataLen; i++) { - // Counter to determine the position of each bar within a data point - let num = 0 + this.data.forEach((categ, colId) => { + // Value of the bar + let value = categ.values[i] + // The left position of the bar in section + let left = this.zoomBounds.left + (size * (i + 0.15) + (innerSize * num / barCount)) + // The height of the bar relative to the chart scale + let bar_height = value / this.extreme * this.scale * this.zoom.scaleY + // The top position of the bar + let top = this.zoomBounds.xAxis - categ.values[i] / this.extreme * this.scale * this.zoom.scaleY - this.data.forEach((categ, colId) => { - // Value of the bar - let value = categ.values[i] - // The left position of the bar in section - let left = this.zoomBounds.left + (size * (i + 0.15) + (innerSize * num / barCount)) - // The height of the bar relative to the chart scale - let bar_height = value / this.extreme * this.scale * this.zoom.scaleY - // The top position of the bar - let top = this.zoomBounds.xAxis - categ.values[i] / this.extreme * this.scale * this.zoom.scaleY + // Increment the bar count + num++ - // Increment the bar count - num++ + // Create a new Rectangle object representing the current bar + let newObject = new Rectangle(this.ctx, value, colId, left, top, bar_width, bar_height) + // Add the new Rectangle object to the list of objects + this.objects.push(newObject) - // Create a new Rectangle object representing the current bar - let newObject = new Rectangle(this.ctx, value, colId, left, top, bar_width, bar_height) - // Add the new Rectangle object to the list of objects - this.objects.push(newObject) + // Check if the center of the new bar is within the bounds + if (this.isInBounds(newObject.getCenter())) { + // Set the fill color to the category color + this.ctx.fillStyle = categ.color + // Set the line width to 0 to prevent drawing the border + this.ctx.lineWidth = 0 + // Draw the filled rectangle representing the current bar + newObject.draw() + } + }) + } - // Check if the center of the new bar is within the bounds - if (this.isInBounds(newObject.getCenter())) { - // Set the fill color to the category color - this.ctx.fillStyle = categ.color - // Set the line width to 0 to prevent drawing the border - this.ctx.lineWidth = 0 - // Draw the filled rectangle representing the current bar - newObject.draw() - } - }) - } + // Draw x-axis labels if enabled + if (this.settings.displayAxisValues) { + // Restore canvas state to undo region clipping + this.ctx.restore() + // Loop through each data point to draw the labels + for (let i = 0; i < this.dataLen; i++) { + let text = (i + 1).toString() - // Draw x-axis labels if enabled - if (this.settings.displayAxisValues) { - // Restore canvas state to undo region clipping - this.ctx.restore() - // Loop through each data point to draw the labels - for (let i = 0; i < this.dataLen; i++) { - let text = (i + 1).toString() + // Begin drawing the text + this.ctx.beginPath() + this.ctx.font = "16px Arial" + this.ctx.fillStyle = "black" + this.ctx.textAlign = "center" - // Begin drawing the text - this.ctx.beginPath() - this.ctx.font = "16px Arial" - this.ctx.fillStyle = "black" - this.ctx.textAlign = "center" + // Calculate the position of the label + let x = this.zoomBounds.left + this.zoomBounds.width / this.dataLen * i + size / 2 + let y = this.bounds.bottom + 15 - // Calculate the position of the label - let x = this.zoomBounds.left + this.zoomBounds.width / this.dataLen * i + size / 2 - let y = this.bounds.bottom + 15 - - // Draw the label text - this.ctx.fillText(text, x, y) - // Stroke the text - this.ctx.stroke() - // Close the path - this.ctx.closePath() + // Draw the label text + this.ctx.fillText(text, x, y) + // Stroke the text + this.ctx.stroke() + // Close the path + this.ctx.closePath() + } } - } + } + + // Draw the chart + if (this.settings.displayPoints) + async ? setTimeout(() => fn(), 0) : fn() } } diff --git a/public/scripts/charts/chart.js b/public/scripts/charts/chart.js index 9953955..9f42999 100644 --- a/public/scripts/charts/chart.js +++ b/public/scripts/charts/chart.js @@ -249,6 +249,9 @@ class Chart { // Filter objects based on the column ID let objects = chartLoader.chart.objects.filter(object => object.colId == index) + // Add selected shape + if (chartLoader.selectedShapeIndex !== null) + objects.push(this.objects[chartLoader.selectedShapeIndex]) // Set effect canvas opacity and draw effect chartLoader.effectCanvas.style.opacity = 1 diff --git a/public/scripts/charts/point_chart.js b/public/scripts/charts/point_chart.js index 8d947cc..1bd8160 100644 --- a/public/scripts/charts/point_chart.js +++ b/public/scripts/charts/point_chart.js @@ -44,9 +44,6 @@ class PointChart extends Chart { this.ctx.strokeStyle = this.settings.pointBorderColor } - /*console.log(values) - console.log(this.dataLen)*/ - // Iterate over the values to plot each data point for (let i = 0; i < this.dataLen; i++) { @@ -76,19 +73,16 @@ class PointChart extends Chart { /** * Draws the entire PointChart + * @param {Boolean} async - Says if the chart should be drawn synchronously or asynchronously */ - draw() { + draw(async = true) { // Clear the canvas and draw the axis this.clear() this.drawAxis() // Draw the data points + let fn = () => this.data.forEach((categ, colId) => { this.drawPoints(categ.values, colId, categ.color) }) if (this.settings.displayPoints) - setTimeout(() => { - console.time("1") - this.data.forEach((categ, colId) => { this.drawPoints(categ.values, colId, categ.color) }) - console.timeEnd("1") - }, 0) - + async ? setTimeout(() => fn(), 0) : fn() } } diff --git a/public/scripts/charts/primitives.js b/public/scripts/charts/primitives.js index c543e20..21430ac 100644 --- a/public/scripts/charts/primitives.js +++ b/public/scripts/charts/primitives.js @@ -74,7 +74,7 @@ class Rectangle extends Shape { getCenter() { return { x: this.x + this.w / 2, - y: 50 + y: this.ctx.canvas.clientHeight / 2 } } diff --git a/public/scripts/charts/smooth_line_chart.js b/public/scripts/charts/smooth_line_chart.js index 2d4456c..811e3d9 100644 --- a/public/scripts/charts/smooth_line_chart.js +++ b/public/scripts/charts/smooth_line_chart.js @@ -17,47 +17,54 @@ class SmoothLineChart extends PointChart { /** * Draws the smooth line chart on the canvas. + * @param {Boolean} async - Says if the chart should be drawn synchronously or asynchronously */ - draw() { + draw(async = true) { // Clear the canvas and draw the axis this.clear() this.drawAxis() - // Iterate over each category in the data - this.data.forEach(categ => { - // Begin a new path for drawing lines - this.ctx.beginPath() - this.ctx.lineJoin = "round" - this.ctx.strokeStyle = categ.color - - // Calculate the starting point for the line - let pos1 = this.getPointPos(0, categ.values[0]) - this.ctx.moveTo(pos1.x, pos1.y) - - // Draw the Bezier curve for the smooth line - for (let i = 0; i < this.dataLen - 1; i++) { - // Calculate left point coordinates - let leftPos = this.getPointPos(i, categ.values[i]) - - // Calculate right point coordinates - let rightPos = this.getPointPos(i + 1, categ.values[i + 1]) - - // Find middle point - let xm = (leftPos.x + rightPos.x) / 2 - // Find quarter points - let xl = (leftPos.x + xm) / 2 - let xr = (rightPos.x + xm) / 2 - - // Draw a curve that smoothly connects the points - this.ctx.bezierCurveTo(xl, leftPos.y, xr, rightPos.y, rightPos.x, rightPos.y); - } - this.ctx.stroke() - this.ctx.closePath() - }) - - // Draw points on the chart if required - if (this.settings.displayPoints) - this.data.forEach((categ, colId) => {this.drawPoints(categ.values, colId, categ.color)}) + // Define function for async + let fn = () => { + // Iterate over each category in the data + this.data.forEach(categ => { + // Begin a new path for drawing lines + this.ctx.beginPath() + this.ctx.lineJoin = "round" + this.ctx.strokeStyle = categ.color + + // Calculate the starting point for the line + let pos1 = this.getPointPos(0, categ.values[0]) + this.ctx.moveTo(pos1.x, pos1.y) + + // Draw the Bezier curve for the smooth line + for (let i = 0; i < this.dataLen - 1; i++) { + // Calculate left point coordinates + let leftPos = this.getPointPos(i, categ.values[i]) + + // Calculate right point coordinates + let rightPos = this.getPointPos(i + 1, categ.values[i + 1]) + // Find middle point + let xm = (leftPos.x + rightPos.x) / 2 + // Find quarter points + let xl = (leftPos.x + xm) / 2 + let xr = (rightPos.x + xm) / 2 + + // Draw a curve that smoothly connects the points + this.ctx.bezierCurveTo(xl, leftPos.y, xr, rightPos.y, rightPos.x, rightPos.y); + } + this.ctx.stroke() + this.ctx.closePath() + }) + + // Draw points on the chart if required + if (this.settings.displayPoints) + this.data.forEach((categ, colId) => { this.drawPoints(categ.values, colId, categ.color) }) + } + + // Draw the chart + if (this.settings.displayPoints) + async ? setTimeout(() => fn(), 0) : fn() } } diff --git a/public/scripts/charts/stacked_chart.js b/public/scripts/charts/stacked_chart.js index 8363b0f..d13d3c6 100644 --- a/public/scripts/charts/stacked_chart.js +++ b/public/scripts/charts/stacked_chart.js @@ -24,7 +24,7 @@ class StackedChart extends Chart { for (let i = 0; i < this.dataLen; i++) { let sum = 0 this.data.forEach(categ => { - categ.values[i] = Math.abs(categ.values[i]) + categ.values[i] = Math.abs(categ.values[i]) || 0 sum += categ.values[i] }) largest = sum > largest ? sum : largest @@ -47,7 +47,7 @@ class StackedChart extends Chart { this.data.forEach((categ, colId) => { // Value of the bar segment - let value = categ.values[i] + let value = categ.values[i] || 0 // The height of the bar segment relative to the largest total value let bar_height = value / largest * this.zoomBounds.height // The left position of the bar segment diff --git a/public/scripts/edit_chart.js b/public/scripts/edit_chart.js old mode 100755 new mode 100644 index e5c58ef..23164e5 --- a/public/scripts/edit_chart.js +++ b/public/scripts/edit_chart.js @@ -1,153 +1,21 @@ -/** - * Initializes variables and event listeners after the DOM content is loaded. - */ -let canvas, parent, legend, dataDiv, table - -let rcTarget = {} - -$(document).ready( function () { - canvas = document.getElementById("graphCanvas") - parent = document.getElementById("graphDiv") - legend = document.getElementById("graphLegend") - dataDiv = document.getElementById("dataDiv") - table = new Table(document.getElementById("dataTable")) - - document.getElementById('upload').addEventListener('change', handleFileSelect, false) - - load_data() - table.reloadEvLi() - reloadShares() - - //Click - document.addEventListener('mousemove', (e) => { - const pos = { - x: e.clientX - canvas.offsetLeft, - y: e.clientY - canvas.offsetTop - } - let obj = checkHit(pos) - - //show point value - if (obj !== null) { - dataDiv.style.left = pos.x + canvas.offsetLeft + "px" - dataDiv.style.top = pos.y + canvas.offsetTop + "px" - dataDiv.style.display = "block" - dataDiv.innerHTML = "" + obj.name + "

" + obj.value + "

" - } else { - dataDiv.style.display = "none" - } - }) - - /*$("#exportBtn").on('click', function (e) { - table.reloadData() - exportData('tableData') - }) - - $("#saveBtn").on('click', function (e) { - table.reloadData() - save_data() - }) - - $("#drawBtn").on('click', function (e) { - table.reloadData() - submitData() - }) - - //RIGHT CLICK menu - $(document).bind("click", function(event) { - document.getElementById("rcMenu").style.display = "none" - }) - - //odebere řádek - $("#rcDelRow").on('click', function (e) { - e.preventDefault() - if (rcTarget.parentElement.parentElement.tagName === "THEAD") - return - table.removeRow(rcTarget.parentElement) - }) - - //přidá řádek - $("#rcAddRow").on('click', function (e) { - e.preventDefault() - table.addRow(table, rcTarget) - }) - - //odebere sloupec - $('#rcDelCol').on('click', function (e) { - e.preventDefault() - - table.removeCol(getCellIndex(rcTarget)) - }) - - //přidá sloupec - $('#rcAddCol').on('click', function (e) { - e.preventDefault() - - table.addCol(getCellIndex(rcTarget)) - }) - - //Sharing - $('#shareBtn').on('click', function (e) { - e.preventDefault() - - let username = document.getElementById("shareUsername").value - addShare(username) - })*/ -}) - -/*function handleFileSelect(evt) { - let files = evt.target.files - table.importData(files[0], table) -} - -function submitData() { - table.reloadData() - drawChart(getSettings(), table.data) -} - -function save_data() { - table.reloadData() - let settings = getSettings() - $.ajax({ - url: "php/save_data.php", - type: "post", - dataType: "text", - data: {code: graph_code, data: JSON.stringify(table.data), settings: JSON.stringify(settings), name:settings.title}, - success: function (result) { - //alert("Saved successfully " + result) - } - }) -} - -function load_data() { - $.ajax({ - url: "php/load_data.php", - type: "post", - dataType: "json", - data: {code: graph_code}, - success: function (result) { - if (result.data == null) { - alert("Error: no data found") - return - } - table.data = JSON.parse(result.data) - table.updateTable() - if (result.settings == null) { - alert("Error: no graph settings found") - } else { - loadSettings(JSON.parse(result.settings)) - } - drawChart(getSettings(), table.data) - } - }) -} - -function getCellIndex(cell) { - let parent = cell.parentElement - - let children = Array.from(parent.children) - for (let i = 0; i < children.length; i++){ - if (children[i] === cell) { - return i - } - } -}*/ +function loadEventListeners() { + // Add click event listeners to toggle display of settings submenus + let labels = [...document.getElementsByClassName("submenuLabel")] + labels.forEach(label => { + label.addEventListener('click', e => { + let submenuDiv = e.target.nextElementSibling + + // Toggle display of submenu + if (getComputedStyle(submenuDiv).display == "block") { + submenuDiv.style.display = "none" + label.classList.remove("arrow-up") + label.classList.add("arrow-down") + } + else { + submenuDiv.style.display = "block" + label.classList.remove("arrow-down") + label.classList.add("arrow-up") + } + }) + }) +} diff --git a/public/scripts/edit_chart.js.old b/public/scripts/edit_chart.js.old new file mode 100755 index 0000000..e5c58ef --- /dev/null +++ b/public/scripts/edit_chart.js.old @@ -0,0 +1,153 @@ +/** + * Initializes variables and event listeners after the DOM content is loaded. + */ +let canvas, parent, legend, dataDiv, table + +let rcTarget = {} + +$(document).ready( function () { + canvas = document.getElementById("graphCanvas") + parent = document.getElementById("graphDiv") + legend = document.getElementById("graphLegend") + dataDiv = document.getElementById("dataDiv") + table = new Table(document.getElementById("dataTable")) + + document.getElementById('upload').addEventListener('change', handleFileSelect, false) + + load_data() + table.reloadEvLi() + reloadShares() + + //Click + document.addEventListener('mousemove', (e) => { + const pos = { + x: e.clientX - canvas.offsetLeft, + y: e.clientY - canvas.offsetTop + } + let obj = checkHit(pos) + + //show point value + if (obj !== null) { + dataDiv.style.left = pos.x + canvas.offsetLeft + "px" + dataDiv.style.top = pos.y + canvas.offsetTop + "px" + dataDiv.style.display = "block" + dataDiv.innerHTML = "" + obj.name + "

" + obj.value + "

" + } else { + dataDiv.style.display = "none" + } + }) + + /*$("#exportBtn").on('click', function (e) { + table.reloadData() + exportData('tableData') + }) + + $("#saveBtn").on('click', function (e) { + table.reloadData() + save_data() + }) + + $("#drawBtn").on('click', function (e) { + table.reloadData() + submitData() + }) + + //RIGHT CLICK menu + $(document).bind("click", function(event) { + document.getElementById("rcMenu").style.display = "none" + }) + + //odebere řádek + $("#rcDelRow").on('click', function (e) { + e.preventDefault() + if (rcTarget.parentElement.parentElement.tagName === "THEAD") + return + table.removeRow(rcTarget.parentElement) + }) + + //přidá řádek + $("#rcAddRow").on('click', function (e) { + e.preventDefault() + table.addRow(table, rcTarget) + }) + + //odebere sloupec + $('#rcDelCol').on('click', function (e) { + e.preventDefault() + + table.removeCol(getCellIndex(rcTarget)) + }) + + //přidá sloupec + $('#rcAddCol').on('click', function (e) { + e.preventDefault() + + table.addCol(getCellIndex(rcTarget)) + }) + + //Sharing + $('#shareBtn').on('click', function (e) { + e.preventDefault() + + let username = document.getElementById("shareUsername").value + addShare(username) + })*/ +}) + +/*function handleFileSelect(evt) { + let files = evt.target.files + table.importData(files[0], table) +} + +function submitData() { + table.reloadData() + drawChart(getSettings(), table.data) +} + +function save_data() { + table.reloadData() + let settings = getSettings() + $.ajax({ + url: "php/save_data.php", + type: "post", + dataType: "text", + data: {code: graph_code, data: JSON.stringify(table.data), settings: JSON.stringify(settings), name:settings.title}, + success: function (result) { + //alert("Saved successfully " + result) + } + }) +} + +function load_data() { + $.ajax({ + url: "php/load_data.php", + type: "post", + dataType: "json", + data: {code: graph_code}, + success: function (result) { + if (result.data == null) { + alert("Error: no data found") + return + } + table.data = JSON.parse(result.data) + table.updateTable() + if (result.settings == null) { + alert("Error: no graph settings found") + } else { + loadSettings(JSON.parse(result.settings)) + } + drawChart(getSettings(), table.data) + } + }) +} + +function getCellIndex(cell) { + let parent = cell.parentElement + + let children = Array.from(parent.children) + for (let i = 0; i < children.length; i++){ + if (children[i] === cell) { + return i + } + } +}*/ diff --git a/public/scripts/table.js b/public/scripts/table.js index f3751cd..91c7624 100644 --- a/public/scripts/table.js +++ b/public/scripts/table.js @@ -47,20 +47,6 @@ class Table { }) - // Add click event listeners to toggle display of settings submenus - let labels = [...document.getElementsByClassName("submenuLabel")] - labels.forEach(label => { - label.addEventListener('click', e => { - let submenuDiv = e.target.nextElementSibling - - // Toggle display of submenu - if (getComputedStyle(submenuDiv).display == "block") - submenuDiv.style.display = "none" - else - submenuDiv.style.display = "block" - }) - }) - this.rcAddRow.addEventListener("mousedown", (e) => { this.addRow() }) this.rcDelRow.addEventListener("mousedown", (e) => { this.delRow() }) this.rcAddCol.addEventListener("mousedown", (e) => { this.addCol() }) @@ -141,7 +127,6 @@ class Table { for (let j = 0; j < cells.length; j++) { let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${pos.row}`) currentCell.value = "" - console.log(currentCell) } } @@ -238,11 +223,15 @@ class Table { headCell.querySelector("input[type='color']").value = "#FFFFFF" } + /** + * Deletes the selected column from the table. + */ delCol() { let rows = Array.from(this.tableBody.children) let rowCount = rows.length let colCount = this.tableBody.lastElementChild.children.length + // Shift existing column data to the left. let pos = this.getCellPos(this.selectedCell) for (let i = 0; i < rowCount; i++) { for (let j = pos.col; j < colCount - 1; j++) { @@ -252,22 +241,26 @@ class Table { } } + // Shift existing column titles to the left. for (let i = pos.col; i < colCount - 1; i++) { let currentCell = this.tableHead.querySelector(`#chart_table_${i}_col_name`) let previousCell = this.tableHead.querySelector(`#chart_table_${i + 1}_col_name`) currentCell.value = previousCell.value } + // Shift existing column colors to the left. for (let i = pos.col; i < colCount - 1; i++) { let currentCell = this.tableHead.querySelector(`#chart_table_${i}_color`) let previousCell = this.tableHead.querySelector(`#chart_table_${i + 1}_color`) currentCell.value = previousCell.value } + // Remove the last cell from each row. rows.forEach(row => { row.removeChild(row.lastElementChild) }) + // Remove the last header cell. this.tableHead.lastElementChild.removeChild(this.tableHead.lastElementChild.lastElementChild) } } diff --git a/public/scripts/zoom_manager.js b/public/scripts/zoom_manager.js index eb84716..f162cea 100644 --- a/public/scripts/zoom_manager.js +++ b/public/scripts/zoom_manager.js @@ -105,4 +105,4 @@ class ZoomManager { this.scaleY = 1 } } -} \ No newline at end of file +} diff --git a/public/styles/edit_chart.css b/public/styles/edit_chart.css index 5740964..293fcff 100755 --- a/public/styles/edit_chart.css +++ b/public/styles/edit_chart.css @@ -23,27 +23,55 @@ main { } /* Styles for submenu label */ -.submenuLabel + div { +.submenuLabel+div { display: none; } /* Styles for chart metadata groups */ div[id^="chart_metadata_group"] { - padding: 0.5em; /* Padding around metadata groups */ - margin-bottom: 0.5em; /* Margin below metadata groups */ - background-color: var(--side); /* Background color */ + padding: 0.5em; + /* Padding around metadata groups */ + margin-bottom: 0.5em; + /* Margin below metadata groups */ + background-color: var(--side); + /* Background color */ +} + +/* Style for arrow after submenu label */ +.submenuLabel::after { + content: " "; + border: solid black; + border-width: 0 2px 2px 0; + display: inline-block; + padding: 0.2rem; + margin-left: 0.5rem; +} + +.arrow-down::after { + transform: rotate(45deg); + -webkit-transform: rotate(45deg); +} + +.arrow-up::after { + transform: rotate(225deg); + -webkit-transform: rotate(225deg); } + /* Styles for the table container div */ #tableDiv { - padding: 0; /* Remove padding */ - overflow: auto; /* Add scrollbars if content overflows */ - flex-basis: 100%; /* Set the size of the table area */ + padding: 0; + /* Remove padding */ + overflow: auto; + /* Add scrollbars if content overflows */ + flex-basis: 100%; + /* Set the size of the table area */ } /* Styles for secondary div */ #secondaryDiv { - display: flex; /* Use flexbox layout */ + display: flex; + /* Use flexbox layout */ } /* Styles for table elements */ @@ -55,7 +83,8 @@ div[id^="chart_metadata_group"] { #dataTable td, #dataTable th { - height: 2em; /* Set height of table cells */ + height: 2em; + /* Set height of table cells */ } /* Styles for table input elements */ @@ -106,7 +135,7 @@ div[id^="chart_metadata_group"] { width: 180px; } -#rcMenu ul{ +#rcMenu ul { list-style-type: none; padding-left: 10px; width: 180px; @@ -119,7 +148,7 @@ div[id^="chart_metadata_group"] { display: block; } -#rcMenu a:hover{ +#rcMenu a:hover { background-color: lightgrey; } diff --git a/src/Api/Controller/ChartRestController.php b/src/Api/Controller/ChartRestController.php index 4b37c95..5c5d4d3 100644 --- a/src/Api/Controller/ChartRestController.php +++ b/src/Api/Controller/ChartRestController.php @@ -18,14 +18,14 @@ class ChartRestController extends AbstractRestController #[Rest\Get('/', name: '_list')] public function list(DocumentManager $dm, Request $request) : Response { - $rooms = \array_map(fn(Chart $entity) => ChartOutput::fromEntity($entity), $dm->getRepository(Chart::class)->findAll); - return $this->respond($rooms, Response::HTTP_OK); + $charts = \array_map(fn(Chart $entity) => ChartOutput::fromEntity($entity)->id, $dm->getRepository(Chart::class)->findAll()); + return $this->respond($charts, Response::HTTP_OK); } - #[Rest\Get('/{id}', name: '_detail', requirements: ['id' => '\d+'])] - public function detail(DocumentManager $dm, int $id): Response + #[Rest\Get('/{id}', name: '_detail')]//, requirements: ['id' => '\d+'])] + public function detail(DocumentManager $dm, Chart $chart): Response { - $chart = $this->findOrFail($dm, $id); + //$chart = $this->findOrFail($dm, $id); $chartDto = ChartOutput::fromEntity($chart); return $this->respond( $chartDto, Response::HTTP_OK); @@ -47,13 +47,15 @@ class ChartRestController extends AbstractRestController return $this->respond( $chart->getName(), Response::HTTP_OK); } - private function findOrFail(DocumentManager $dm, int $id): Chart + /*private function findOrFail(DocumentManager $dm, int $id): Chart { - $room = $dm->getRepository(Chart::class)->findOneBy(['code' => $id]); - if ($room === null) { + //$chart = $dm->getRepository(Chart::class)->findOneBy(['code' => $id]); + $chart = $dm->getRepository(Chart::class)->findOneBy(['_id' => $id]); + + if ($chart === null) { throw $this->createNotFoundException("Chart was not found"); } - return $room ; - } + return $chart ; + }*/ } \ No newline at end of file diff --git a/src/Controller/ChartController.php b/src/Controller/ChartController.php index 05d4a93..24c260f 100644 --- a/src/Controller/ChartController.php +++ b/src/Controller/ChartController.php @@ -11,21 +11,21 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Redirect; use Symfony\Component\Routing\Annotation\Route; +#[Route(path: '/charts', name: 'charts')] class ChartController extends AbstractController { - /*#[Route('/charts', name: 'list_charts')] - public function registerAction(): Response + #[Route('/', name: '_list')] + public function list(DocumentManager $dm, Request $request) : Response { - //$form = $this->createForm(RegistrationType::class, new Registration()); - - return $this->render('.html.twig', []); - }*/ + $charts = $dm->getRepository(Chart::class)->findAll(); + return $this->render('list.html.twig', [ + 'charts' => $charts + ]); + } - #[Route('/charts/{id}/edit', name: 'edit_chart', requirements: ['id' => '\d+'])] - public function editAction(DocumentManager $dm, Request $request, int $id) + #[Route('/{id}/edit', name: '_edit')] + public function editAction(DocumentManager $dm, Request $request, Chart $chart) : Response { - $chart = ($id !== null) ? $this->findOrFail($dm, $id) : new Chart(); - $form = $this->createForm(ChartType::class, $chart); $form->handleRequest($request); @@ -39,21 +39,20 @@ class ChartController extends AbstractController } return $this->render('edit.html.twig', [ - 'chartForm' => $form + 'chartForm' => $form, + 'id' => $chart->getId(), ]); } - #[Route('/charts/{id}', name: 'display_chart', requirements: ['id' => '\d+'])] - public function displayAction(DocumentManager $dm, Request $request, int $id) + #[Route('/{id}', name: '_display')] + public function displayAction(DocumentManager $dm, Chart $chart) : Response { - $chart = ($id !== null) ? $this->findOrFail($dm, $id) : new Chart(); - return $this->render('chart.html.twig', [ - 'code' => $id + 'id' => $chart->getId() ]); } - private function findOrFail(DocumentManager $dm, int $id): Chart + /*private function findOrFail(DocumentManager $dm, int $id): Chart { $chart = $dm->getRepository(Chart::class)->findOneBy(['code' => $id]); //$chart = $dm->getRepository(Chart::class)->findAll(); @@ -62,5 +61,5 @@ class ChartController extends AbstractController } return $chart; - } + }*/ } diff --git a/src/Form/Type/ChartType.php b/src/Form/Type/ChartType.php index f5686da..28c5463 100644 --- a/src/Form/Type/ChartType.php +++ b/src/Form/Type/ChartType.php @@ -17,13 +17,16 @@ class ChartType extends AbstractType $builder ->add('name', TextType::class) ->add('code', TextType::class) - ->add('metadata', MetadataType::class) - ->add('table', CollectionType::class, [ - 'entry_type' => ColumnType::class, - 'allow_add' => true, - /*'prototype' => true, - 'prototype_data' => 'Placeholder'*/ - ]); + ->add('metadata', MetadataType::class); + + if (sizeof($options['data']->getTable()[0]['values']) <= 1000) + $builder + ->add('table', CollectionType::class, [ + 'entry_type' => ColumnType::class, + 'allow_add' => true, + /*'prototype' => true, + 'prototype_data' => 'Placeholder'*/ + ]); } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Form/Type/ColumnType.php b/src/Form/Type/ColumnType.php index 86c00bd..058faa8 100644 --- a/src/Form/Type/ColumnType.php +++ b/src/Form/Type/ColumnType.php @@ -1,6 +1,7 @@ add('col_name', TextType::class, [ 'label' => false, - 'attr'=>['autocomplete' => 'off'] + 'attr' => ['autocomplete' => 'off'] ]) ->add('color', ColorType::class, [ 'label' => false, @@ -26,7 +27,7 @@ class ColumnType extends AbstractType 'allow_add' => true, 'entry_options' => [ 'label' => false, - 'attr'=>['autocomplete' => 'off'] + 'attr' => ['autocomplete' => 'off'] ], 'required' => false ]); diff --git a/src/Form/Type/MetadataType.php b/src/Form/Type/MetadataType.php index 5238874..4a5a2ae 100644 --- a/src/Form/Type/MetadataType.php +++ b/src/Form/Type/MetadataType.php @@ -38,19 +38,26 @@ class MetadataType extends AbstractType ], 'label' => 'Chart type', ]) - ->add('margin', IntegerType::class, [ - 'label' => 'Margin', - 'required' => false, - 'constraints' => [ - new Assert\PositiveOrZero() - ], - ]) + ->add( + $builder->create('group0', FormType::class, [ + 'inherit_data' => true, + 'label' => 'Margin settings', + 'label_attr' => ['class' => 'submenuLabel arrow-down'] + ]) + ->add('margin', IntegerType::class, [ + 'label' => 'Margin', + 'required' => false, + 'constraints' => [ + new Assert\PositiveOrZero() + ], + ]) + ) // Title settings ->add( $builder->create('group1', FormType::class, [ 'inherit_data' => true, 'label' => 'Title settings', - 'label_attr' => ['class' => 'submenuLabel'] + 'label_attr' => ['class' => 'submenuLabel arrow-down'] ]) ->add('title', TextType::class, [ 'label' => 'Title', @@ -71,7 +78,7 @@ class MetadataType extends AbstractType $builder->create('group2', FormType::class, [ 'inherit_data' => true, 'label' => 'Labels', - 'label_attr' => ['class' => 'submenuLabel'] + 'label_attr' => ['class' => 'submenuLabel arrow-down'] ]) ->add('xLabel', TextType::class, [ 'label' => 'X label', @@ -106,7 +113,7 @@ class MetadataType extends AbstractType $builder->create('group3', FormType::class, [ 'inherit_data' => true, 'label' => 'Legend settings', - 'label_attr' => ['class' => 'submenuLabel'] + 'label_attr' => ['class' => 'submenuLabel arrow-down'] ]) ->add('displayLegend', CheckboxType::class, [ 'label' => 'Display legend', @@ -122,7 +129,7 @@ class MetadataType extends AbstractType $builder->create('group4', FormType::class, [ 'inherit_data' => true, 'label' => 'Point Settings', - 'label_attr' => ['class' => 'submenuLabel'] + 'label_attr' => ['class' => 'submenuLabel arrow-down'] ]) ->add('displayPoints', CheckboxType::class, [ 'label' => 'Display points', @@ -152,7 +159,7 @@ class MetadataType extends AbstractType $builder->create('group5', FormType::class, [ 'inherit_data' => true, 'label' => 'Zoom Settings', - 'label_attr' => ['class' => 'submenuLabel'] + 'label_attr' => ['class' => 'submenuLabel arrow-down'] ]) ->add('horizontalZoom', CheckboxType::class, [ 'label' => 'Enable horizontal zoom', @@ -167,20 +174,22 @@ class MetadataType extends AbstractType 'label' => 'Background color', 'required' => false, ]) - ->add('submit', SubmitType::class, ['label' => 'Provést']); + ->add('submit', SubmitType::class, ['label' => 'Uložit a vykreslit']); } public function configureOptions(OptionsResolver $resolver) { - $resolver->setDefaults(array( - 'constraints' => array( - new Assert\Callback(function($data){ - // $data is instance of object (or array) with all properties - // you can compare Count1, Count2 and Count 3 - // and raise validation errors - }) + $resolver->setDefaults( + array( + 'constraints' => array( + new Assert\Callback(function ($data) { + // $data is instance of object (or array) with all properties + // you can compare Count1, Count2 and Count 3 + // and raise validation errors + }) + ) ) - )); + ); /*$resolver->setDefaults([ 'type' => 'point', 'margin'=> 5, diff --git a/templates/chart.html.twig b/templates/chart.html.twig index 4a39ece..823add0 100644 --- a/templates/chart.html.twig +++ b/templates/chart.html.twig @@ -38,7 +38,7 @@ // Create ChartLoader instance let chartLoader = new ChartLoader(canvas, effectCanvas, detectionCanvas, parent, legend, tooltip) - chartLoader.loadData({{ code }}) + chartLoader.loadData({{ id | json_encode() | raw() }}) }) {% endblock %} diff --git a/templates/edit.html.twig b/templates/edit.html.twig index 2017d51..b60c6b6 100644 --- a/templates/edit.html.twig +++ b/templates/edit.html.twig @@ -9,16 +9,19 @@ {% block javascripts %} {{ parent() }} - + + }) + {% endblock %} {% block title %} @@ -30,36 +33,40 @@
{{ form_start(chartForm) }}
-
- {{ form_row(chartForm.name) }} - {{ form_row(chartForm.code) }} + {{ form_row(chartForm.name) }} + {{ form_row(chartForm.code) }} {{ form_row(chartForm.metadata) }} + -->
+
- - - - {% for col in chartForm.table %} - - {% endfor %} - - - {% for i in 0..chartForm.table[0].values|length-1 %} - - {% for col in chartForm.table %} - - {% endfor %} - - {% endfor %} -
{{ form_row(col.col_name) }}{{ form_row(col.color) }}
{{ form_row(col.values[i]) }}
+ {% if chartForm.table is defined %} + + + + {% for col in chartForm.table %} + + {% endfor %} + + + {% for i in 0..chartForm.table[0].values|length-1 %} + + {% for col in chartForm.table %} + + {% endfor %} + + {% endfor %} +
{{ form_row(col.col_name) }}{{ form_row(col.color) }}
{{ form_row(col.values[i]) }}
+ {% else %} + Příliš mnoho hodnot, použijte API + {% endif %}
- {{ form_end(chartForm) }} + {{ form_end(chartForm) }}
  • add row
  • diff --git a/templates/header.html.twig b/templates/header.html.twig index 25d47b7..608c7ea 100644 --- a/templates/header.html.twig +++ b/templates/header.html.twig @@ -3,7 +3,7 @@ EasyCharts
Login diff --git a/templates/list.html.twig b/templates/list.html.twig new file mode 100644 index 0000000..9a102d9 --- /dev/null +++ b/templates/list.html.twig @@ -0,0 +1,16 @@ +{% extends 'base.html.twig' %} + +{% block title %} + List +{% endblock %} + +{% block body %} + {{ parent() }} +
+ +
+{% endblock %}