How to rotate 180 text lables on canvas - javascript

Hey I have a canvas element which I am rotate 180 degree using the following code:
-moz-transform: scale(-1, 1);
-webkit-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
filter: FlipH;
The canvas is get rotated but the text is flipped out.
Here is the the result:
I want to flip the text, after I have rotate the canvas 180 degree,apparently the text get rotate 180 too and I want to reverse it.
I want to rotate only the text and not all the other elements.
The original code is here:
go to ticklines function
http://codepen.io/Barak/pen/PNXROK
$(document).ready(function() {
$(function() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.transform(1, 0, 0, -1, 0, canvas.height);
// https://www.researchgate.net/publication/4345236_A_Software_Implementation_of_the_Duval_Triangle_Method
var v0 = {
x: 50 * 10,
y: (86.603 * 10)
};
var v1 = {
x: 0 * 10,
y: (0 * 10)
};
var v2 = {
x: 100 * 10,
y: (0 * 10)
};
var triangle = [v0, v1, v2];
// Define all your segments here
var segments = [{
points: [{
x: 0 * 10,
y: 0 * 10
}, {
x: 43.5 * 10,
y: 75.34461 * 10
}, {
x: 55 * 10,
y: 55.42 * 10
}, {
x: 23 * 10,
y: 0 * 10
}],
fill: 'rgb(172,236,222)',
label: {
text: '',
cx: 300,
cy: 645,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 23 * 10,
y: 0 * 10
}, {
x: 55 * 10,
y: 55.42 * 10
}, {
x: 63.5 * 10,
y: 40.70 * 10
}, {
x: 55.5 * 10,
y: 26.84 * 10
}, {
x: 71 * 10,
y: 0 * 10
}],
fill: 'deepskyblue',
label: {
text: '',
cx: 490,
cy: 645,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 71 * 10,
y: 0 * 10
}, {
x: 55.5 * 10,
y: 26.84693 * 10
}, {
x: 63.5 * 10,
y: 40.70341 * 10
}, {
x: 43.5 * 10,
y: 75.34461 * 10
}, {
x: 48 * 10,
y: 83.13888 * 10
}, {
x: 73 * 10,
y: 39.83737 * 10
}, {
x: 67.5 * 10,
y: 30.311 * 10
}, {
x: 85 * 10,
y: 0 * 10
}],
fill: 'lightCyan',
label: {
text: '',
cx: 656,
cy: 645,
withLine: false,
endX: 366,
endY: 120
},
}, { //here - I am in hell.-s5
points: [{
x: 49 * 10,
y: 84.87093999999999 * 10
}, {
x: 50 * 10,
y: 86.603 * 10
}, {
x: 51 * 10,
y: 84.87093999999999 * 10
}],
fill: 'black',
label: {
text: 'PD',
cx: 600,
cy: 52,
withLine: true,
endX: 520,
endY: 70
},
}, {
points: [{
x: 48 * 10,
y: 83.1388 * 10
}, {
x: 49 * 10,
y: 84.87093 * 10
}, {
x: 51 * 10,
y: 84.87093 * 10
}, {
x: 60 * 10,
y: 69.2824 * 10
}, {
x: 58 * 10,
y: 65.81828 * 10
}],
fill: 'navajoWhite',
label: {
text: '',
cx: 670,
cy: 140,
withLine: true,
endX: 574,
endY: 105
},
}, {
points: [{
x: 58 * 10,
y: 65.81828 * 10
}, {
x: 60 * 10,
y: 69.2824 * 10
}, {
x: 75 * 10,
y: 43.3015 * 10
}, {
x: 73 * 10,
y: 39.837 * 10
}],
fill: 'tan',
label: {
text: '',
cx: 800,
cy: 290,
withLine: true,
endX: 662,
endY: 120
},
}, {
points: [{
x: 85 * 10,
y: 0 * 10
}, {
x: 67.5 * 10,
y: 30.311 * 10
}, {
x: 75 * 10,
y: 43.3015 * 10
}, {
x: 100 * 10,
y: 0
}],
fill: 'peru',
label: {
text: '',
cx: 800,
cy: 645,
withLine: false,
endX: null,
endY: null
},
}, ];
// label styles
var labelfontsize = 12;
var labelfontface = 'verdana';
var labelpadding = 3;
// pre-create a canvas-image of the arrowhead
var arrowheadLength = 10;
var arrowheadWidth = 8;
var arrowhead = document.createElement('canvas');
premakeArrowhead();
var legendTexts = ['PD = Partial Discharge',
'DT = Discharges and Thermal',
'T1 = Thermal fault T < 300 ℃',
'T2 = Thermal fault 300 ℃ < T < 700 ℃',
'T3 = Thermal fault T > 700 ℃',
'D1 = Discharges of low energy',
'D2 = Discharges of high energy'
];
// start drawing
/////////////////////
// draw colored segments inside triangle
for (var i = 0; i < segments.length; i++) {
drawSegment(segments[i]);
}
// draw ticklines
//
ticklines(v0, v1, 9, Math.PI * 1.2, 20);
ticklines(v1, v2, 9, Math.PI * 3 / 4, 20);
ticklines(v2, v0, 9, Math.PI * 2, 20);
// molecules
// moleculeLabel(v0, v1, 100, Math.PI / 2, '% CH4');
// moleculeLabel(v1, v2, 100, 0, '% C2H4');
// moleculeLabel(v2, v0, 100, Math.PI, '% C2H2');
// draw outer triangle
drawTriangle(triangle);
// draw legend
//drawLegend(legendTexts, 10, 10, 12.86);
drawCircle(canvas.width / 3, canvas.height / 2, 2.5, 'red');
// end drawing
/////////////////////
function drawCircle(point1, point2, radius, color) {
ctx.beginPath();
ctx.arc(point1, point2, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = color;
ctx.fill();
}
function drawSegment(s) {
// draw and fill the segment path
ctx.beginPath();
ctx.moveTo(s.points[0].x, s.points[0].y);
for (var i = 1; i < s.points.length; i++) {
ctx.lineTo(s.points[i].x, s.points[i].y);
}
ctx.closePath();
ctx.fillStyle = s.fill;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
// draw segment's box label
/* if (s.label.withLine) {
lineBoxedLabel(s, labelfontsize, labelfontface, labelpadding);
} else {
boxedLabel(s, labelfontsize, labelfontface, labelpadding);
} */
}
function moleculeLabel(start, end, offsetLength, angle, text) {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = '14px verdana';
var dx = end.x - start.x;
var dy = end.y - start.y;
var x0 = parseInt(start.x + dx * 0.50);
var y0 = parseInt(start.y + dy * 0.50);
var x1 = parseInt(x0 + offsetLength * Math.cos(angle));
var y1 = parseInt(y0 + offsetLength * Math.sin(angle));
ctx.fillStyle = 'black';
ctx.fillText(text, x1, y1);
// arrow
var x0 = parseInt(start.x + dx * 0.35);
var y0 = parseInt(start.y + dy * 0.35);
var x1 = parseInt(x0 + 50 * Math.cos(angle));
var y1 = parseInt(y0 + 50 * Math.sin(angle));
var x2 = parseInt(start.x + dx * 0.65);
var y2 = parseInt(start.y + dy * 0.65);
var x3 = parseInt(x2 + 50 * Math.cos(angle));
var y3 = parseInt(y2 + 50 * Math.sin(angle));
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x3, y3);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
var angle = Math.atan2(dy, dx);
ctx.save(); // save
ctx.translate(x3, y3);
ctx.rotate(angle);
ctx.drawImage(arrowhead, -arrowheadLength, -arrowheadWidth / 2);
ctx.restore()
}
function boxedLabel(s, fontsize, fontface, padding) {
var centerX = s.label.cx;
var centerY = s.label.cy;
var text = s.label.text;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = fontsize + 'px ' + fontface
var textwidth = ctx.measureText(text).width;
var textheight = fontsize * 1.286;
var leftX = centerX - textwidth / 2 - padding;
var topY = centerY - textheight / 2 - padding;
ctx.fillStyle = 'white';
ctx.fillRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.lineWidth = 1;
ctx.strokeRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.fillStyle = 'black';
ctx.fillText(text, centerX, centerY);
}
function lineBoxedLabel(s, fontsize, fontface, padding) {
var centerX = s.label.cx;
var centerY = s.label.cy;
var text = s.label.text;
var lineToX = s.label.endX;
var lineToY = s.label.endY;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = fontsize + 'px ' + fontface
var textwidth = ctx.measureText(text).width;
var textheight = fontsize * 1.286;
var leftX = centerX - textwidth / 2 - padding;
var topY = centerY - textheight / 2 - padding;
// the line
ctx.beginPath();
ctx.moveTo(leftX, topY + textheight / 2);
ctx.lineTo(lineToX, topY + textheight / 2);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
// the boxed text
ctx.fillStyle = 'white';
ctx.fillRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.strokeRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.fillStyle = 'black';
ctx.fillText(text, centerX, centerY);
}
function ticklines(start, end, count, angle, length) {
var dx = end.x - start.x;
var dy = end.y - start.y;
ctx.lineWidth = 1;
for (var i = 1; i < count; i++) {
var x0 = parseInt(start.x + dx * i / count);
var y0 = parseInt(start.y + dy * i / count);
var x1 = parseInt(x0 + length * Math.cos(180 - angle));
var y1 = parseInt(y0 + length * Math.sin(180 - angle));
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
if (i == 2 || i == 4 || i == 6 || i == 8) {
var labelOffset = length * 3 / 4;
var x1 = parseInt(x0 - labelOffset * Math.cos(180 - angle));
var y1 = parseInt(y0 - labelOffset * Math.sin(180 - angle));
ctx.save();
ctx.fillStyle = 'black';
ctx.rotate(Math.PI);
ctx.fillText(parseInt(i * 10), x1, y1);
ctx.restore();
}
}
}
function premakeArrowhead() {
var actx = arrowhead.getContext('2d');
arrowhead.width = arrowheadLength;
arrowhead.height = arrowheadWidth;
actx.beginPath();
actx.moveTo(0, 0);
actx.lineTo(arrowheadLength, arrowheadWidth / 2);
actx.lineTo(0, arrowheadWidth);
actx.closePath();
actx.fillStyle = 'black';
actx.fill();
}
function drawTriangle(t) {
ctx.beginPath();
ctx.moveTo(t[0].x, t[0].y);
ctx.lineTo(t[1].x, t[1].y);
ctx.lineTo(t[2].x, t[2].y);
ctx.closePath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
}
function drawLegend(texts, x, y, lineheight) {
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillStyle = 'black';
ctx.font = '12px arial';
for (var i = 0; i < texts.length; i++) {
ctx.fillText(texts[i], x, y + i * lineheight);
}
}
})
});
body {
background-color: ivory;
padding: 10px;
}
#canvas {
border: 1px solid red;
margin: 0 auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
<canvas id="canvas" width=1024 height=1024></canvas>
</div>

