Hit Detection Mask

main
František Špaček 2 years ago
parent a4c23e116d
commit 3f59a07483

@ -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 += "<div><span style='display:inline-block;width:20px;background-color:" + categ.color + ";'>&nbsp;</span> " + categ.col_name + "</div>";
});
legend.innerHTML = legendHTML;
legend.style.display = "block";
} else {
legend.style.display = "none";
}
}
function drawChart(graphSettings, data) {
updateLegend(graphSettings.display_legend, data);
resizeCanvas(canvas, parent, legend.offsetHeight, graphSettings.backgroundColor);
objects = [];
//Choose the correct graph
switch (graphSettings.type) {
case "point":
drawPointChart(canvas, data, graphSettings);
break;
case "line":
drawLineChart(canvas, data, graphSettings);
break;
case "pie":
drawPieChart(canvas, data, graphSettings);
break;
case "bar":
drawBarChart(canvas, data, graphSettings);
break;
case "area":
drawAreaChart(canvas, data, graphSettings);
break;
case "stacked":
stackedChart(canvas, data, graphSettings);
break;
}
if (graphSettings.display_title)
drawTitle(canvas, graphSettings);
}

@ -0,0 +1,128 @@
class ChartLoader {
constructor(canvas, detectionCanvas, parent, legend, dataDiv) {
this.canvas = canvas
this.detectionCanvas = detectionCanvas
this.parent = parent
this.legend = legend
this.dataDiv = dataDiv
}
//Načtení dat
loadData(code) {
fetch("/api/charts/" + code, {
method: 'GET',
headers: {
'Content-Type': 'json'
}
})
.then(response => response.json())
.then(data => {
let metadata = data.metadata;
metadata.custom_x_values = ""
let table = data.table
if (data !== null) {
this.drawChart(metadata, table)
this.parent.style.backgroundColor = metadata.backgroundColor
}
else
this.parent.innerHTML = "graph not found."
})
}
addListener(newChart) {
//Click
document.addEventListener('mousemove', (e) => {
const pos = {
x: e.clientX,
y: e.clientY
}
var ctx = this.detectionCanvas.getContext('2d');
var pixelData = ctx.getImageData(pos.x, pos.y, 1, 1).data;
if (pixelData[3] === 255) {
let index = (pixelData[0] * 256 + pixelData[1]) * 256 + pixelData[2]
let obj = newChart.objects[index]
let divHitBox = this.dataDiv
divHitBox.style.display = "block"
// make the info fit into the chart div
if (pos.x + divHitBox.clientWidth <= this.parent.clientWidth - 2)
divHitBox.style.left = pos.x + "px"
else
this.dataDiv.style.left = pos.x - divHitBox.clientWidth + "px"
if (pos.y + divHitBox.clientHeight <= parent.clientHeight - 2)
this.dataDiv.style.top = pos.y + "px"
else
this.dataDiv.style.top = pos.y - divHitBox.clientHeight + "px"
this.dataDiv.style.display = "block"
this.dataDiv.innerHTML = "<b>" + obj.name + "</b><br><p>" + obj.value + "</p>"
}
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)
}
}

@ -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)

@ -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()
}
}

@ -1,10 +1,13 @@
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 }
@ -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()
}
}

@ -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()
}
}

@ -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)
}

@ -14,6 +14,8 @@ class PieChart extends Chart {
total_value += val
})
this.clear()
this.data.forEach(categ => {
let val = categ.values[0]
let slice_angle = 2 * Math.PI * val / total_value
@ -31,5 +33,8 @@ class PieChart extends Chart {
start_angle = end_angle
index++
})
if (this.settings.displayTitle)
this.drawTitle()
}
}

@ -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)
})
this.data.forEach(categ => {this.drawPoints(categ.values, categ.col_name, categ.color)})
}
}

@ -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)})
}
}

@ -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)})
}
}

@ -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)

@ -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;
}

