diff --git a/public/scripts/chart.js.old b/public/scripts/chart.js.old deleted file mode 100755 index d90dfdc..0000000 --- a/public/scripts/chart.js.old +++ /dev/null @@ -1,528 +0,0 @@ -function Rect(value, name, x, y, w, h){ - this.value = value; - this.name = name; - this.x = x; - this.y = y; - this.w = w; - this.h = h; - this.checkHit = function (mouseX, mouseY) { - return (mouseX >= x && mouseX <= x + w && mouseY >= y && mouseY <= y + h); - } -} - -function Circle(value, name, x, y, r){ - this.value = value; - this.name = name; - this.x = x; - this.y = y; - this.r = r; - this.checkHit = function (mouseX, mouseY) { - return Math.pow((mouseX - x),2) + Math.pow((mouseY - y),2) <= Math.pow(r, 2); - } -} - -function PieSlice(value, name, x, y, r, sAngle, eAngle){ - this.value = value; - this.name = name; - this.x = x; - this.y = y; - this.r = r; - this.sAngle = sAngle; - this.eAngle = eAngle; - this.checkHit = function (mouseX, mouseY) { - if (Math.pow((mouseX - x),2) + Math.pow((mouseY - y),2) <= Math.pow(r, 2)){ - var dy = mouseY - y; - var dx = mouseX - x; - var theta = Math.atan2(dy, dx); // range (-PI, PI] - if (theta < 0) theta += 2*Math.PI; - - return (theta > sAngle && theta < eAngle); - } - } -} - -let objects = []; - -function checkHit(pos) { - for (let i = 0; i < objects.length; i++){ - if (objects[i].checkHit(pos.x, pos.y)){ - return objects[i]; - } - } - return null; -} - -function getLargest(data) { - let largest = data[0].values[0]; - - data.forEach(function (categ) { - for (let i = 0; i < categ.values.length; i++) - if (categ.values[i] > largest) { - largest = categ.values[i]; - } - }); - - return largest; -} - -function getSmallest(data) { - let smallest = data[0].values[0]; - - data.forEach(function (categ) { - for (let i = 0; i < categ.values.length; i++) - if (categ.values[i] < smallest) { - smallest = categ.values[i]; - } - }); - - return smallest; -} - -function drawAxis(bounds, largest, smallest, arrayLen, ctx, graphSettings, drawValues = true) { - ctx.font = "16px Arial"; - if (graphSettings.y_step <= 0) graphSettings.y_step = 1; - - ctx.beginPath(); - for (let i = (smallest < 0)?smallest:0; i <= ((largest>=0)?largest:0); i += parseFloat(graphSettings.y_step)){ - - ctx.strokeStyle = "#BBB"; - ctx.lineWidth = 1; - let scale = bounds.height - ((largest >= 0)?(bounds.bottom - bounds.xAxis):0); - let extreme = (largest<=0)?Math.abs(smallest):Math.abs(largest); - let yPos = Math.round((bounds.xAxis - i * scale / extreme)); - - //support line - if (graphSettings.display_support_lines) { - - ctx.moveTo( bounds.left, yPos ); - ctx.lineTo( bounds.right, yPos ); - } - - //Y axis value - ctx.fillStyle = "black"; - ctx.textAlign = "center"; - ctx.textAlign = "end"; - ctx.fillText( i, bounds.left - 3, yPos); - ctx.stroke(); - } - - //X axis value - if (drawValues) { - ctx.fillStyle = "black"; - ctx.textAlign = "center"; - for (let i = 0; i < arrayLen; i++){ - let x = bounds.left + bounds.width / (arrayLen - 1) * i; - let text = (i + 1).toString(); - if (graphSettings.custom_x_values !== "") - text = graphSettings.custom_x_values.split(';')[i]; - ctx.fillText(text, x, bounds.bottom + 18); - } - ctx.closePath(); - } - - //X and Y axis - ctx.strokeStyle = "black"; - ctx.lineWidth = "2px"; - ctx.beginPath(); - ctx.moveTo( bounds.left, bounds.top ); - ctx.lineTo( bounds.left, bounds.bottom ); - ctx.moveTo( bounds.left, bounds.xAxis); - ctx.lineTo( bounds.right, bounds.xAxis ); - ctx.stroke(); - - //Axis labels - //X axis text - ctx.beginPath(); - ctx.font = "20px Arial"; - ctx.fillStyle = "black"; - ctx.textAlign = "center"; - ctx.fillText(graphSettings.x_label, bounds.width/2 + bounds.left, bounds.height + 2*bounds.top - 5); - - //Y axis text - ctx.save(); - ctx.rotate(-Math.PI / 2); - ctx.textAlign = "center"; - ctx.fillText(graphSettings.y_label, -(bounds.left + bounds.height/2), 18); - ctx.restore(); - ctx.stroke(); -} - -function drawTitle(canvas, graphSettings) { - let ctx = canvas.getContext("2d"); - let x = canvas.width / 2; - let y = 25; - - ctx.font = "30px Arial"; - ctx.fillStyle = "black"; - ctx.textAlign = "center"; - ctx.fillText( graphSettings.title, x, y); -} - -function drawPoints(ctx, bounds, values, name, arrayLen, largest, color) { - ctx.fillStyle = color; - let radius = 3; - - for( let i = 0; i < arrayLen; i++ ){ - ctx.beginPath(); - if(values[i] === null) continue; - let scale = bounds.height - ((largest >= 0)?(bounds.bottom - bounds.xAxis):0); - let extreme = (largest<=0)?Math.abs(smallest):Math.abs(largest); - let x = bounds.left + bounds.width / (arrayLen - 1) * i; - let y = (bounds.xAxis - values[i] / extreme * scale); - ctx.arc(x, y, radius, 0, 2 * Math.PI); - ctx.fill(); - ctx.stroke(); - ctx.closePath(); - - let new_object = new Circle(values[i], name, x, y, radius); - objects.push(new_object); - } -} - -function drawSlice(ctx, centerX, centerY, radius, startAngle, endAngle, color ){ - ctx.fillStyle = color; - ctx.beginPath(); - ctx.moveTo(centerX,centerY); - ctx.arc(centerX, centerY, radius, startAngle, endAngle); - ctx.fill(); - ctx.closePath(); -} - -function getBounds(canvas, graphMargin) { - return { - top: graphMargin, - bottom: canvas.height - graphMargin, - left: graphMargin, - right: canvas.width - graphMargin, - height: canvas.height - 2*graphMargin, - width: canvas.width - 2*graphMargin, - xAxis: canvas.height - graphMargin - }; -} - -function drawPieChart(canvas, data, graphSettings) { - - let ctx = canvas.getContext("2d"); - let index = 0; - let start_angle = 0; - let total_value = 0; - let bounds = getBounds(canvas, graphSettings.margin); - - - data.forEach(function (categ) { - let val = categ.values[0]; - if (val !== null) - total_value += val; - }); - - data.forEach(function (categ) { - let val = categ.values[0]; - let slice_angle = 2 * Math.PI * val / total_value; - - let x = canvas.width/2; - let y = canvas.height/2; - let r = Math.min(bounds.width/2, bounds.height/2); - let end_angle = start_angle + slice_angle; - - drawSlice(ctx, x, y, r, start_angle, end_angle, categ.color); - let new_object = new PieSlice(val + " (" + Math.round(val/total_value*100) + "%)", categ.name, x, y, r, start_angle, end_angle); - objects.push(new_object); - - start_angle = end_angle; - index++; - }); - - -} - -function drawPointChart(canvas, data, graphSettings){ - let ctx = canvas.getContext( "2d" ); - - let bounds = getBounds(canvas, graphSettings.margin); - let largest = getLargest(data); - let smallest = getSmallest(data); - - if (smallest < 0) - bounds.xAxis = bounds.bottom - (bounds.height / (((largest<=0)?0:Math.abs(largest)) + Math.abs(smallest)) * Math.abs(smallest)); - let arrayLen = data[0].values.length; - - drawAxis(bounds, largest, smallest, arrayLen, ctx, graphSettings); - - data.display_points = true; - data.forEach(function (categ) { - //Points - if (graphSettings.display_points) - drawPoints(ctx, bounds, categ.values, categ.name, arrayLen, largest, categ.color); - }); -} - -function drawLineChart(canvas, data, graphSettings){ - let ctx = canvas.getContext( "2d" ); - - let bounds = getBounds(canvas, graphSettings.margin); - let largest = getLargest(data); - let smallest = getSmallest(data); - - if (smallest < 0) - bounds.xAxis = bounds.bottom - (bounds.height / (((largest<=0)?0:Math.abs(largest)) + Math.abs(smallest)) * Math.abs(smallest)); - let arrayLen = data[0].values.length; - - drawAxis(bounds, largest, smallest, arrayLen, ctx, graphSettings); - - data.forEach(function (categ) { - //Lines - ctx.beginPath(); - ctx.lineJoin = "round"; - ctx.strokeStyle = categ.color; - - for (let i = 0; i < arrayLen; i++) { - if (categ.values[i] === null) continue; - - let scale = bounds.height - ((largest >= 0)?(bounds.bottom - bounds.xAxis):0); - let extreme = (largest<=0)?Math.abs(smallest):Math.abs(largest); - let x = bounds.left + bounds.width / (arrayLen - 1) * i; - let y = (bounds.xAxis - categ.values[i] / extreme * scale); - - ctx.lineTo(x, y); - } - ctx.stroke(); - ctx.closePath(); - - //Points - if (graphSettings.display_points) - drawPoints(ctx, bounds, categ.values, categ.name, arrayLen, largest, categ.color); - }); -} - -function drawAreaChart(canvas, data, graphSettings){ - let ctx = canvas.getContext( "2d" ); - - let bounds = getBounds(canvas, graphSettings.margin); - let largest = getLargest(data); - let smallest = getSmallest(data); - - if (smallest < 0) - bounds.xAxis = bounds.bottom - (bounds.height / (((largest<=0)?0:Math.abs(largest)) + Math.abs(smallest)) * Math.abs(smallest)); - let arrayLen = data[0].values.length; - - drawAxis(bounds, largest, smallest, arrayLen, ctx, graphSettings); - - data.forEach(function (categ) { - //Lines - ctx.beginPath(); - ctx.lineJoin = "round"; - ctx.strokeStyle = categ.color; - - let xmax = 0; - for (let i = 0; i < arrayLen; i++) { - if (categ.values[i] === null) continue; - let scale = bounds.height - ((largest >= 0)?(bounds.bottom - bounds.xAxis):0); - let extreme = (largest<=0)?Math.abs(smallest):Math.abs(largest); - let x = bounds.left + bounds.width / (arrayLen - 1) * i; - let y = (bounds.xAxis - categ.values[i] / extreme * scale); - xmax = x; - - ctx.lineTo(x, y); - } - ctx.stroke(); - - ctx.lineTo(xmax, bounds.xAxis); - ctx.lineTo(bounds.left, bounds.xAxis); - ctx.globalAlpha = 0.5; - ctx.fillStyle = categ.color; - ctx.closePath(); - ctx.fill(); - - ctx.globalAlpha = 1; - - //Points - if (graphSettings.display_points) - drawPoints(ctx, bounds, categ.values, categ.name, arrayLen, largest, categ.color); - }); -} - -function drawBarChart(canvas, data, graphSettings) { - let ctx = canvas.getContext( "2d" ); - - let bounds = getBounds(canvas, graphSettings.margin); - ctx.shadowOffsetX = 15; - ctx.shadowOffsetY = 15; - ctx.shadowBlur = 4; - - let largest = getLargest(data); - let barCount = data.length; - let dataCount = data[0].values.length; - - let smallest = getSmallest(data); - - if (smallest < 0) - bounds.xAxis = bounds.bottom - (bounds.height / (((largest<=0)?0:Math.abs(largest)) + Math.abs(smallest)) * Math.abs(smallest)); - - drawAxis(bounds, largest, smallest, dataCount, ctx, graphSettings, false); - - let size = bounds.width / dataCount; - let innerSize = size * 0.8; - let bar_width = innerSize * 0.7 / barCount; - - for (let i = 0; i < dataCount; i++) { - let num = 0; - data.forEach(function (categ) { - ctx.beginPath(); - let value = categ.values[i]; - let left = bounds.left + (size * (i + 0.15) + (innerSize * num / barCount)); - let scale = bounds.height - ((largest >= 0)?(bounds.bottom - bounds.xAxis):0); - let extreme = (largest<=0)?Math.abs(smallest):Math.abs(largest); - let bar_height = value / extreme * scale; - let top = (bounds.xAxis - categ.values[i] / extreme * scale); - - ctx.fillStyle = categ.color; - ctx.fillRect(left, top, bar_width, bar_height); - - //x value - if (num === 0){ - let text = (i + 1).toString(); - if (graphSettings.custom_x_values !== ""){ - text = graphSettings.custom_x_values.split(';')[i]; - } - ctx.font = "16px Arial"; - ctx.fillStyle = "black"; - ctx.textAlign = "center"; - ctx.fillText(text, bounds.width / dataCount * i + size / 2 + bounds.left, bounds.bottom + 15); - ctx.stroke(); - } - - num++; - let new_object = new Rect(value, categ.name, left, top, bar_width, bar_height); - objects.push(new_object); - }); - } -} - -function stackedChart(canvas, data, graphSettings) { - let ctx = canvas.getContext( "2d" ); - - let bounds = getBounds(canvas, graphSettings.margin); - ctx.shadowOffsetX = 15; - ctx.shadowOffsetY = 15; - ctx.shadowBlur = 4; - - let dataCount = data[0].values.length; - - let largest = 0; - for (let i = 0; i < dataCount; i++){ - let sum = 0; - data.forEach(function (categ) { - categ.values[i] = Math.abs(categ.values[i]); - sum += categ.values[i]; - }); - if (sum > largest) largest = sum; - } - - let smallest = getSmallest(data); - if (smallest < 0) - bounds.xAxis = bounds.bottom - (bounds.height / (((largest<=0)?0:Math.abs(largest)) + Math.abs(smallest)) * Math.abs(smallest)); - - drawAxis(bounds, largest, smallest, dataCount, ctx, graphSettings, false); - - let size = bounds.width / dataCount; - let bar_width = size * 0.7; - - for (let i = 0; i < dataCount; i++) { - let last_top = bounds.xAxis; - let num = 0; - data.forEach(function (categ) { - ctx.beginPath(); - - let value = categ.values[i]; - let bar_height = value / largest * bounds.height; - let left = bounds.left + (size * (i + 0.15)); - let top = last_top - bar_height; - - ctx.fillStyle = categ.color; - ctx.fillRect(left, top, bar_width, bar_height); - - last_top = top; - - //x value - if (num === 0){ - let text = (i + 1).toString(); - if (graphSettings.custom_x_values !== ""){ - text = graphSettings.custom_x_values.split(';')[i]; - } - ctx.font = "16px Arial"; - ctx.fillStyle = "black"; - ctx.textAlign = "center"; - ctx.fillText(text, bounds.width / dataCount * i + size / 2 + bounds.left, bounds.bottom + 15); - ctx.stroke(); - } - num++; - - let new_object = new Rect(value, categ.name, left, top, bar_width, bar_height); - objects.push(new_object); - }); - } -} - -function resizeCanvas(canvas, parent, legendHeight = 0, bgColor) { - - if (legendHeight > 0) legendHeight += 3.1; - //set size - canvas.style.width = parent.clientWidth.toString(); - canvas.style.height = (parent.clientHeight - legendHeight).toString(); - canvas.width = parent.clientWidth; - canvas.height = parent.clientHeight - legendHeight; - - //reset canvas color - let ctx = canvas.getContext( "2d" ); - if (bgColor == null){ - ctx.clearRect(0, 0, canvas.width, canvas.height); - } else { - ctx.fillStyle = bgColor; - ctx.fillRect(0, 0, canvas.width, canvas.height); - } -} - -function updateLegend(displayLegend, data) { - if (displayLegend){ - let legendHTML = ""; - data.forEach(function (categ) { - legendHTML += "
" + obj.value + "
" + } + else { + this.dataDiv.style.display = "none" + } + }) + + window.addEventListener("resize", e => { + //newChart.updateLegend(graphSettings.displayLegend, data) + newChart.resizeCanvas(this.parent, this.legend.offsetHeight) + newChart.draw() + + this.detectionCanvas.width = this.canvas.width + this.detectionCanvas.height = this.canvas.height + this.chart.drawDetectionMap(this.detectionCanvas.getContext("2d")) + }); + } + + drawChart(graphSettings, data) { + //objects = [] + this.chart = new Chart(this.canvas, data, graphSettings) + this.chart.updateLegend(graphSettings.displayLegend, this.legend) + + this.chart.resizeCanvas(this.parent, this.legend.offsetHeight) + + this.detectionCanvas.width = this.canvas.width + this.detectionCanvas.height = this.canvas.height + + //Choose the correct graph + switch (graphSettings.type) { + case "point": + this.chart = new PointChart(this.canvas, data, graphSettings) + break + case "line": + this.chart = new LineChart(this.canvas, data, graphSettings) + break + case "smoothline": + this.chart = new SmoothLineChart(this.canvas, data, graphSettings) + break + case "pie": + this.chart = new PieChart(this.canvas, data, graphSettings) + break + case "donut": + this.chart = new DonutChart(this.canvas, data, graphSettings) + break + case "bar": + this.chart = new BarChart(this.canvas, data, graphSettings) + break + case "area": + this.chart = new AreaChart(this.canvas, data, graphSettings) + break + case "smootharea": + this.chart = new SmoothAreaChart(this.canvas, data, graphSettings) + break + case "stacked": + this.chart = new StackedChart(this.canvas, data, graphSettings) + break + } + + this.chart.draw() + this.chart.drawDetectionMap(this.detectionCanvas.getContext("2d")) + + this.addListener(this.chart) + } +} diff --git a/public/scripts/charts/area_chart.js b/public/scripts/charts/area_chart.js index 1ccc45b..7ac1253 100644 --- a/public/scripts/charts/area_chart.js +++ b/public/scripts/charts/area_chart.js @@ -4,9 +4,7 @@ class AreaChart extends PointChart { } draw() { - if (this.smallest < 0) - this.bounds.xAxis = this.bounds.bottom - (this.bounds.height / (((this.largest <= 0) ? 0 : Math.abs(this.largest)) + Math.abs(this.smallest)) * Math.abs(this.smallest)) - + this.clear() this.drawAxis() this.data.forEach(categ => { @@ -18,10 +16,9 @@ class AreaChart extends PointChart { let xmax = 0 for (let i = 0; i < this.dataLen; i++) { if (categ.values[i] === null) continue - let scale = this.bounds.height - (this.largest >= 0 ? (this.bounds.bottom - this.bounds.xAxis) : 0) - let extreme = (this.largest <= 0) ? Math.abs(this.smallest) : Math.abs(this.largest) + let x = this.bounds.left + this.bounds.width / (this.dataLen - 1) * i - let y = (this.bounds.xAxis - categ.values[i] / extreme * scale) + let y = (this.bounds.xAxis - categ.values[i] / this.extreme * this.scale) xmax = x this.ctx.lineTo(x, y) diff --git a/public/scripts/charts/bar_chart.js b/public/scripts/charts/bar_chart.js index 065f22b..e6074f9 100644 --- a/public/scripts/charts/bar_chart.js +++ b/public/scripts/charts/bar_chart.js @@ -23,13 +23,10 @@ class BarChart extends Chart { let num = 0 this.data.forEach(categ => { - this.ctx.beginPath() let value = categ.values[i] let left = this.bounds.left + (size * (i + 0.15) + (innerSize * num / barCount)) - let scale = this.bounds.height - (this.largest >= 0 ? (this.bounds.bottom - this.bounds.xAxis) : 0) - let extreme = this.largest <= 0 ? Math.abs(this.smallest) : Math.abs(this.largest) - let bar_height = value / extreme * scale - let top = (this.bounds.xAxis - categ.values[i] / extreme * scale) + let bar_height = value / this.extreme * this.scale + let top = (this.bounds.xAxis - categ.values[i] / this.extreme * this.scale) //x value if (num === 0 && this.settings.displayAxisValues) { @@ -51,5 +48,8 @@ class BarChart extends Chart { this.objects.push(new_object) }) } + + if (this.settings.displayTitle) + this.drawTitle() } } diff --git a/public/scripts/charts/chart.js b/public/scripts/charts/chart.js index d44fc88..cff1665 100644 --- a/public/scripts/charts/chart.js +++ b/public/scripts/charts/chart.js @@ -1,15 +1,18 @@ class Shape { + static globalIndex = 0 + constructor(ctx, value, name, x, y) { this.ctx = ctx this.value = value this.name = name this.x = x this.y = y + this.index = Shape.globalIndex++ } checkHit() { return false } - draw() {} + draw() { } } class Rectangle extends Shape { @@ -25,7 +28,9 @@ class Rectangle extends Shape { } draw(ctx = this.ctx) { + ctx.beginPath() ctx.fillRect(this.x, this.y, this.w, this.h) + ctx.closePath() } } @@ -40,6 +45,7 @@ class Circle extends Shape { } draw(ctx = this.ctx) { + ctx.beginPath() ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI) ctx.fill() ctx.stroke() @@ -77,6 +83,22 @@ class PieSlice extends Circle { } } +class DonutSlice extends PieSlice { + constructor(ctx, value, name, x, y, r, sAngle, eAngle, r2) { + super(ctx, value, name, x, y, r, sAngle, eAngle) + this.r2 = r2 + } + + draw(ctx = this.ctx) { + ctx.beginPath() + ctx.moveTo(this.x, this.y) + ctx.arc(this.x, this.y, this.r, this.sAngle, this.eAngle, false) + ctx.arc(this.x, this.y, this.r2, this.eAngle, this.sAngle, true) + ctx.fill() + ctx.closePath() + } +} + function getLargest(data) { let largest = +data[0].values[0] @@ -107,24 +129,41 @@ class Chart { this.settings = settings this.canvas = canvas this.ctx = canvas.getContext("2d") - this.bounds = this.getBounds(canvas, settings.margin) this.largest = getLargest(data) this.smallest = getSmallest(data) this.dataLen = data[0].values.length + this.updateBounds() this.objects = [] } + updateBounds() { + this.bounds = this.getBounds(this.canvas, this.settings.margin) + + this.scale = this.bounds.height + - (this.largest >= 0 ? (this.bounds.bottom - this.bounds.xAxis) : 0) + this.extreme = this.largest <= 0 ? Math.abs(this.smallest) : Math.abs(this.largest) + } + getBounds(canvas, graphMargin) { - return { + let result = { top: graphMargin, bottom: canvas.height - graphMargin, left: graphMargin, right: canvas.width - graphMargin, height: canvas.height - 2 * graphMargin, width: canvas.width - 2 * graphMargin, - xAxis: canvas.height - graphMargin } + + if (this.smallest >= 0) + result.xAxis = result.bottom + else if (this.largest <= 0) + result.xAxis = result.top + else + result.xAxis = result.bottom + - result.height / ((Math.abs(this.largest)) + Math.abs(this.smallest)) * Math.abs(this.smallest) + + return result } checkHit(pos) { @@ -139,13 +178,13 @@ class Chart { let x = this.canvas.width / 2 let y = 25 - this.ctx.font = "30px Arial" + this.ctx.font = this.settings.titleFont.size + "px " + this.settings.titleFont.font this.ctx.fillStyle = "black" this.ctx.textAlign = "center" this.ctx.fillText(this.settings.title, x, y) } - updateLegend(displayLegend) { + updateLegend(displayLegend, legend) { if (displayLegend) { let legendHTML = "" this.data.forEach(categ => { @@ -160,13 +199,15 @@ class Chart { } resizeCanvas(parent, legendHeight = 0) { - if (legendHeight > 0) legendHeight += 3.1 //set size - this.canvas.style.width = parent.clientWidth.toString() - this.canvas.style.height = (parent.clientHeight - legendHeight).toString() this.canvas.width = parent.clientWidth this.canvas.height = parent.clientHeight - legendHeight + + this.updateBounds() + } + // Clear the canvas and remove all objects + clear() { //reset canvas color if (this.settings.backgroundColor == null) { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) @@ -174,10 +215,24 @@ class Chart { this.ctx.fillStyle = this.settings.backgroundColor this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) } + + Shape.globalIndex = 0 + this.objects = [] + } + + drawDetectionMap(ctx) { + this.objects.forEach(object => { + let color = "#" + object.index.toString(16).padStart(6, '0') + ctx.fillStyle = color + ctx.strokeStyle = color + ctx.lineWidth = 3 + + object.draw(ctx) + }) } drawAxis(displayAxisValues = this.settings.displayAxisValues) { - this.ctx.font = "16px Arial" + this.ctx.font = this.settings.labelFont.size + "px " + this.settings.labelFont.font if (this.settings.yStep <= 0 || !this.settings.yStep) this.settings.yStep = 1 this.ctx.beginPath() @@ -185,9 +240,8 @@ class Chart { this.ctx.strokeStyle = "#BBB" this.ctx.lineWidth = 1 - let scale = this.bounds.height - (this.largest >= 0 ? (this.bounds.bottom - this.bounds.xAxis) : 0) - let extreme = (this.largest <= 0) ? Math.abs(this.smallest) : Math.abs(this.largest) - let yPos = Math.round(this.bounds.xAxis - i * scale / extreme) + + let yPos = Math.round(this.bounds.xAxis - i * this.scale / this.extreme) //support line if (this.settings.displaySupportLines) { @@ -242,5 +296,8 @@ class Chart { this.ctx.fillText(this.settings.yLabel, -(this.bounds.left + this.bounds.height / 2), 18) this.ctx.restore() this.ctx.stroke() + + if (this.settings.displayTitle) + this.drawTitle() } } diff --git a/public/scripts/charts/donut_chart.js b/public/scripts/charts/donut_chart.js new file mode 100644 index 0000000..ea29361 --- /dev/null +++ b/public/scripts/charts/donut_chart.js @@ -0,0 +1,40 @@ +class DonutChart extends Chart { + constructor(canvas, data, settings) { + super(canvas, data, settings) + } + + draw() { + let index = 0 + let start_angle = 0 + let total_value = 0 + + this.data.forEach(categ => { + let val = categ.values[0] + if (val !== null) + total_value += val + }) + + this.clear() + + this.data.forEach(categ => { + let val = categ.values[0] + let slice_angle = 2 * Math.PI * val / total_value + + let x = this.canvas.width / 2 + let y = this.canvas.height / 2 + let r = Math.min(this.bounds.width / 2, this.bounds.height / 2) + let end_angle = start_angle + slice_angle + + this.ctx.fillStyle = categ.color + let new_slice = new DonutSlice(this.ctx, val + " (" + Math.round(val / total_value * 100) + "%)", categ.col_name, x, y, r, start_angle, end_angle, r / 2) + new_slice.draw() + this.objects.push(new_slice) + + start_angle = end_angle + index++ + }) + + if (this.settings.displayTitle) + this.drawTitle() + } +} diff --git a/public/scripts/charts/line_chart.js b/public/scripts/charts/line_chart.js index d7dfcc2..12f28e3 100644 --- a/public/scripts/charts/line_chart.js +++ b/public/scripts/charts/line_chart.js @@ -4,9 +4,7 @@ class LineChart extends PointChart { } draw() { - if (this.smallest < 0) - this.bounds.xAxis = this.bounds.bottom - (this.bounds.height / (((this.largest <= 0) ? 0 : Math.abs(this.largest)) + Math.abs(this.smallest)) * Math.abs(this.smallest)) - + this.clear() this.drawAxis() this.data.forEach(categ => { @@ -17,10 +15,8 @@ class LineChart extends PointChart { for (let i = 0; i < this.dataLen; i++) { if (categ.values[i] === null) continue - let scale = this.bounds.height - (this.largest >= 0 ? (this.bounds.bottom - this.bounds.xAxis) : 0) - let extreme = (this.largest <= 0) ? Math.abs(this.smallest) : Math.abs(this.largest) let x = this.bounds.left + this.bounds.width / (this.dataLen - 1) * i - let y = (this.bounds.xAxis - categ.values[i] / extreme * scale) + let y = (this.bounds.xAxis - categ.values[i] / this.extreme * this.scale) this.ctx.lineTo(x, y) } diff --git a/public/scripts/charts/pie_chart.js b/public/scripts/charts/pie_chart.js index 92bf7d2..ac2125a 100644 --- a/public/scripts/charts/pie_chart.js +++ b/public/scripts/charts/pie_chart.js @@ -13,6 +13,8 @@ class PieChart extends Chart { if (val !== null) total_value += val }) + + this.clear() this.data.forEach(categ => { let val = categ.values[0] @@ -31,5 +33,8 @@ class PieChart extends Chart { start_angle = end_angle index++ }) + + if (this.settings.displayTitle) + this.drawTitle() } } diff --git a/public/scripts/charts/point_chart.js b/public/scripts/charts/point_chart.js index 5f7547b..9b61f5b 100644 --- a/public/scripts/charts/point_chart.js +++ b/public/scripts/charts/point_chart.js @@ -5,14 +5,13 @@ class PointChart extends Chart { drawPoints(values, name, color) { this.ctx.fillStyle = color + this.ctx.strokeStyle = color for (let i = 0; i < this.dataLen; i++) { - this.ctx.beginPath() if (values[i] === null) continue - let scale = this.bounds.height - (this.largest >= 0 ? (this.bounds.bottom - this.bounds.xAxis) : 0) - let extreme = (this.largest <= 0) ? Math.abs(this.smallest) : Math.abs(this.largest) + let x = this.bounds.left + this.bounds.width / (this.dataLen - 1) * i - let y = (this.bounds.xAxis - values[i] / extreme * scale) + let y = this.bounds.xAxis - values[i] / this.extreme * this.scale let new_object = new Circle(this.ctx, values[i], name, x, y, this.settings.pointSize) new_object.draw() @@ -21,14 +20,10 @@ class PointChart extends Chart { } draw() { - if (this.smallest < 0) - this.bounds.xAxis = this.bounds.bottom - (this.bounds.height / ((this.largest <= 0 ? 0 : Math.abs(this.largest)) + Math.abs(this.smallest)) * Math.abs(this.smallest)) - + this.clear() this.drawAxis() - this.data.forEach(categ => { - if (this.settings.displayPoints) - this.drawPoints(categ.values, categ.col_name, categ.color) - }) + if (this.settings.displayPoints) + this.data.forEach(categ => {this.drawPoints(categ.values, categ.col_name, categ.color)}) } } diff --git a/public/scripts/charts/smooth_area_chart.js b/public/scripts/charts/smooth_area_chart.js new file mode 100644 index 0000000..79f0ff2 --- /dev/null +++ b/public/scripts/charts/smooth_area_chart.js @@ -0,0 +1,50 @@ +class SmoothAreaChart extends PointChart { + constructor(canvas, data, settings) { + super(canvas, data, settings) + } + + draw() { + this.clear() + this.drawAxis() + + this.data.forEach(categ => { + //Lines + this.ctx.beginPath() + this.ctx.lineJoin = "round" + this.ctx.strokeStyle = categ.color + + let x = this.bounds.left + let y = (this.bounds.xAxis - categ.values[0] / this.extreme * this.scale) + this.ctx.moveTo(x, y) + let xmax = 0 + + for (let i = 0; i < this.dataLen - 1; i++) { + let x1 = this.bounds.left + this.bounds.width / (this.dataLen - 1) * i + let y1 = (this.bounds.xAxis - categ.values[i] / this.extreme * this.scale) + + let x2 = this.bounds.left + this.bounds.width / (this.dataLen - 1) * (i + 1) + let y2 = (this.bounds.xAxis - categ.values[i + 1] / this.extreme * this.scale) + + let xm = (x1 + x2) / 2 + let xl = (x1 + xm) / 2 + let xr = (x2 + xm) / 2 + + this.ctx.bezierCurveTo(xl, y1, xr, y2, x2, y2); + xmax = x2 + } + + this.ctx.lineTo(xmax, this.bounds.xAxis) + this.ctx.lineTo(this.bounds.left, this.bounds.xAxis) + this.ctx.globalAlpha = 0.5 + this.ctx.fillStyle = categ.color + this.ctx.closePath() + this.ctx.fill() + + this.ctx.globalAlpha = 1 + }) + + // Points + if (this.settings.displayPoints) + this.data.forEach(categ => {this.drawPoints(categ.values, categ.col_name, categ.color)}) + } +} diff --git a/public/scripts/charts/smooth_line_chart.js b/public/scripts/charts/smooth_line_chart.js new file mode 100644 index 0000000..18fd0d6 --- /dev/null +++ b/public/scripts/charts/smooth_line_chart.js @@ -0,0 +1,40 @@ +class SmoothLineChart extends PointChart { + constructor(canvas, data, settings) { + super(canvas, data, settings) + } + + draw() { + this.clear() + this.drawAxis() + + this.data.forEach(categ => { + this.ctx.beginPath() + this.ctx.lineJoin = "round" + this.ctx.strokeStyle = categ.color + + let x = this.bounds.left + let y = (this.bounds.xAxis - categ.values[0] / this.extreme * this.scale) + this.ctx.moveTo(x, y) + + for (let i = 0; i < this.dataLen - 1; i++) { + let x1 = this.bounds.left + this.bounds.width / (this.dataLen - 1) * i + let y1 = (this.bounds.xAxis - categ.values[i] / this.extreme * this.scale) + + let x2 = this.bounds.left + this.bounds.width / (this.dataLen - 1) * (i + 1) + let y2 = (this.bounds.xAxis - categ.values[i + 1] / this.extreme * this.scale) + + let xm = (x1 + x2) / 2 + let xl = (x1 + xm) / 2 + let xr = (x2 + xm) / 2 + + this.ctx.bezierCurveTo(xl, y1, xr, y2, x2, y2); + } + this.ctx.stroke() + this.ctx.closePath() + }) + + // Points + if (this.settings.displayPoints) + this.data.forEach(categ => {this.drawPoints(categ.values, categ.col_name, categ.color)}) + } +} diff --git a/public/scripts/charts/stacked_chart.js b/public/scripts/charts/stacked_chart.js index fc77b71..8960820 100644 --- a/public/scripts/charts/stacked_chart.js +++ b/public/scripts/charts/stacked_chart.js @@ -18,9 +18,7 @@ class StackedChart extends Chart { largest = sum > largest ? sum : largest } - if (this.smallest < 0) - this.bounds.xAxis = this.bounds.bottom - (this.bounds.height / ((this.largest <= 0 ? 0 : Math.abs(this.largest)) + Math.abs(this.smallest)) * Math.abs(this.smallest)) - + this.clear() this.drawAxis(false) let size = this.bounds.width / this.dataLen @@ -30,8 +28,6 @@ class StackedChart extends Chart { let last_top = this.bounds.xAxis let num = 0 this.data.forEach(categ => { - this.ctx.beginPath() - let value = categ.values[i] let bar_height = value / largest * this.bounds.height let left = this.bounds.left + size * (i + 0.15) diff --git a/public/styles/chart_style.css b/public/styles/chart_style.css new file mode 100644 index 0000000..ab136ac --- /dev/null +++ b/public/styles/chart_style.css @@ -0,0 +1,63 @@ +:root { + --legend-height: 30px +} + +body { + height: 100%; + margin: 0; + display: grid; +} + +#chartCanvas, +#detectionCanvas { + grid-column: 1; + grid-row: 1; + width: 100%; + height: 100%; +} + +html { + height: 100%; +} + +#graphLegend { + grid-column: 1; + grid-row: 2; + display: none; + justify-content: center; + align-items: center; + text-align: center; +} + +#graphLegend div { + display: inline; + margin: 0 10px 0 10px; + width: 50px; +} + +#dataDiv { + display: none; + position: absolute; + height: 35px; + width: 70px; + background-color: white; + border: 1px solid lightgrey; + padding: 5px; + border-radius: 5px; +} + +#dataDiv p { + margin: 0; + font-size: 13px; +} + +#dataDiv b { + position: center; + font-size: 13px; +} + +#detectionCanvas { + opacity: 0; + image-rendering: pixelated; + image-rendering: crisp-edges; +} \ No newline at end of file diff --git a/public/styles/edit_chart.css b/public/styles/edit_chart.css index 583aec0..c8359ff 100755 --- a/public/styles/edit_chart.css +++ b/public/styles/edit_chart.css @@ -19,10 +19,14 @@ main { flex-basis: 25%; } +.submenuLabel + div { + display: none; +} + div[id^="chart_metadata_group"] { padding: 0.5em; - margin: 0.5em; - background-color: aquamarine; + margin-bottom: 0.5em; + background-color: var(--side); } #tableDiv { diff --git a/public/styles/style.css b/public/styles/style.css index 8f99499..9233766 100755 --- a/public/styles/style.css +++ b/public/styles/style.css @@ -1,48 +1,7 @@ -#graphDiv { - height: 400px; -} - -body { - height: 100%; - margin: 0; -} - -html { - height: 100%; -} - -#dataDiv { - display: none; - position: absolute; - height: 35px; - width: 70px; - background-color: white; - border: 1px solid lightgrey; - padding: 5px; - border-radius: 5px; -} - -#dataDiv p { - margin: 0; - font-size: 13px; -} - -#dataDiv b{ - position: center; - font-size: 13px; -} -#graphLegend { - display: none; - justify-content: center; - align-items: center; - text-align: center; -} -#graphLegend div { - display: inline; - margin: 0 10px 0 10px; - width: 50px; +#graphDiv { + height: 400px; } #settings_div{ diff --git a/src/Form/Type/MetadataType.php b/src/Form/Type/MetadataType.php index cc01ea5..44139e8 100644 --- a/src/Form/Type/MetadataType.php +++ b/src/Form/Type/MetadataType.php @@ -23,18 +23,26 @@ class MetadataType extends AbstractType 'choices' => [ 'Point' => 'point', 'Line' => 'line', + 'Smooth line' => 'smoothline', 'Area' => 'area', + 'Smooth Area' => 'smootharea', 'Pie' => 'pie', + 'Donut' => 'donut', 'Bar' => 'bar', 'Stacked' => 'stacked', ], 'label' => 'Chart type', ]) + ->add('margin', NumberType::class, [ + 'label' => 'Margin', + 'required' => false, + ]) // Title settings ->add( $builder->create('group1', FormType::class, [ 'inherit_data' => true, - 'label' => 'Title settings' + 'label' => 'Title settings', + 'label_attr' => ['class' => 'submenuLabel'] ]) ->add('title', TextType::class, [ 'label' => 'Title', @@ -45,18 +53,16 @@ class MetadataType extends AbstractType 'required' => false, ]) ->add('titleFont', FontType::class, [ - 'label' => 'Title font' + 'label' => 'Title font', + 'required' => false, ]) ) - ->add('margin', NumberType::class, [ - 'label' => 'Margin', - 'required' => false, - ]) // Axis label settings ->add( $builder->create('group2', FormType::class, [ 'inherit_data' => true, - 'label' => 'Labels' + 'label' => 'Labels', + 'label_attr' => ['class' => 'submenuLabel'] ]) ->add('xLabel', TextType::class, [ 'label' => 'X label', @@ -78,16 +84,33 @@ class MetadataType extends AbstractType 'label' => 'Display support lines', 'required' => false, ]) + ->add('labelFont', FontType::class, [ + 'label' => 'Label font', + 'required' => false, + ]) ) - ->add('displayLegend', CheckboxType::class, [ - 'label' => 'Display legend', - 'required' => false, - ]) - // Point settings + // Legend settings ->add( $builder->create('group3', FormType::class, [ 'inherit_data' => true, - 'label' => 'Point Settings' + 'label' => 'Legend settings', + 'label_attr' => ['class' => 'submenuLabel'] + ]) + ->add('displayLegend', CheckboxType::class, [ + 'label' => 'Display legend', + 'required' => false, + ]) + ->add('legendFont', FontType::class, [ + 'label' => 'Legend font', + 'required' => false, + ]) + ) + // Point settings + ->add( + $builder->create('group4', FormType::class, [ + 'inherit_data' => true, + 'label' => 'Point Settings', + 'label_attr' => ['class' => 'submenuLabel'] ]) ->add('displayPoints', CheckboxType::class, [ 'label' => 'Display points', diff --git a/templates/chart.html.twig b/templates/chart.html.twig index d208bb2..5638ec5 100644 --- a/templates/chart.html.twig +++ b/templates/chart.html.twig @@ -6,7 +6,7 @@ {% block stylesheets %} {{ parent() }} - + {% endblock %} {% block javascripts %} @@ -14,126 +14,33 @@ + + + -{% endblock %} -{% block body %} -