Related

How to find X,Y coordinates on Half Doughnut Chart JS react

I've got the doughnut part of the chart complete and the gauge needle. I want to add this circular pointer on the doughnut instead of the needle. I was able to draw the circular pointer but couldn't find the right X,Y coordinates to place the pointer.
Here is the DEMO
Here in the below image, the circle should be placed at the gauge needle pointer
The code I've used is the following for the circular pointer.
const pointer = {
id: "pointer",
afterDatasetsDraw: (chart) => {
const { ctx } = chart;
var data = chart._metasets[0].data[0];
var radius = data.innerRadius + (data.outerRadius - data.innerRadius) / 2;
var centerX = data.x;
var centerY = data.y;
const angle = (180 / 1000) * speed;
// this thing needs to be fixed
var x = centerX + radius * Math.cos(angle * Math.PI);
var y = centerY + radius * Math.sin(angle * Math.PI);
ctx.save();
ctx.beginPath();
ctx.lineWidth = 6;
ctx.arc(x, y, 12, 0, 2 * Math.PI);
ctx.stroke();
ctx.restore();
}
};
Target to achive:
Basically you want 75% of 180 Degrees (because the speed = 75):
const angle = Math.PI * ( speed / 100) + Math.PI;
And than Math.cos and Math.sin expect a radiant value(link to mdn documentation), which you already have, so no multiplication with Math.PI is needed anymore.
var x = centerX + radius * Math.cos( angle );
var y = centerY + radius * Math.sin( angle );
Full working demo (Updated Example, now with animation):
const speed = 75;
let animationAngle = 0;
var pointer = {
id: 'pointer',
defaults:{
percentage: 0,
maxAngle: 0
},
afterDraw: function(chart, args, opt) {
const { ctx } = chart;
var data = chart._metasets[0].data[0];
var radius = data.innerRadius + (data.outerRadius - data.innerRadius) / 2;
var centerX = data.x;
var centerY = data.y;
const angle = (Math.PI * ( speed / 100) * chart.options.plugins.pointer.percentage) + Math.PI;
var x = centerX + radius * Math.cos( angle );
var y = centerY + radius * Math.sin( angle );
ctx.save();
ctx.beginPath();
ctx.lineWidth = 6;
ctx.arc(x, y, 12, 0, 2 * Math.PI);
ctx.stroke();
ctx.restore();
},
}
var options = {
type: 'doughnut',
data: {
datasets: [{
data: [20, 50, 30],
backgroundColor: [
'rgba(231, 76, 60, 1)',
'rgba(255, 164, 46, 1)',
'rgba(46, 204, 113, 1)'
],
borderColor: [
'rgba(255, 255, 255 ,1)',
'rgba(255, 255, 255 ,1)',
'rgba(255, 255, 255 ,1)'
],
borderWidth: 0
}]},
options: {
cutout: 80,
rotation: -90,
circumference: 180,
animation:{
onProgress: function(context){
if(context.initial){
this.options.plugins.pointer.percentage = context.currentStep / context.numSteps;
}
}
},
maintainAspectRatio: false,
legend: { display: false },
plugins:{
tooltip: { enabled: false },
pointer: {currentAngle: 1}
}
},
plugins:[pointer]
}
const chart = document.getElementById('chart1')
new Chart(chart, options);
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<div style="width:500px;height:184px">
<canvas id="chart1" width="500" height="184"></canvas>
<div>