@ -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 {

@ -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{

@ -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'
])
)
->add('margin', NumberType::class, [
'label' => 'Margin',
'label' => 'Title font',
'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,
])
)
// Legend settings
->add(
$builder->create('group3', FormType::class, [
'inherit_data' => true,
'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('group3', FormType::class, [
$builder->create('group4', FormType::class, [
'inherit_data' => true,
'label' => 'Point Settings'
'label' => 'Point Settings',
'label_attr' => ['class' => 'submenuLabel']
])
->add('displayPoints', CheckboxType::class, [
'label' => 'Display points',

@ -6,7 +6,7 @@
{% block stylesheets %}
{{ parent() }}
<link href="{{ asset('styles/style.css') }}" rel="stylesheet"/>
<link href="{{ asset('styles/chart_style.css') }}" rel="stylesheet"/>
{% endblock %}
{% block javascripts %}
@ -14,126 +14,33 @@
<script type="text/javascript" src={{ asset('/scripts/charts/chart.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/point_chart.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/area_chart.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/smooth_area_chart.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/bar_chart.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/line_chart.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/smooth_line_chart.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/pie_chart.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/donut_chart.js') }}></script>
<script type="text/javascript" src={{ asset('/scripts/charts/stacked_chart.js') }}></script>
{% endblock %}
{% block body %}
<div id="graphDiv">
<canvas id="graphCanvas"></canvas>
<div id="graphLegend"></div>
<div id="dataDiv"></div>
</div>
<script type="text/javascript" src={{ asset('/scripts/chart_loader.js') }}></script>
<script>
let canvas = document.getElementById("graphCanvas")
let parent = document.getElementById("graphDiv")
document.addEventListener("DOMContentLoaded", function(event){
let canvas = document.getElementById("chartCanvas")
let detectionCanvas = document.getElementById("detectionCanvas")
let parent = document.body
let legend = document.getElementById("graphLegend")
let dataDiv = document.getElementById("dataDiv")
const urlParams = new URLSearchParams(window.location.search)
const code = urlParams.get('code')
let data = []
let graphSettings = []
//Načtení dat
function load_data() {
fetch("/api/charts/" + {{ code }}, {
method: 'GET',
headers: {
'Content-Type': 'json'
}
})
.then(response => response.json())
.then(data => {
let metadata = data.metadata;
metadata.custom_x_values = ""
let table = data.table
if (data !== null) {
drawChart(metadata, table)
}
else
parent.innerHTML = "graph not found."
let chartLoader = new ChartLoader(canvas, detectionCanvas, parent, legend, dataDiv)
chartLoader.loadData({{ code }})
})
}
load_data()
function addListener(newChart) {
//Click
document.addEventListener('mousemove', (e) => {
const pos = {
x: e.clientX,
y: e.clientY
}
let obj = newChart.checkHit(pos)
//show point value
if (obj !== null){
let divHitBox = dataDiv
divHitBox.style.display = "block"
if (pos.x + divHitBox.clientWidth <= parent.clientWidth - 2)
divHitBox.style.left = pos.x + "px"
else
dataDiv.style.left = pos.x - divHitBox.clientWidth + "px"
if (pos.y + divHitBox.clientHeight <= parent.clientHeight - 2)
dataDiv.style.top = pos.y + "px"
else
dataDiv.style.top = pos.y - divHitBox.clientHeight + "px"
dataDiv.style.display = "block"
dataDiv.innerHTML = "<b>" + obj.name + "</b><br><p>" + obj.value + "</p>"
}
else {
dataDiv.style.display = "none"
}
})
}
//Resize
/*$(window).on('resize', function(){
drawChart(graphSettings, data);
});*/
function drawChart(graphSettings, data) {
//objects = []
let newChart = new Chart(canvas, data, graphSettings)
newChart.updateLegend(graphSettings.displayLegend, data)
newChart.resizeCanvas(parent, legend.offsetHeight)
//Choose the correct graph
switch (graphSettings.type) {
case "point":
newChart = new PointChart(canvas, data, graphSettings)
break
case "line":
newChart = new LineChart(canvas, data, graphSettings)
break
case "pie":
newChart = new PieChart(canvas, data, graphSettings)
break
case "bar":
newChart = new BarChart(canvas, data, graphSettings)
break
case "area":
newChart = new AreaChart(canvas, data, graphSettings)
break
case "stacked":
newChart = new StackedChart(canvas, data, graphSettings)
break
}
newChart.draw()
if (graphSettings.displayTitle)
newChart.drawTitle(canvas, graphSettings)
addListener(newChart)
}
</script>
{% endblock %}
{% block body %}
<canvas id="chartCanvas"></canvas>
<canvas id="detectionCanvas"></canvas>
<div id="graphLegend"></div>
<div id="dataDiv"></div>
{% endblock %}

@ -19,11 +19,6 @@
<main>
{{ form_start(form) }}
<div id="mainDiv">
<!--<div id="graphDiv">
<canvas id="graphCanvas"></canvas>
<div id="graphLegend"></div>
<div id="dataDiv"></div>
</div>-->
<object id="graphDiv" data={{ "https://spacek.blue/charts/" ~ field_value(form.code) }}></object>
<div id="settings_div">
{{ form_row(form.name) }}
@ -94,6 +89,19 @@
e.preventDefault()
e.stopPropagation()
})
let labels = [...document.getElementsByClassName("submenuLabel")]
labels.forEach(label => {
label.addEventListener('click', e => {
let submenuDiv = e.target.nextElementSibling
if (getComputedStyle(submenuDiv).display == "block")
submenuDiv.style.display = "none"
else
submenuDiv.style.display = "block"
})
})
</script>
</main>
{% endblock %}

@ -17,6 +17,7 @@ return array(
'App\\Form\\Type\\CellType' => $baseDir . '/src/Form/Type/CellType.php',
'App\\Form\\Type\\ChartType' => $baseDir . '/src/Form/Type/ChartType.php',
'App\\Form\\Type\\ColumnType' => $baseDir . '/src/Form/Type/ColumnType.php',
'App\\Form\\Type\\FontType' => $baseDir . '/src/Form/Type/FontType.php',
'App\\Form\\Type\\MetadataType' => $baseDir . '/src/Form/Type/MetadataType.php',
'App\\Form\\Type\\UserType' => $baseDir . '/src/Form/Type/UserType.php',
'App\\Kernel' => $baseDir . '/src/Kernel.php',

@ -648,6 +648,7 @@ class ComposerStaticInit51f3c6df917af85305ff4843868663eb
'App\\Form\\Type\\CellType' => __DIR__ . '/../..' . '/src/Form/Type/CellType.php',
'App\\Form\\Type\\ChartType' => __DIR__ . '/../..' . '/src/Form/Type/ChartType.php',
'App\\Form\\Type\\ColumnType' => __DIR__ . '/../..' . '/src/Form/Type/ColumnType.php',
'App\\Form\\Type\\FontType' => __DIR__ . '/../..' . '/src/Form/Type/FontType.php',
'App\\Form\\Type\\MetadataType' => __DIR__ . '/../..' . '/src/Form/Type/MetadataType.php',
'App\\Form\\Type\\UserType' => __DIR__ . '/../..' . '/src/Form/Type/UserType.php',
'App\\Kernel' => __DIR__ . '/../..' . '/src/Kernel.php',

Loading…
Cancel
Save

Powered by TurnKey Linux.