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 += "