Chart.js color is not updating dynamically

I am working with a custom gauge using chart.js doughnut type and it's working fine. Now I am trying to change the color of each elements of gauge when the theme button is triggered. So, I have created a update function to do the task. But the plugins are updating but inversely. Also, don't know how to get the needleColor plugin path location for changing the needle color dynamically.
Images are linked below:
lightmood-without-trigger
darkmood-without-trigger
lightmood-after-trigger-theme-button-from-night-to-day
darkmood-after-trigger-theme-button-from-day-to-night
HTML
<div class="gauge_container">
<canvas class="gauge" id="gauge"> </canvas>
</div>
<i onclick="changeColor();" class="uil uil-moon change-theme" id="theme-button"
><span class="theme-name">Dark</span>
</i>
CSS
:root {
--hue-color: 5;
--gauge-color: hsl(var(--hue-color), 69%, 61%);
--gauge-color-border: #fff;
}
body.dark-theme {
--gauge-color: #fff;
--gauge-color-border: hsl(var(--hue-color), 30%, 8%);
}
.gauge_container {
width: 250px;
height: 200px;
margin-left: auto;
margin-right: auto;
}
JavaScript
const gaugeStyle = getComputedStyle(document.body);
var gaugeColor = gaugeStyle.getPropertyValue("--gauge-color");
var gaugeColorBorder = gaugeStyle.getPropertyValue("--gauge-color-border");
function changeColor() {
var gaugeColor = gaugeStyle.getPropertyValue("--gauge-color");
var gaugeColorBorder = gaugeStyle.getPropertyValue("--gauge-color-border");
myGauge.options.plugins.tooltip.backgroundColor = gaugeColor;
myGauge.options.plugins.tooltip.bodyColor = gaugeColorBorder;
myGauge.data.datasets[0].borderColor = gaugeColorBorder;
// Don't know how to get the needle color location path
myGauge.update();
}
const gaugeData = {
labels: ["Safe", "Risky", "High Risk"],
datasets: [
{
label: "Gauge",
data: [100, 65, 35],
backgroundColor: [
"rgba(75, 192, 192, 0.8)",
"rgba(255, 206, 86, 0.8)",
"rgba(255, 26, 104, 0.8)",
],
needleValue: 50,
borderColor: gaugeColorBorder,
borderWidth: 1,
cutout: "95%",
circumference: 180,
rotation: 270,
borderRadius: 5,
},
],
};
//gaugeNeedle
const gaugeNeedle = {
id: "gaugeNeedle",
afterDatasetDraw(chart) {
const {
ctx,
data,
chartArea: { width, height },
} = chart;
ctx.save();
const needleValue = data.datasets[0].needleValue;
if (needleValue <= 100) {
var angle = Math.PI + (1 / 200) * needleValue * Math.PI;
} else if (needleValue <= 10000) {
var angle =
Math.PI +
(1 / 200) * 100 * Math.PI +
((1 / 200) * needleValue * Math.PI * 65) / 10000;
} else if (needleValue <= 1000000) {
var angle =
Math.PI +
(1 / 200) * 100 * Math.PI +
((1 / 200) * 10000 * Math.PI * 65) / 10000 +
((1 / 200) * needleValue * Math.PI * 35) / 1000000;
} else {
var angle = 0;
}
const cx = width / 2;
const cy = chart._metasets[0].data[0].y;
//needle
ctx.translate(cx, cy);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(0, -2);
ctx.lineTo(height - ctx.canvas.offsetTop - 130, 0);
ctx.lineTo(0, 2);
ctx.fillStyle = gaugeColor; // Don't how to get this in changeColor function
ctx.fill();
//needle dot
ctx.translate(-cx, -cy);
ctx.beginPath();
ctx.arc(cx, cy, 5, 0, 10);
ctx.fill();
ctx.restore();
//text
ctx.font = "20px Ubuntu";
ctx.fillStyle = gaugeColor;
ctx.fillText(needleValue + " CPM", cx, cy + 50);
ctx.font = "10px Ubuntu";
ctx.fillText(0, 3, cy + 20);
ctx.fillText(100, cx, 50);
ctx.fillText("10k", cx + 115, 115);
ctx.fillText("1M", cx + 118, 205);
ctx.textAlign = "center";
ctx.restore();
},
};
// config
const gaugeConfig = {
type: "doughnut",
data: gaugeData,
options: {
plugins: {
legend: {
display: false,
},
tooltip: {
yAlign: "bottom",
displayColors: false,
callbacks: {
label: function (tooltipItem) {
return tooltipItem.label;
},
},
backgroundColor: gaugeColor,
bodyColor: gaugeColorBorder,
},
},
},
plugins: [gaugeNeedle],
};
// render init block
var myGauge = new Chart(document.getElementById("gauge"), gaugeConfig);

