You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
528 lines
17 KiB
528 lines
17 KiB
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 + ";'> </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);
|
|
} |