Problem
I am trying to put this line drawing in the center of my canvas, when I try to use the moveTo(100, 400) for the x-axis, it does not change the horizontal start position to 100. If I try the same thing with the y-axis it will move the the line along the x-axis.
I also need help with drawing the y-axis numbers 1 - 9 vertically along the y-axis it seems to only align horizontally.
EDIT!: I have manually stroked each point on the y-axis so I have the numbers on there, now I just want to know how to move the graph to center!!
Script
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.linecap = 'round';
// draw a scale with the numbers on it
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF9900';
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.moveTo(100, 400);
for (i = 0; i <= 6; i+=1) {
//put a stroke mark
ctx.lineTo(100*i,400);
ctx.lineTo(100*i,405); //markers
ctx.lineTo(100*i,400);
// write the number 10px below
ctx.strokeStyle = '#000000';
// default size is 10px
ctx.strokeText(i, 100*i, 415);
ctx.strokeStyle = '#FF9900';
}
// draw a vertical scale with lines on it
ctx.moveTo(0, -100);
for (b = 0; b <= 9; b+=1) {
//put a stroke mark
ctx.lineTo(0,44.5*b);
ctx.lineTo(5,44.5*b);
ctx.lineTo(0,44.5*b);
// write the number 10px below
ctx.strokeStyle = '#000000';
// default size is 10px
}
ctx.strokeStyle = '#000000'
ctx.strokeText(1, 8, 365);
ctx.strokeText(2, 8, 320.5);
ctx.strokeText(3, 8, 276);
ctx.strokeText(4, 8, 231.5);
ctx.strokeText(5, 8, 187);
ctx.strokeText(6, 8, 142.5);
ctx.strokeText(7, 8, 98);
ctx.strokeText(8, 8, 53.5);
ctx.strokeText(9, 8, 9);
ctx.strokeStyle = '#FF9900';
ctx.stroke();
<!DOCTYPE html>
<html>
<head>
<title>Canvas Axis calibration</title>
<link rel="stylesheet" type="text/css" href="base.css"/>
</head>
<body>
<canvas id="myCanvas" width="1600" height="500"style="border:1px solid #c3c3c3;">
Canvas is not playing!
</canvas>
</body>
</html>
moveTo() just set starting point for your line, it's not draw actual line. Use lineTo() for draw actual line. so moveTo() is from or where you begin and lineTo() is where you go. So starting point for x axis must be moveTo(800, 0).
var c = document.getElementById("myCanvas"),
ctx = c.getContext("2d"),
lineWidth = 2,
xNumber = 6,
yNumber = 9,
xCenter = c.width / 2,
yCenter = 44.5 * yNumber + 44.5
ctx.linecap = 'round';
// draw a scale with the numbers on it
ctx.lineWidth = lineWidth;
ctx.strokeStyle = '#FF9900';
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.moveTo(xCenter, yCenter);
for (i = 0; i <= xNumber; ++i) {
//put a stroke mark
ctx.lineTo((xCenter + (100 * i)), yCenter);
ctx.lineTo((xCenter + (100 * i)), (yCenter + 5)); //markers
ctx.lineTo((xCenter + (100 * i)), yCenter);
// write the number 10px below
ctx.strokeStyle = '#000000';
// default size is 10px
ctx.strokeText(i, (xCenter + (100 * i)), (yCenter + 15));
}
ctx.strokeStyle = '#FF9900';
ctx.stroke()
// draw a vertical scale with lines on it
ctx.beginPath()
ctx.moveTo(xCenter, yCenter);
for (b = 0; b <= yNumber; ++b) {
//put a stroke mark
if(b === 0) continue;
ctx.lineTo(xCenter, (yCenter - (44.5 * b)));
ctx.lineTo((xCenter - 5), (yCenter - (44.5 * b)));
ctx.lineTo(xCenter, (yCenter - (44.5 * b)));
ctx.strokeStyle = '#000000';
ctx.strokeText(b, (xCenter - 15), (yCenter - (44.5 * b)));
}
ctx.strokeStyle = '#FF9900';
ctx.stroke();
<!DOCTYPE html>
<html>
<head>
<title>Canvas Axis calibration</title>
<link rel="stylesheet" type="text/css" href="base.css"/>
</head>
<body>
<canvas id="myCanvas" width="1600" height="500"style="border:1px solid #c3c3c3;">
Canvas is not playing!
</canvas>
</body>
</html>
CanvasRenderingContext2D has a method for that: translate(). It simply sets a coordinate-shift which is going to be applied to everything you draw afterwards:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.linecap = 'round';
ctx.lineWidth = 2;
ctx.fillStyle = 'blue';
ctx.translate((1600-500)/2,0); // <-----------
ctx.strokeStyle = '#000000';
ctx.beginPath();
ctx.moveTo(100, 400);
for (var i = 0; i <= 6; i+=1) {
ctx.lineTo(100*i,400);
ctx.lineTo(100*i,405);
ctx.lineTo(100*i,400);
ctx.strokeText(i, 100*i, 415);
}
ctx.moveTo(0, -100);
for (var b = 0; b <= 9; b+=1) {
ctx.lineTo(0,44.5*b);
ctx.lineTo(5,44.5*b);
ctx.lineTo(0,44.5*b);
if(b<9)
ctx.strokeText(b+1, 8, 365-44.5*b);
}
ctx.strokeStyle = '#FF9900';
ctx.stroke();
<!DOCTYPE html>
<html>
<head>
<title>Canvas Axis calibration</title>
<link rel="stylesheet" type="text/css" href="base.css"/>
</head>
<body>
<canvas id="myCanvas" width="1600" height="500"style="border:1px solid #c3c3c3;">Canvas is not playing!</canvas>
</body>
</html>
Here I assumed the drawing is 500 units wide, which does not seem to be entirely correct, but you will certainly see the result of translate(). The effect of translate() can be reset with setTransform(1, 0, 0, 1, 0, 0) call (if you are familiar with homogeneous coordinates and transformation matrices, note it has a heavily modified order, see in docs). It is actually a matrix which can do all kinds of 2D transformations (translation, rotation, scaling, skewing), translate() is just a convenience function (the equivalent call probaby would be setTransform(1,0,0,1,(1600-500)/2,0), but I have not tried).
Minor changes:
added the var-s into the loops: otherwise variables become global ones which is usually not a problem for a loop variable like i, but generally considered bad practice
reduced to a single stroke() and two strokeStyle-s. The thing is that lines, arcs and the like are drawn with the settings which are set at the very moment when you call stroke(), it does not matter what happened in between. So color is black for most of the time, as strokeText() is immediate, and color becomes that beige/whatever one just for the stroke()
moved the second set of labels into the corresponding loop. I am not sure if the loop is entirely correct, as 9 labels and 9 line segments are visible, but 10 line segments are drawn.
Think local origins
When you get a building plan you don't get a giant sheet of paper with the plan way in the corner because you are building in the burbs, you want to move some windows out of the summer sun, you don't redraw the plan with new coordinates for each wall.
No you get the plan that fits a small sheet, on the plan is a location and orientation. The position of the walls are fix to the local coordinates of the plan.
Same goes for drawing in 2D. You can define a box as 4 points around an origin. [[-10,-10],[10,-10],[10,10],[-10,10]] and when you draw it you set its location and orientation, you dont change the position of each point to the new location.
Draw local coordinate in the world via setTransform
In the 2D API the position and orientation is set via a transform.
function drawPath(x,y, points) { // only position changes
ctx.setTransform(1,0,0,1,x,y); // set the location
ctx.beginPath();
for(const [x,y] of points) {
ctx.lineTo(x,y);
}
ctx.stroke();
}
const box = [[-10,-10],[10,-10],[10,10],[-10,10]];
drawPath(100, 100, box);
And with scale and rotate
function drawPath(x,y,scale, rotate, points) {
const xdx = Math.cos(rotate) * scale;
const xdy = Math.sin(rotate) * scale;
ctx.setTransform(xdx, xdy, -xdy, xdx, x, y); // set the location
ctx.beginPath();
for(const [x,y] of points) {
ctx.lineTo(x,y);
}
ctx.stroke();
}
drawPath(100, 100, 2, 0.5, box);
const box = [[-10,-10],[10,-10],[10,10],[-10,10]];
const W = canvas.width;
const H = canvas.height;
const ctx = canvas.getContext("2d");
ctx.font = "2opx arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "red";
const rand = v => Math.random() * v;
drawRandom();
function drawPath(x, y,scale, rotate, points) {
const xdx = Math.cos(rotate) * scale;
const xdy = Math.sin(rotate) * scale;
ctx.setTransform(xdx, xdy, -xdy, xdx, x, y); // set the location
ctx.fillText("Hi",0,0);
ctx.beginPath();
for(const [x,y] of points) {
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.setTransform(1, 0, 0, 1, 0, 0); // Resets so line width remains 1 px
ctx.stroke();
}
function drawRandom() {
drawPath(rand(W), rand(H), rand(2) + 0.5, rand(Math.PI * 2), box);
setTimeout(drawRandom, 500);
}
canvas {
border: 1px solid black;
}
<canvas id="canvas" width ="400" height="400"></canvas>
All you need is ctx.setTransform and maybe ctx.transform if you are doing rigged animation stuff. I never use ctx.translate, ctx.scale, ctx.rotate because they are slow, and its hard to picture just where you are, oh and did I say they are SLOW!!!!
To reset the transform (remove scale, rotation and move back to 0,0) call ctx.resetTransform() or ctx.setTransform(1,0,0,1,0,0)
And some more regarding your approach to the code.
Granular coding
Looks like you want to draw a graph.
Manually drawing every tick, setting styles, and dozens of magic numbers and values is not going to make it much fun. Worse is that when it comes time to make changes it will take forever.
Don't repeat
You need to think like a lazy programmer. Create functions so you dont have to do the same thing over and over.
Define styles once and name them
For example setting the 2D context style is a pain. A drawing usually only has a few different styles, so create an object with named styles
const styles = {
textHang: {
textAlign : "center",
textBaseline : "top",
fillStyle: "blue",
font: "16px Arial",
},
};
And a function that will set a style
const setStyle = (style, c = ctx) => Object.assign(c, style);
Now you can set a style
const ctx = myCanvas.getContext("2d");
setStyle(styles, styles.textHang);
ctx.fillText("The text", 100, 100);
Basic 2D point helper
You are working in 2D and 2D uses a lot of points. You will be adding multiplying, copying... 2D points over and over and over.
Reduce the typing and cover the most basic 2D needs with only 7 functions
const P2 = (x = 0, y = 0) => ({x,y});
const P2Set = (p, pAs) => (p.x = pAs.x, p.y = pAs.y, p);
const P2Copy = p => P2(p.x, p.y);
const P2Mult = (p, val) => (p.x *= val, p.y *= val, p);
const P2Add = (p, pAdd) => (p.x += pAdd.x, p.y += pAdd.y, p);
const P2Sub = (p, pSub) => (p.x -= pSub.x, p.y -= pSub.y, p);
const P2Dist = (p1, p2) => ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5;
No line? 2D API
The 2D API is great, but lacking. To just draw a line is crazy long, foo bar....
ctx.linecap = 'round';
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF9900';
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(410, 410);
ctx.stroke();
No way create functions, use named styles, don't enter coordinates use points.
Some common 2D tasks as functions
const clear = (c = ctx) => (setPos(), c.clearRect(0,0,c.canvas.width,c.canvas.height));
const line = (p1, p2, c = ctx) => (c.moveTo(p1.x, p1.y), c.lineTo(p2.x, p2.y))
const setPos = (p, c = ctx) => p ? c.setTransform(1, 0, 0, 1, p.x, p.y) : c.resetTransform();
const path = (p, path, c = ctx) => {
c.setTransform(1,0,0,1,p.x,p.y);
for(const seg of path) { // each segment
let first = true;
for(const p of seg) { // each point
first ? (c.moveTo(p.x,p.y), first = false):(c.lineTo(p.x, p.y));
}
}
}
Example
The following takes all the above and creates 2 Axis. It may seem like a lot extra, but as you add complexity to your drawing you quickly find you need less and less code.
/* Set up the context get common values eg W,H for width and height */
const W = canvas.width;
const H = canvas.height;
const ctx = canvas.getContext("2d");
// Helper functions will use a global ctx, or pass a 2d context as last argument
// P2 is a point. I use p to mean a point
const P2 = (x = 0, y = 0) => ({x,y});
const P2Set = (p, pAs) => (p.x = pAs.x, p.y = pAs.y, p);
const P2Copy = p => P2(p.x, p.y);
const P2Mult = (p, val) => (p.x *= val, p.y *= val, p);
const P2Add = (p, pAdd) => (p.x += pAdd.x, p.y += pAdd.y, p);
const P2Sub = (p, pSub) => (p.x -= pSub.x, p.y -= pSub.y, p);
const P2Dist = (p1, p2) => ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5;
const setStyle = (style, c = ctx) => Object.assign(c, style);
const clear = (c = ctx) => (setPos(0, c), c.clearRect(0,0,c.canvas.width,c.canvas.height));
const line = (p1, p2, c = ctx) => (c.moveTo(p1.x, p1.y), c.lineTo(p2.x, p2.y))
const setPos = (p, c = ctx) => p ? c.setTransform(1, 0, 0, 1, p.x, p.y) : c.resetTransform();
const path = (p, path, c = ctx) => {
setPos(p,c);
for(const seg of path) { // each segment
let first = true;
for(const p of seg) { // each point
first ? (c.moveTo(p.x,p.y), first = false):(c.lineTo(p.x, p.y));
}
}
}
const styles = { // define any of the 2D context properties you wish to set
textHang: {textAlign : "center", textBaseline : "top"},
textLeft: {textAlign : "left", textBaseline : "middle"},
markTextStyle: {fillStyle: "blue", font: "16px Arial"},
markStyle: {
strokeStyle: "black",
lineCap: "round",
lineWidth: 2,
},
};
const paths = { // Array of arrays of points. each sub array is a line segment
markLeft: [[P2(-2, 0), P2(5, 0)]],
markUp: [[P2(0, 2), P2(0, -5)]],
}
// Draw an axis from point to point, using mark to mark, lineStyle for the line
// marks is an array of names for each mark, markStyle is the style for the text marks
// markDist is the distance out (90 to the right) to put the text marks
function drawAxis(fromP, toP, mark, lineStyle, marks, markStyle, markDist) {
const norm = P2Mult(P2Sub(P2Copy(toP), fromP), 1 / P2Dist(fromP, toP));
const step = P2Mult(P2Sub(P2Copy(toP), fromP), 1 / (marks.length-1));
const pos = P2Copy(fromP);
setStyle(lineStyle);
ctx.beginPath();
setPos(); // without argument pos is 0,0
line(fromP, toP);
for(const m of marks) {
path(pos, mark);
P2Add(pos, step);
}
ctx.stroke();
P2Set(pos, fromP);
setStyle(markStyle);
for(const m of marks) {
setPos(pos);
ctx.fillText(m,-norm.y * markDist, norm.x * markDist)
P2Add(pos, step)
}
}
const insetW = W * 0.1;
const insetH = H * 0.1;
const axisText = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
clear();
drawAxis(
P2(insetW, H - insetH), P2(insetW, insetH), paths.markLeft,
styles.markStyle,
axisText,
{...styles.textLeft, ...styles.markTextStyle},
-18
);
drawAxis(
P2(insetW, H - insetH), P2(W - insetW, H - insetH), paths.markUp,
styles.markStyle,
axisText,
{...styles.textHang, ...styles.markTextStyle},
6
);
canvas {
border: 1px solid black;
}
<canvas id="canvas" width ="400" height="400"></canvas>
I'm trying to draw lines progressively (currently using recursive functions) in a canvas element, and I'm able to successfully draw pairs of lines that are parallel to the x or y axes, this way:
function line(xa, ya, xb, yb) {
context.beginPath();
context.moveTo(xa, ya);
context.lineTo(xb, yb);
context.stroke();
}
(function drawLine(i){
line(155, i, 155, i-2);
line(245, i, 245, i-2);
if (i > 35) {
setTimeout(function(){
drawLine(i-2);
}, 10);
}
})(57);
And I get this:
(context.lineWidth is set to 10 and context.lineCap is set to round)
However, I've tried several ways of drawing pairs of lines that aren't strictly horizontal nor vertical but I'm only able to get something like this:
(function drawLine(i){
line(155, i, 155+(57-i)/2, i-2);
line(245, i, 245-(57-i)/2, i-2);
if (i > 35) {
setTimeout(function(){
drawLine(i-2);
}, 10);
}
})(57);
(changing the value of context.lineWidth or context.lineCap doesn't solve the problem)
Is there a way to draw any kind of line progressively in a canvas element?
In your first version, you draw lines from a point based on the current value of i to a point based on the value of i in the next iteration. But in the second version, the x value of your start point is a constant. Base the start point on i, and the end point on i - 2:
let c = document.querySelector('canvas');
let context = c.getContext('2d');
context.lineWidth = 10;
context.lineCap = 'round';
function line(xa, ya, xb, yb) {
context.beginPath();
context.moveTo(xa, ya);
context.lineTo(xb, yb);
context.stroke();
}
(function drawLine(i){
line(155 + (57 - i) / 2, i, 155 + (57 - (i - 2)) / 2, (i - 2));
line(245 - (57 - i) / 2, i, 245 - (57 - (i - 2)) / 2, (i - 2));
if (i > 35) {
setTimeout(function(){
drawLine(i-2);
}, 10);
}
})(57);
<canvas></canvas>
The easiest way is to use Canvas.js:
const canvas = new Canvas('my-canvas', 200, 200).start();
const line1 = new Canvas.Line({
from: {
x: 50,
y: 70
},
to: {
x: 60,
y: 30
},
lineWidth: 7,
lineCap: 'round',
lineLength: 0.1
});
canvas.addElement(line1);
line1.animate('lineLength', {lineLength: 1, duration: 500});
const line2 = new Canvas.Line({
from: {
x: 90,
y: 70
},
to: {
x: 80,
y: 30
},
lineWidth: 7,
lineCap: 'round',
lineLength: 0.1
});
canvas.addElement(line2);
line2.animate('lineLength', {lineLength: 1, duration: 500});
<script src="https://gustavgenberg.github.io/handy-front-end/Canvas.js"></script>
Using line dash to animate along paths.
A simple way to animate along any path is to use the line dash and line dash offset.
const ctx = canvas.getContext('2d');
ctx.lineWidth = 10;
ctx.lineCap = 'round';
function drawLines(){
function drawLine(x1, y1, x2, y2){
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
}
drawLine(10,10,490,90);
drawLine(10,190,490,110);
}
var lineLength = 30; // pixels
var distanceBetween = 400;
var lineSpeed = 300; //pixels per second
ctx.setLineDash([lineLength, distanceBetween]);
function animateLines(time){
ctx.lineDashOffset = -time * lineSpeed / 1000;
ctx.stroke();
}
function loop(time){
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
ctx.beginPath();
drawLines();
animateLines(time);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
<canvas id="canvas" width=500 height=200></canvas>
I have a bubble chart with big bubbles. I want to draw a cross or anything in the center of each bubble, but I can't find any solutions. The following picture show you the context:
I use Chart.js 2.5.0 on meteor.
Your question seemed quite interesting to me, therefore, I constructed the following chartjs plugin, that will help fulfill your requirement.
Chart.plugins.register({
afterDraw: c => {
let datasets = c.data.datasets;
datasets.forEach((e, i) => {
let isHidden = e._meta[0].hidden;
if (!isHidden) {
let data = c.getDatasetMeta(i).data;
data.forEach(e => {
let ctx = c.chart.ctx;
let x = e._model.x;
let y = e._model.y;
let r = e._model.radius;
// draw a cross
// or you can draw anything using general canvas methods
ctx.save();
ctx.beginPath();
ctx.moveTo(x - r / 4, y - r / 4);
ctx.lineTo(x + r / 4, y + r / 4);
ctx.moveTo(x + r / 4, y - r / 4);
ctx.lineTo(x - r / 4, y + r / 4);
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
});
}
});
}
});
ᴅᴇᴍᴏ
Chart.plugins.register({
afterDraw: c => {
let datasets = c.data.datasets;
datasets.forEach((e, i) => {
let isHidden = e._meta[0].hidden;
if (!isHidden) {
let data = c.getDatasetMeta(i).data;
data.forEach(e => {
let ctx = c.chart.ctx;
let x = e._model.x;
let y = e._model.y;
let r = e._model.radius;
// draw a cross
// or you can draw anything using general canvas methods
ctx.save();
ctx.beginPath();
ctx.moveTo(x - r / 4, y - r / 4);
ctx.lineTo(x + r / 4, y + r / 4);
ctx.moveTo(x + r / 4, y - r / 4);
ctx.lineTo(x - r / 4, y + r / 4);
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
});
}
});
}
});
let ctx = document.querySelector('#c').getContext('2d');
let chart = new Chart(ctx, {
type: 'bubble',
data: {
labels: ['Jan', 'Feb', 'Mar'],
datasets: [{
label: 'John',
data: [
{ x: 5, y: 5, r: 10 },
{ x: 10, y: 10, r: 15 },
{ x: 16, y: 15, r: 18 }
],
backgroundColor: '#76d1bf'
}, {
label: 'Smith',
data: [
{ x: 3, y: 10, r: 10 },
{ x: 7, y: 11, r: 15 },
{ x: 12, y: 6, r: 18 }
],
backgroundColor: '#827ada'
}]
},
options: {
responsive: false,
scales: {
xAxes: [{
ticks: {
min: 2,
max: 18,
stepSize: 4
}
}],
yAxes: [{
ticks: {
min: 0,
max: 20,
stepSize: 4
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
<canvas id="c" height="200"></canvas>
Chart.js has always been a very straight forward and simple library. One of the advantages this has is that it is easy to customize. This is also one of the good points of JavaScript, source code is always available for what ever library you use.
There is how ever one draw back with customization. Once you have made changes to the library you will have to use that particular version because customization is not something that the authors will consider when making changes.
So to use the code below you should go to the github page and download the project and use that version of chart.js for your site. I dont change the original, most of the time each customization is specific for a particular case and the code is customised at the clients side.
The change is very simple. First backup the function that draws a point
Chart.canvasHelpers.defaultDrawPoint = Chart.canvasHelpers.drawPoint;
This is done so you can pass on all calls that you are not interested in back to the standard handler.
Next write the intercept code by replacing the function you just backed up.
Chart.canvasHelpers.drawPoint = function(ctx, pointStyle, radius, x, y){
Looking at the original source code you can work out how it draws the circles and just trap that behavior passing on all other argument variants to the original.
If pointStyle is undefined or === "circle" you handle that your self
// pointStyle undefined is default
// pointStyle === "circle" is the default named style
if(pointStyle === undefined || pointStyle === "circle"){
// next 4 lines copied from the source
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
You then add your custom code to render what ever you like. It is important that you save the current 2D context as you dont want to have to worry that you break something further down the line.
// draw a cross
ctx.save(); // save the state
ctx.strokeStyle = "white";
ctx.strokeWidth = 4;
ctx.beginPath();
ctx.moveTo(x - radius *0.3, y - radius *0.3);
ctx.lineTo(x + radius *0.3, y + radius *0.3);
ctx.moveTo(x + radius *0.3, y - radius *0.3);
ctx.lineTo(x - radius *0.3, y + radius *0.3);
ctx.stroke();
Then restore the state of the 2D context
ctx.restore(); // restore the state
The else handles the standard calls you are not interested in
}else{ // all other styles pass on to default handler
Chart.canvasHelpers.defaultDrawPoint(ctx, pointStyle, radius, x, y);
}
For Chart.js this is particularly useful as it gives a way to customize and get the animations as well.
Chart.canvasHelpers.defaultDrawPoint = Chart.canvasHelpers.drawPoint;
Chart.canvasHelpers.drawPoint = function(ctx, pointStyle, radius, x, y){
// PointStyle undefined is default
// PointStyle === "circle" is the default named style
if(pointStyle === undefined || pointStyle === "circle"){
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
// custom code here
ctx.save(); // save the state
ctx.strokeStyle = "white";
ctx.strokeWidth = 4;
ctx.beginPath();
ctx.moveTo(x - radius *0.3, y - radius *0.3);
ctx.lineTo(x + radius *0.3, y + radius *0.3);
ctx.moveTo(x + radius *0.3, y - radius *0.3);
ctx.lineTo(x - radius *0.3, y + radius *0.3);
ctx.stroke();
ctx.restore(); // restor the state
}else{ // all other styles pass on to default handler
Chart.canvasHelpers.defaultDrawPoint(ctx, pointStyle, radius, x, y);
}
}
// some utils to add data
// returns a random int
const rand = (min, max = min + (min = 0))=> Math.floor( Math.random() * (max-min) + min);
// returns a random data point {x,y,r}
const randData = ()=>({x : rand(0,50), y: rand(5,50), r: rand(4,20)});
// create a chart.
const ctx = canvas.getContext("2d");
const chart = new Chart(ctx, {
type: "bubble",
data: {
datasets: [{
label: "Random Data",
backgroundColor: "#7AF",
data: (()=>{
var dat = [];
for(var i = 0; i < 10; i++){ dat.push(randData()) }
return dat;
})(),
}]
},
options: { responsive: false } // this is required to work or it throws
// not sure why by it is not due to the
// changes
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
<canvas id=canvas height=200 width=400></canvas>
To roll back the changes simply set the function back to the original.
Chart.canvasHelpers.drawPoint = Chart.canvasHelpers.defualtDrawPoint;
And remove the extra reference
Chart.canvasHelpers.defualtDrawPoint = undefined;
grunt's answer did not work for me. I don't know if there are different formats or if it's written in an outdated version but I have modified his solution to the following so it would work in my project. Basically I have
removed the register since it wasn't in line with what the example for background-color was like and
found out that the data points' info isn't as nested anymore so I changed the way the properties are accessed, too
plugins: [
{
id: 'background-colour',
beforeDraw: (chart) => {
const ctx = chart.ctx;
ctx.save();
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, width, height);
ctx.restore();
}
},
{
id: 'abc',
afterDraw: (c) => {
let datasets = c.data.datasets;
datasets.forEach((e, i) => {
let isHidden = e.hidden;
if (!isHidden) {
let data = c.getDatasetMeta(i).data;
data.forEach(e => {
let ctx = c.ctx;
let x = e.x;
let y = e.y;
let r = e.options.radius as number;
ctx.save();
ctx.beginPath();
ctx.moveTo(x - r / 4, y - r / 4);
ctx.lineTo(x + r / 4, y + r / 4);
ctx.moveTo(x + r / 4, y - r / 4);
ctx.lineTo(x - r / 4, y + r / 4);
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
});
}
});
}
}
]