What are the options available for plotting graphs from a table? (Without using any plotting library )

What are the options available for plotting graphs upon selecting different columns from a Data Table. I have worked with ag-grid and I want some thing like that which will come directly out of the box without using any other graph library(like plotly or highcharts) and manually writing code.
As I said in my comment, I recommend looking into Canvas (mdn).
Here's a minimal exmaple that allows panning (mouse 1) and zooming (mouse 2) in addition to callbacks for clicks and mouse moves.
class Chart {
constructor(canvas, hoverCallback, clickCallback) {
this.width = canvas.width;
this.height = canvas.height;
this.ctx = canvas.getContext('2d');
this.ctx.font = '14px serif';
canvas.addEventListener('mousedown', e => {
this.dragged = false;
this.mouseDown = {x: e.offsetX, y: e.offsetY};
});
canvas.addEventListener('mousemove', e => {
hoverCallback?.(this.pixelToCoord(e.offsetX, e.offsetY));
if (!this.mouseDown)
return;
this.dragged = true;
if (e.buttons & 1 && !e.shiftKey)
this.panRange(e.offsetX - this.mouseDown.x, e.offsetY - this.mouseDown.y);
else if (e.buttons & 2 || e.shiftKey)
this.zoomRange(e.offsetX - this.mouseDown.x, e.offsetY - this.mouseDown.y);
this.mouseDown = {x: e.offsetX, y: e.offsetY};
});
canvas.addEventListener('mouseleave', () => hoverCallback?.());
document.addEventListener('mouseup', () => this.mouseDown = null);
canvas.addEventListener('click', e => {
if (this.dragged)
return;
clickCallback?.(this.pixelToCoord(e.offsetX, e.offsetY));
});
canvas.addEventListener('dblclick', e => {
if (this.dragged)
return;
this.resetRange(e.shiftKey);
});
this.pointSets = [];
this.resetRange();
}
set pointSets(value) {
this.pointSets_ = value;
this.draw();
}
resetRange(zeroMins = false) {
let allPoints = this.pointSets_
.flatMap(({points}) => points);
[this.minX, this.deltaX] = Chart.getRange(allPoints.map(({x}) => x), zeroMins);
[this.minY, this.deltaY] = Chart.getRange(allPoints.map(({y}) => y), zeroMins);
this.verifyRange();
this.draw();
}
panRange(x, y) {
this.minX -= x * this.deltaX / this.width;
this.minY += y * this.deltaY / this.height;
this.verifyRange();
this.draw();
}
zoomRange(x, y) {
let dx = x * this.deltaX / this.width;
let dy = -y * this.deltaY / this.height;
this.minX += dx;
this.minY += dy;
this.deltaX -= dx * 2;
this.deltaY -= dy * 2;
this.verifyRange();
this.draw();
}
verifyRange() {
this.minX = Math.max(this.minX, -this.deltaX / 10);
this.minY = Math.max(this.minY, -this.deltaY / 10);
}
draw() {
if (!this.pointSets_ || this.minX === undefined)
return;
this.ctx.fillStyle = 'white';
this.ctx.fillRect(0, 0, this.width, this.height);
this.drawPoints();
this.drawAxis();
}
drawPoints() {
this.pointSets_.forEach(({color, fill, size, points, isPath}) => {
this.ctx.strokeStyle = color;
this.ctx.fillStyle = color;
if (isPath) {
this.ctx.lineWidth = size;
this.ctx.beginPath();
points.forEach((p, i) => {
let {x, y} = this.coordToPixel(p.x, p.y);
if (!i)
this.ctx.moveTo(x, y);
else
this.ctx.lineTo(x, y);
});
if (fill)
this.ctx.fill();
else
this.ctx.stroke();
} else {
points.forEach(p => {
let {x, y} = this.coordToPixel(p.x, p.y);
this.ctx[fill ? 'fillRect' : 'strokeRect'](x - size / 2, y - size / 2, size, size);
});
}
});
}
drawAxis() {
let n = 20;
let step = this.width / n;
let size = 10;
let sizeSmall = 1;
this.ctx.lineWidth = 1;
this.ctx.strokeStyle = `rgb(0, 0, 0)`;
this.ctx.fillStyle = `rgb(0, 0, 0)`;
this.ctx.strokeRect(this.width / n, this.height * (n - 1) / n, this.width * (n - 2) / n, 0); // x axis line
this.ctx.strokeRect(this.width / n, this.height / n, 0, this.width * (n - 2) / n); // y axis line
for (let i = 2; i < n; i += 2) {
let x = i * step;
let y = (n - i) * step;
let xText = Chart.numToPrint(this.minX + i / n * this.deltaX);
let yText = Chart.numToPrint(this.minY + i / n * this.deltaY);
this.ctx.fillText(xText, x - 9, step * (n - 1) + 17); // x axis text
this.ctx.fillText(yText, step - 28, y + 4, 30); // y axis text
this.ctx.fillRect(x - sizeSmall / 2, step * (n - 1) - size / 2, sizeSmall, size); // x axis dots
this.ctx.fillRect(step - size / 2, x - sizeSmall / 2, size, sizeSmall); // y axis dots
}
}
pixelToCoord(x, y) {
return {
x: x / this.width * this.deltaX + this.minX,
y: (1 - y / this.height) * this.deltaY + this.minY,
width: 20 / this.width * this.deltaX,
height: 20 / this.height * this.deltaY
};
}
coordToPixel(x, y) {
return {
x: x === Infinity ? this.width : (x - this.minX) / this.deltaX * this.width,
y: y === Infinity ? 0 : (1 - (y - this.minY) / this.deltaY) * this.height,
};
}
static getRange(values, zeroMin = false, buffer = .1) {
let min = values.length && !zeroMin ? Math.min(...values) : 0;
let max = values.length ? Math.max(...values) : 10;
let delta = max - min + .001;
return [min - delta * buffer, delta + delta * buffer * 2]
}
static numToPrint(n) {
return Math.round(n * 10) / 10;
}
}
let canvas = document.querySelector('canvas');
let chart = new Chart(canvas);
chart.pointSets = [
// e.g. {color: 'rgb(255,0,0)', fill: true, size: 5, points: [{x: 0, y: 1}, ...], isPath: true}
{
color: 'rgb(255,0,0)',
fill: false,
size: 3,
points: [
{x: 0, y: 1},
{x: 1, y: 1},
{x: 1, y: 0},
{x: 0, y: 0},
{x: 0, y: .5},
],
isPath: true,
}, {
color: 'rgb(0,0,255)',
fill: true,
size: 10,
points: [
{x: 0, y: 1},
{x: 0, y: .5},
{x: 3, y: 1},
{x: 6, y: 2},
{x: 7, y: 4},
{x: 6, y: 5},
],
isPath: false,
},
];
chart.resetRange();
canvas {
border: 1px solid;
}
<canvas width="500" height="500"></canvas>

