I'm using ChartJS to draw a line chart. The problem is that I'm trying to draw some small lines on Vertical axis to mark the axis in each 10% like this:
extendChartLine: function() {
window.Chart.plugins.register({
beforeDraw: function(chart) {
var ctx = chart.chart.ctx,
chartArea = chart.chartArea,
spacing = (chartArea.bottom - 40) /10;
for(var i = 1; i < 10; i++) {
ctx.restore();
ctx.beginPath();
ctx.moveTo(40, chartArea.bottom - i*spacing);
ctx.strokeStyle = '#333';
ctx.lineTo(47, chartArea.bottom - i*spacing);
ctx.lineWidth = 0.2;
ctx.stroke();
ctx.closePath();
ctx.save();
}
}
});
The problem is that is function is called many times causing those lines are drawing more than one time and collapsing on each others (I know that because some lines are darker than others)
I tried to use a flag variable in order to make use these codes are executing on 1 time only, but no lines are drawing on the chart.
I faced the same, if the charts have the animation option on, I think that is causing the redraw to be called multiple times
Related
I am trying to use different line width for different axes for parallel coordinates plot using d3.js
It works fine only if its in strictly decreasing order (for eg: lineWidth=10 for 1st axis, lineWidth=5 for 2nd axis and lineWidth=2 for 3rd axis). But if the values are like (lineWidth=10 for 1st axis, lineWidth=2 for 2nd axis and lineWidth=5 for 3rd axis) then for both the second and third axis the line thickness is 5, which is the greater value after a smaller one always overrides the smaller value. Is there any way to handle this issue? Your help is much appreciable.
Below is some code snippet which I'm currently working on.
if (i == 0) {
ctx.beginPath();
ctx.moveTo(x,y);
}
else {
if(i == 1) {
ctx.lineWidth = 1;
}
if(i == 2) {
ctx.lineWidth = 5;
}
if(i == 3) {
ctx.lineWidth = 10;
}
var cp1x = x - 0.55*(x-x0);
var cp1y = y0;
var cp2x = x - 0.45*(x-x0);
var cp2y = y;
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
ctx.stroke();
}
For the above code the line width is 10 for all the axes.
If I use ctx.beginPath() before every if statement, then the line thickness is working fine. But unfortunately the lines drawn between axes are shifting its position (only half-lines are being drawn)
Thanks in advance.
If I use ctx.beginPath() at every if statement then this is what I'm getting
Move
ctx.beginPath();
ctx.moveTo(x,y);
before every if statement. That way each iteration will be a new path and you will move to the correct position to start each line.
I have this jsfiddle which creates a pattern of 4 points.
What I want is for it to continuously draw the projected line until the user click for point B, then point C and D.
function draw(){
//
ctx.clearRect(0,0,cw,ch);
// draw connecting lines
for(var i=0;i<connectors.length;i++){
var c=connectors[i];
var s=anchors[c.start];
var e=anchors[c.end];
ctx.beginPath();
ctx.moveTo(s.x,s.y);
ctx.lineTo(e.x,e.y);
ctx.stroke();
}
// draw circles
for(var i=0;i<anchors.length;i++){
ctx.beginPath();
ctx.arc(anchors[i].x,anchors[i].y,radius,0,Math.PI*2);
ctx.fill();
ctx.fillText(anchors[i].label,anchors[i].x-5,anchors[i].y-15);
}
}
Ok so, basically you need your connector adding function to be slightly smarter, so we can make this work like in this fiddle
(You were adding way to many connectors, and it stopped at length over 7, this fixes both those)
if(draggingIndex==-1 && fullDrag == null){
addAnchor(startX,startY);
var al = anchors.length-1;
var almod4 = al%4;
if(almod4==1){
connectors.push({start:al-1,end:al});
}
if(almod4==2){
connectors.push({start:al-2,end:al});
connectors.push({start:al-1,end:al});
}
if(almod4==3){
connectors.push({start:al-2,end:al});
connectors.push({start:al-1,end:al});
}
draw();
}
As you can see, based on the value of anchors.length-1 modular 4, we know if we need to draw 1 or 2 lines. In our draw function we can then add:
if (anchors.length>0 && anchors.length%4>0){
ctx.strokeStyle='gray';
var al = anchors.length-1;
var almod4 = al%4;
if (almod4==1 || almod4==2){
//draw extra line
ctx.beginPath();
ctx.moveTo(anchors[al-1].x,anchors[al-1].y);
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
}
ctx.beginPath();
ctx.moveTo(anchors[al].x,anchors[al].y);
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
}
Note that instead of checking for almod4 being 2 or 3, we check for 1 and 2, because that means we're in the process of adding 2 or 3.
Now all you need to do is tell it to draw at every mouseover, and voila, preview lines.
I am trying to draw lines with canvas and I am changing the coordinates with a for loop.
here is my canvas element:
<canvas id="c" width="300px" height="300px"></canvas>
and here is the js codes:
var c = document.getElementById('c');
ci = c.getContext('2d');
for(var a = 18; a < 300; a +=18){
fnc(a, ci);
}
function fnc(x, ci){
ci.strokeStyle = 'red';
ci.moveTo(0, x);
ci.lineTo(300, x); ci.lineWidth = 0.2; ci.stroke();
}
As you can see I am trying to draw these lines with 18px spaces between them. But the thickness of the lines and the color(or opacity, I am not sure) are changing from top to bottom.
Here is a fiddle : http://jsfiddle.net/J6zzD/1/
So what is wrong with that I can't find my mistake. Why are the color and the thicknesses are different?
UPDATE :
I just wrote these lines out of the function and now all the lines becomes faded but thicknesses are same. So strange :
ci.strokeStyle = 'red';
ci.lineWidth = 0.2; ci.stroke();
here is demo : http://jsfiddle.net/J6zzD/4/
That's again the eternal issue of forgetting to call beginPath.
Each time you call moveTo then lineTo, you create a new *sub*path, which adds to the current Path.
Then each time you call stroke(), the current path, so all the current subpaths get re-drawn, when the last added path is drawn for the first time.
Since opacities will add-up, top lines will reach 100% opacity (alpha=255) when the bottom line, drawn once, will have a 20% opacity (lineWidth=0.2) .
In your second fiddle, you stroke only once, so all lines have 20% opacity, which is correct for the 0.2 lineWidth.
So : use beginPath before drawing a new figure.
In this case you have two choices :
• draw line by line
OR
• draw once a path with all lines as subpath.
(see code below).
TIP : To get clean lines remember that pixels's center is at the (+0.5, +0.5) coordinates of each pixels, so
a 'trick' is to translate by 0.5, 0.5 on app start, then only use rounded coordinates and lineWidth.
1) draw line by line
http://jsfiddle.net/gamealchemist/J6zzD/6/
var c = document.getElementById('c');
var ctx = c.getContext('2d');
ctx.translate(0.5, 0.5);
ctx.lineWidth = 1;
for (var y = 18; y < 300; y += 18) {
strokeLine(ctx, y);
}
function strokeLine(ctx, y) {
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.moveTo(0, y);
ctx.lineTo(300, y);
ctx.stroke();
}
2) draw multiple subPath :
(you can have only one color for one stroke() )
http://jsfiddle.net/gamealchemist/J6zzD/7/
var c = document.getElementById('c');
var ctx = c.getContext('2d');
ctx.translate(0.5, 0.5);
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.beginPath();
for (var y = 18; y < 300; y += 18) {
addLineSubPath(ctx, y);
}
ctx.stroke();
function addLineSubPath(ctx, y) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
}
See: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Applying_styles_and_colors#A_lineWidth_example
Because canvas coordinates do not directly reference pixels, special care must be taken to obtain crisp horizontal and vertical lines.
Basically, because you're trying to draw a line that's 0.2 pixels wide, the browser does some math to approximate a continuous number into discrete units and you get your "fading" lines.
So now we can fix up your code by changing context.lineWidth to 1 (I actually remove it because it defaults to 1) and shifting everything down by half a pixel.
var c = document.getElementById('c');
ci = c.getContext('2d');
for(var a = 18.5; a < 300.5; a +=18)
{
fnc(a, ci);
}
function fnc(x, ci)
{
ci.strokeStyle = 'red';
ci.moveTo(0, x);
ci.lineTo(300, x);
ci.stroke();
}
Demo
I'm plotting a graph on a canvas and having trouble to draw the grid of the plot underneath the graph. My data points are drawn as rectangles (fillRect). When I first draw the graph and then draw the grid it works as expected, but since the grid is on the graph it doesnt look good. But when I draw the grid first and then plot the graph, all the grids disappear underneath.
I draw my plots as follows:
var plots = document.getElementsByClassName("PlotCanvas");
for (var x=0; x < tokens.length; x++)
{
var canvas = plots[x];
canvas.width = arrayOfArrays[x].length;
var context = canvas.getContext("2d");
for(var point=1; point<arrayOfArrays[x].length; point++)
{
context.fillRect(point, arrayOfArrays[x][point],...);
}
}
Then draw the grids as:
function DrawGrids(plots)
{
for(var count=0; count<plots.length; count++)
{
var ctx = plots[count].getContext("2d");
ctx.beginPath();
for (var x = 0.5; x < plots[count].width; x += 20) {
ctx.moveTo(x, 0);
ctx.lineTo(x, plots[count].height);
}
for (var y = 0.5; y < plots[count].height; y += 20) {
ctx.moveTo(0, y);
ctx.lineTo(plots[count].width, y);
}
ctx.strokeStyle = "#eee";
ctx.stroke();
}
}
Could someone suggest me how I can draw the grid underneath the plot. Or how to draw the graph such that it doesn't draw on the whole canvas thus disappearing the grid drawn earlier.
Thank you.
Use ctx.globalCompositeOperation="destination-over" to draw your grid behind your plots!
// draw your plots here
// save the context
ctx.save();
// set compositing to "destination-over"
// New drawings are drawn behind the existing canvas content.
ctx.globalCompositeOperation = "destination-over";
// draw your grids behind your plots!
DrawGrids();
// restore the context
ctx.restore();
I want to move a widget around on the canvas, and for various reasons I don't want to use sprites. I'm using the latest version of Chrome. In order to move the widget, I 'undraw' it and then redraw it in another place. By 'undraw', I mean that I just draw the same image in the same place, but draw it with the same color as the background, so the widget disappears completely before I draw the new one. The problem is that when I 'undraw', traces of the original image remain on the canvas. I've poked around on related questions here and haven't found anything that helps. I understand the problem of drawing a one-pixel line and getting anti-aliasing, so I set my line width to 2 (and various other non-integer values), but to no avail. Anyone have any ideas? Here's a fiddle demo, and here's the function that does the update:
function draw(){
if(previousX !== null) {
ctx.lineWidth = 1;
ctx.fillStyle = '#ffffff';
ctx.strokeStyle = '#ffffff';
drawCircle(previousX, previousY, 20);
}
ctx.lineWidth = 1;
ctx.fillStyle = '#000000';
ctx.strokeStyle = '#000000';
drawCircle(x, y, 20);
console.log('drew circle (' + x + ', ' + y + ')');
previousX = x;
previousY = y;
}
P.S. I'm just a hobbyist with no great experience in graphics, so please dumb-down your answer a bit if possible.
When your draw a shape with anti-aliasing, you are doing a solid covering of some pixels, but only a partial covering of the edge pixels. The trouble is that pixels (temporarily ignoring LCD panels) are indivisible units. So how do we partially cover pixels? We achieve this using the alpha channel.
The alpha channel (and alpha blending) combines the colour at the edge of a circle with the colour underneath it. This happens when the circle only partially covers the pixel. Here's a quick diagram to visualise this issue.
The mixing of colours causes a permanent change that is not undone by drawing the circle again in the background colour. The reason: colour mixing happens again, but that just causes the effect to soften.
In short, redrawing only covers up the pixels with total coverage. The edge pixels are not completely part of the circle, so you cannot cover up the edge effects.
If you need to erase the circle, rather think about it in terms of restoring what was originally there. You can probably copy the original content, then draw the circle, then when you want to move the circle, restore the original content and repeat the process.
This previous SO question may give you some ideas about copying canvas regions. It uses the drawImage method. The best solution would combine the getImageData and putImageData methods. I have modified your Fiddle example to show you how you might do this. You could try the following code:
var x, y, vx, vy;
var previousX = null, previousY = null;
var data = null;
function draw(){
ctx.lineWidth = 2.5;
ctx.fillStyle = '#000000';
ctx.strokeStyle = '#FF0000';
drawCircle(x, y, 20);
previousX = x;
previousY = y;
}
function drawCircle(x, y, r){
// Step 3: Replace the stuff that was underneath the previous circle
if (data != null)
{
ctx.putImageData(data, previousX - r-5, previousY - r-5);
}
// Step 1: Copy the region in which we intend to draw a circle
data = ctx.getImageData(x - r-5, y - r-5, 2 * r + 10, 2 * r + 10);
// Step 2: Draw the circle
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.stroke();
ctx.fill();
}