how to create Duval Pentagon in canvas

Hey I have create a Duval Pentagon based on the post of creating duval triangle in canvas: how to create Duval Triangle in canvas.
My final result should be:
my current situation, is that I can created all the segments inside the pentagon,but I didn't figure out a way of placing labels of the gases around each corner(point) of my duval pentagon,any advice or help is welcome.
The creating of the duval pentagon process is like this:
1.creating outer pentagon.
2.creating all the segments in a loop.
3.creating the legend for the pentagon.
Update:
I have tried to build again the the moleculeLabel according to #markE guidance lines. so far no so good tried, I am sure doing something wrong. :(
function moleculeLabel(V,P,text){
var dx=V.x-P.x;
var dy=V.y-P.y;
var rAngle=Math.atan2(dy,dx);
var padding=15; // == how far outside the pentagon you want to go
var outsideX=P.x+(P.radius+padding)*Math.cos(rAngle);
var outsideY=P.y+(P.radius+padding)*Math.sin(rAngle);
ctx.textAlign='center';
ctx.textBaseline='middle';
ctx.fillStyle='black';
ctx.fillText(text,outsideX,outsideY);
}`
$(function() {
//offset :
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var points = [{
x: 397,
y: 149
}, {
x: 318,
y: 346
}, {
x: 112,
y: 347
}, {
x: 44,
y: 147
}, {
x: 221,
y: 27
}, {
x: 397,
y: 149
}];
var cx = 0;
var cy = 0;
for (var i = 0; i < points.lenght; i++) {
cx = cx + points[i].x;
cy = cy + points[i].y;
}
cx = cx / points.lenght;
cy = cy / points.lenght;
var centerPoint = {
x: cy,
y: cy
};
// Define all your segments here
var segments = [{
points: [{
x: 61,
y: 191
}, {
x: 112,
y: 347
}, {
x: 190,
y: 223
}, {
x: 214,
y: 211
}, {
x: 217,
y: 198
}, {
x: 61,
y: 192
}],
fill: 'rgb(172,236,222)',
label: {
text: 'T1',
cx: 130,
cy: 250,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 61,
y: 191
}, {
x: 217,
y: 198
}, {
x: 220,
y: 26
}, {
x: 44,
y: 149
}],
fill: 'deepskyblue',
label: {
text: 'S',
cx: 140,
cy: 150,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 220,
y: 26
}, {
x: 217,
y: 198
}, {
x: 239,
y: 135
}, {
x: 365,
y: 230
}, {
x: 397,
y: 149
}, {
x: 221,
y: 27
}],
fill: 'lightCyan',
label: {
text: 'D1',
cx: 270,
cy: 110,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 214,
y: 211
}, {
x: 239,
y: 135
}, {
x: 365,
y: 231
}, {
x: 320,
y: 336
}, ],
fill: 'navajoWhite',
label: {
text: 'D2',
cx: 270,
cy: 210,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 190,
y: 223
}, {
x: 214,
y: 211
}, {
x: 320,
y: 336
}, {
x: 318,
y: 346
}, {
x: 223,
y: 346
}],
fill: 'tan',
label: {
text: 'T3',
cx: 250,
cy: 310,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 112,
y: 347
}, {
x: 190,
y: 223
}, {
x: 223,
y: 346
}],
fill: 'peru',
label: {
text: 'T2',
cx: 175,
cy: 300,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 210,
y: 105
}, {
x: 219,
y: 105
}, {
x: 219,
y: 68
}, {
x: 210,
y: 67
}],
fill: 'red',
label: {
text: 'PD',
cx: 170,
cy: 87,
withLine: true,
endX: 215,
endY: 88
},
}];
// label styles
var labelfontsize = 12;
var labelfontface = 'verdana';
var labelpadding = 3;
var legendTexts = ['PD = Partial Discharge', 'T1 = Thermal fault < 300 celcius', '...'];
// start drawing
/////////////////////
// draw pentagon
drawPentagon(points);
// draw colored segments inside pentagon
for (var i = 0; i < segments.length; i++) {
drawSegment(segments[i]);
}
moleculeLabel(points[0], centerPoint, 'CH4');
moleculeLabel(points[1], centerPoint, 'CH2');
moleculeLabel(points[2], centerPoint, 'H2');
// draw legend
drawLegend(legendTexts, 10, 10, 12.86);
// end drawing
/////////////////////
function drawSegment(s) {
// draw and fill the segment path
ctx.beginPath();
ctx.moveTo(s.points[0].x, s.points[0].y);
for (var i = 1; i < s.points.length; i++) {
ctx.lineTo(s.points[i].x, s.points[i].y);
}
ctx.closePath();
ctx.fillStyle = s.fill;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
// draw segment's box label
if (s.label.withLine) {
lineBoxedLabel(s, labelfontsize, labelfontface, labelpadding);
} else {
boxedLabel(s, labelfontsize, labelfontface, labelpadding);
}
}
function boxedLabel(s, fontsize, fontface, padding) {
var centerX = s.label.cx;
var centerY = s.label.cy;
var text = s.label.text;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = fontsize + 'px ' + fontface
var textwidth = ctx.measureText(text).width;
var textheight = fontsize * 1.286;
var leftX = centerX - textwidth / 2 - padding;
var topY = centerY - textheight / 2 - padding;
ctx.fillStyle = 'white';
ctx.fillRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.lineWidth = 1;
ctx.strokeRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.fillStyle = 'black';
ctx.fillText(text, centerX, centerY);
}
function lineBoxedLabel(s, fontsize, fontface, padding) {
var centerX = s.label.cx;
var centerY = s.label.cy;
var text = s.label.text;
var lineToX = s.label.endX;
var lineToY = s.label.endY;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = fontsize + 'px ' + fontface
var textwidth = ctx.measureText(text).width;
var textheight = fontsize * 1.286;
var leftX = centerX - textwidth / 2 - padding;
var topY = centerY - textheight / 2 - padding;
// the line
ctx.beginPath();
ctx.moveTo(leftX, topY + textheight / 2);
ctx.lineTo(lineToX, topY + textheight / 2);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
// the boxed text
ctx.fillStyle = 'white';
ctx.fillRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.strokeRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.fillStyle = 'black';
ctx.fillText(text, centerX, centerY);
}
function moleculeLabel(V, P, text) {
var dx = V.x - P.x;
var dy = V.y - P.y;
var rAngle = Math.atan2(dy, dx);
var padding = 15; // == how far outside the pentagon you want to go
var outsideX = P.x + (P.radius + padding) * Math.cos(rAngle);
var outsideY = P.y + (P.radius + padding) * Math.sin(rAngle);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'black';
ctx.fillText(text, outsideX, outsideY);
}
/**
* draw basic pentagon.
**/
function drawPentagon(points) {
ctx.beginPath();
for (var i = 0; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
ctx.closePath();
}
function drawLegend(texts, x, y, lineheight) {
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillStyle = 'black';
ctx.font = '12px arial';
for (var i = 0; i < texts.length; i++) {
ctx.fillText(texts[i], x, y + i * lineheight);
}
}
})
body {
background-color: ivory;
padding: 10px;
}
#canvas {
border: 1px solid red;
margin: 0 auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width=650 height=500></canvas>
For each desired molecule label:
Given a pentagon shaped like this: {x:,y:,radius:} and a molecule vertex {x:,y:}
Use Math.atan2 to find the angle between the polygon centerpoint and the molecule vertex.
var dx=V.x-P.x;
var cy=V.y-P.y;
var rAngle=Math.atan2(dy,dx);
Use basic trigonometry to calculate a point outside the polygon along the line between the centerpoint and vertex.
var padding=15; // == how far outside the pentagon you want to go
var outsideX=P.x+(P.radius+padding)*Math.cos(rAngle);
var outsideY=P.y+(P.radius+padding)*Math.sin(rAngle);
Use fillText to draw the desired label at [outsideX,outsideY]
context.textAlign='center';
context.textBaseline='middle';
context.fillStyle='black';
context.fillText('CH4',outsideX,outsideY);
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var points = [{x:397,y:149},{x:318,y:346},{x:112,y:347},{x:44,y:147},{x: 221,y:27},{x:397,y:149}];
// calc the polygon center mass
var cx,cy;
var totx=0;
var toty=0;
for(var i=0;i<points.length-1;i++){ // don't include the "closing" point in points[]
totx+=points[i].x;
toty+=points[i].y;
}
cx=totx/(points.length-1);
cy=toty/(points.length-1);
draw();
function draw(){
// draw the polygon
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=1;i<points.length;i++){
ctx.lineTo(points[i].x,points[i].y);
}
ctx.stroke();
// draw the labels at their radial extension points
ctx.textAlign='center';
ctx.textBaseline='middle';
var padding=20;
for(var i=0;i<points.length-1;i++){
var p=points[i];
var dx=p.x-cx;
var dy=p.y-cy;
var dist=Math.sqrt(dx*dx+dy*dy);
var rAngle=Math.atan2(dy,dx);
var labelX=cx+(dist+padding)*Math.cos(rAngle);
var labelY=cy+(dist+padding)*Math.sin(rAngle);
ctx.fillText('label#'+i,labelX,labelY)
// demo only, for points[0] draw the
//centerpoint and the radial extension point
if(i==0){
ctx.beginPath();
ctx.moveTo(cx,cy);
ctx.lineTo(labelX,labelY);
ctx.moveTo(cx,cy);
ctx.arc(cx,cy,5,0,Math.PI*2);
ctx.moveTo(labelX,labelY);
ctx.arc(labelX,labelY,3,0,Math.PI*2);
ctx.strokeStyle='red';
ctx.stroke();
ctx.fillStyle='red';
ctx.fill();
ctx.strokeStyle='black';
ctx.fillStyle='black';
}
}
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=500 height=500></canvas>
Final answer, many thanks to #markE.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var points = [{x:397,y:149},{x:318,y:346},{x:112,y:347},{x:44,y:147},{x: 221,y:27},{x:397,y:149}];
// calc the polygon center mass
var cx,cy;
var totx=0;
var toty=0;
for(var i=0;i<points.length-1;i++){ // don't include the "closing" point in points[]
totx+=points[i].x;
toty+=points[i].y;
}
cx=totx/(points.length-1);
cy=toty/(points.length-1);
draw();
function draw(){
// draw the polygon
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=1;i<points.length;i++){
ctx.lineTo(points[i].x,points[i].y);
}
ctx.stroke();
// draw the labels at their radial extension points
ctx.textAlign='center';
ctx.textBaseline='middle';
var padding=20;
for(var i=0;i<points.length-1;i++){
var p=points[i];
var dx=p.x-cx;
var dy=p.y-cy;
var dist=Math.sqrt(dx*dx+dy*dy);
var rAngle=Math.atan2(dy,dx);
var labelX=cx+(dist+padding)*Math.cos(rAngle);
var labelY=cy+(dist+padding)*Math.sin(rAngle);
ctx.fillText('label#'+i,labelX,labelY)
// demo only, for points[0] draw the
//centerpoint and the radial extension point
if(i==0){
ctx.beginPath();
ctx.moveTo(cx,cy);
ctx.lineTo(labelX,labelY);
ctx.moveTo(cx,cy);
ctx.arc(cx,cy,5,0,Math.PI*2);
ctx.moveTo(labelX,labelY);
ctx.arc(labelX,labelY,3,0,Math.PI*2);
ctx.strokeStyle='red';
ctx.stroke();
ctx.fillStyle='red';
ctx.fill();
ctx.strokeStyle='black';
ctx.fillStyle='black';
}
}
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=500 height=500></canvas>

Oscillation function

Trying to create a function that can dynamically oscillate properties of an object. I've already managed to create it outside of a function, but I can't make it work in one. It's because of the angle variable, which increases each frame. Here's an example to make an object oscillate x and y props in a circle.
Example fiddle.
Initialization...
var obj = new Ball(arguments...),
target = {
x: 100,
y: 100
},
angle = 0,
radius = 50,
speed = 0.1;
Loop...
// clear canvas
obj.x = target.x + Math.cos(angle) * radius;
obj.y = target.y + Math.sin(angle) * radius;
angle += speed;
// rAF
That works just fine, but when I try to make it reusable and turn it into a function, it doesn't work.
function oscillate(obj, target, angle, radius, speed) {
obj.x = target.x + Math.cos(angle) * radius;
obj.y = target.y + Math.sin(angle) * radius;
angle += speed;
}
How do I make it work in a function?
That's because angle is not passed by reference. JS does not have pass by reference.
You can just modify the outer variable by not declare a local one:
var map = document.getElementById('map'),
fx = map.getContext('2d');
var ball = {
x: 50,
y: 50,
radius: 50
},
target = {
x: map.width / 2,
y: map.height / 2,
},
angle = 0,
radius = 50,
speed = 0.1;
ball.draw = function(fx) {
fx.beginPath();
fx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
fx.fill();
};
function oscillate(obj, target, radius, speed) {
obj.x = target.x + Math.cos(angle) * radius;
obj.y = target.y + Math.sin(angle) * radius;
angle += speed;
}
(function update() {
fx.clearRect(0, 0, map.width, map.height);
oscillate(ball, target, radius, speed)
ball.draw(fx);
requestAnimationFrame(update);
}());
<canvas id='map'></canvas>
Alternatively, pass an object:
var map = document.getElementById('map'),
fx = map.getContext('2d');
var ball = {
x: 50,
y: 50,
radius: 50
},
data = {
obj: ball,
target: {
x: map.width / 2,
y: map.height / 2,
},
angle: 0,
radius: 50,
speed: 0.1
};
ball.draw = function(fx) {
fx.beginPath();
fx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
fx.fill();
};
function oscillate(data) {
data.obj.x = data.target.x + Math.cos(data.angle) * data.radius;
data.obj.y = data.target.y + Math.sin(data.angle) * data.radius;
data.angle += data.speed;
}
(function update() {
fx.clearRect(0, 0, map.width, map.height);
oscillate(data)
ball.draw(fx);
requestAnimationFrame(update);
}());
<canvas id='map'></canvas>

Categories