How to draw a hover effect behind a bar chart? - javascript

I'm using ChartJS in my website. I want to make an interactive bar chart where the user by clicking on a certain index of the bar will get data from it in a table.
I was trying to make the chart more user-friendly where on hover they will see a shadow behind the bar so they will almost understand that they have to click on it.
The issue is that the drawn shadow is too large and the bar index and looks like this:
While what I'm looking for is that gray background only on the hovered index so it should fill only the first data under MIRKO T label.
Here is what my code looks like:
Chart.controllers.bar = Chart.controllers.bar.extend({
draw: function (ease) {
Chart.controllers.line.prototype.draw.call(this, ease);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
topY = 1000000,
width = activePoint._view.width,
bottomY = 0;
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x + width * 1.3, bottomY);
ctx.lineWidth = width * 4.3;
ctx.strokeStyle = 'rgba(160, 160, 160, 0.11)';
ctx.stroke();
ctx.restore();
}
}
});

I solved it by playing with the lintTo and lineWidth values
my final code looks like this:
Chart.controllers.bar = Chart.controllers.bar.extend({
draw: function (ease) {
Chart.controllers.line.prototype.draw.call(this, ease);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
topY = 1000000,
width = activePoint._view.width,
bottomY = 0;
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.lineWidth = width + 20;
ctx.strokeStyle = 'rgba(0, 123, 255, 0.1)';
ctx.stroke();
ctx.restore();
}
}
});

Related

Dynamically locate button inside HTML <canvas>

Inside canvas I draw a line, that line will create dynamically. In the end of line, i want to create an HTML button. But I don't know how to create.
Plz, help me.
HTML
<canvas id="c"></canvas>
<input id="btn" type="button">
Javascript
var line = document.createElement("canvas");
var ctx = line.getContext("2d");
document.getElementById("c").style.height = "300px";
ctx.fillStyle = "red";
ctx.fillRect(10, 1, line.width, line.height);
var ctx = c.getContext("2d");
drawLine(20, 20, 150, 70);
function drawLine(x1, y1, x2, y2) {
var dx = x2 - x1,
dy = y2 - y1,
len = (Math.sqrt(dx*dx+dy*dy)-1)|0,
ang = Math.atan2(dy, dx);
ctx.translate(x1|0, y1|0);
ctx.rotate(ang);
ctx.drawImage(line, 0, 0, len, 1);
ctx.setTransform(1,0,0,1,0,0)
}
ctx.arc(20, 20, 5, 0, 2 * Math.PI);
ctx.fillStyle = 'green';
ctx.fill();
ctx.strokeStyle = 'red'
ctx.stroke();
DEMO
You can't put or draw an HTML element in a canvas. You can create the button and then use position: absolute with the same left and top as the end of the line's x and y.
Take a look at this snippet:
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
ctx.fillStyle = 'green';
ctx.strokeStyle = 'red';
drawLine(20, 20, 150, 70);
ctx.arc(20, 20, 5, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
function drawLine(x1, y1, x2, y2) {
var button = createButtonOnCanvas(x2, y2);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.closePath();
}
function createButtonOnCanvas(x, y) {
var btn = document.createElement("button");
document.body.appendChild(btn);
btn.style.position = "absolute";
btn.style.left = x + "px";
btn.style.top = y + "px";
btn.innerHTML = "btn";
return btn;
}
<canvas id="c" width="300" height="100"></canvas>
Since you want to create a button, instead of moving an already existing one, I made a function that creates a completely new button and moves it over a certain (x, y) of the canvas.
You should also use the lineTo() method to draw a line and resize the canvas' width and height attributes. If the width and height attributes are 100 and 100 while the CSS width and height styles are 200px and 200px, those 100x100 pixels will be stretched to fill a 200x200 canvas and everything drawn will get blurry. For more information, take a look at this question.

canvas purpose of the order of elements

Seeking a canvas and try once in the case.
I draw such a thing jsfiddle.
var widthCanvas = 960;
var heightCanvas = 1910;
var widthWhiteLine = 250;
var startY = 0;
//shadow
ctx.shadowColor = '#111';
ctx.shadowBlur = 20;
//right top stroke
ctx.beginPath();
ctx.lineWidth = widthWhiteLine;
ctx.shadowColor = '#111';
ctx.shadowBlur = 20;
ctx.strokeStyle = '#f2f2f2';
ctx.moveTo( 1920 + widthWhiteLine /1.5 , 0);
ctx.lineTo(widthCanvas - widthWhiteLine /1.6, widthCanvas + widthWhiteLine *1.4);
ctx.stroke();
//left top stroke
ctx.beginPath();
ctx.lineWidth = widthWhiteLine;
ctx.strokeStyle = '#f2f2f2';
ctx.moveTo(-widthWhiteLine /1.4 , startY);
ctx.lineTo(widthCanvas + widthWhiteLine / 1.6, widthCanvas + widthWhiteLine *1.4);
ctx.stroke();
//left bottom stroke
ctx.beginPath();
ctx.lineWidth = widthWhiteLine;
ctx.strokeStyle = '#f2f2f2';
ctx.moveTo(-widthWhiteLine /1.4, heightCanvas);
ctx.lineTo(widthCanvas + widthWhiteLine / 1.6, widthCanvas - widthWhiteLine *1.4);
ctx.stroke();
ctx.closePath;
//right bottom stroke
ctx.beginPath();
ctx.lineWidth = widthWhiteLine;
ctx.strokeStyle = '#f2f2f2';
ctx.moveTo(1920 + widthWhiteLine /1.4, heightCanvas);
ctx.lineTo(widthCanvas - widthWhiteLine /1.6 , widthCanvas - widthWhiteLine *1.4);
ctx.stroke();
ctx.closePath;
After drawing for the correct display of the cross-hairs in the middle, I need to have an element of that right top stroke has become higher than the right bottom stroke but not higher than the left top stroke.
This can be done using the canvas?
You'll need to manage the layering on your own. The best way to do that is to put your draw routines into functions then run the functions in order of bottom to top. I've updated you're fiddle to give you an idea of what I'm talking about.
// pseudo code
function drawSquare() {
...
}
function drawCircle() {
...
}
drawCircle();
drawSquare();

HTML 5 Canvas, rotate everything

I made a cylinder gauge, very similar to this one:
It is drawn using about 7 or so functions... mine is a little different. It is very fleixble in that I can set the colors, transparency, height, width, whether there is % text shown and a host of other options. But now I have a need for the same thing, but all rotated 90 deg so that I can set the height long and the width low to generate something more like this:
I found ctx.rotate, but no mater where it goes all the shapes fall apart.. ctx.save/restore appears to do nothing, I tried putting that in each shape drawing function. I tried modifying, for example, the drawOval function so that it would first rotate the canvas if horizontal was set to one; but it appeared to rotate it every single iteration, even with save/restore... so the top cylinder would rotate and the bottom would rotate twice or something. Very tough to tell what is really happening. What am I doing wrong? I don't want to duplicate all this code and spend hours customizing it, just to produce something I already have but turned horizontal. Erg! Help.
Option 1
To rotate everything just apply a transform to the element itself:
canvas.style.transform = "rotate(90deg)"; // or -90 depending on need
canvas.style.webkitTransform = "rotate(90deg)";
Option 2
Rotate context before drawing anything and before using any save(). Unlike the CSS version you will first need to translate to center, then rotate, and finally translate back.
You will need to make sure width and height of canvas is swapped before this is performed.
ctx.translate(ctx.canvas.width * 0.5, ctx.canvas.height * 0.5); // center
ctx.rotate(Math.PI * 0.5); // 90°
ctx.translate(-ctx.canvas.width * 0.5, -ctx.canvas.height * 0.5);
And of course, as an option 3, you can recalculate all your values to go along the other axis.
Look at the rotate function in this example. You want to do a translation to the point you want to rotate around.
example1();
example2();
function rotate(ctx, degrees, x, y, fn) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(degrees * (Math.PI / 180));
fn();
ctx.restore();
}
function rad(deg) {
return deg * (Math.PI / 180);
}
function example2() {
var can = document.getElementById("can2");
var ctx = can.getContext('2d');
var w = can.width;
var h = can.height;
function drawBattery() {
var percent = 60;
ctx.beginPath();
ctx.arc(35,50, 25,0,rad(360));
ctx.moveTo(35+percent+25,50);
ctx.arc(35+percent,50,25,0,rad(360));
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = "rgba(0,255,0,.5)";
ctx.arc(35,50,25,0,rad(360));
ctx.arc(35+percent,50,25,0,rad(360));
ctx.rect(35,25,percent,50);
ctx.fill();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "#666666";
ctx.moveTo(135,25);
ctx.arc(135,50,25, rad(270), rad(269.9999));
//ctx.moveTo(35,75);
ctx.arc(35,50,25,rad(270),rad(90), true);
ctx.lineTo(135,75);
ctx.stroke();
}
drawBattery();
can = document.getElementById("can3");
ctx = can.getContext('2d');
w = can.width;
h = can.height;
rotate(ctx, -90, 0, h, drawBattery);
}
function example1() {
var can = document.getElementById('can');
var ctx = can.getContext('2d');
var color1 = "#FFFFFF";
var color2 = "#FFFF00";
var color3 = "rgba(0,155,255,.5)"
var text = 0;
function fillBox() {
ctx.save();
ctx.fillStyle = color3;
ctx.fillRect(0, 0, can.width / 2, can.height);
ctx.restore();
}
function drawBox() {
ctx.save();
ctx.beginPath();
ctx.strokeStyle = ctx.fillStyle = color1;
ctx.rect(10, 10, 50, 180);
ctx.font = "30px Arial";
ctx.fillText(text, 25, 45);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = color2;
ctx.lineWidth = 10;
ctx.moveTo(10, 10);
ctx.lineTo(60, 10);
ctx.stroke();
ctx.restore();
}
fillBox();
rotate(ctx, 90, can.width, 0, fillBox);
text = "A";
drawBox();
color1 = "#00FFFF";
color2 = "#FF00FF";
text = "B";
rotate(ctx, 90, can.width, 0, drawBox);
centerRotatedBox()
function centerRotatedBox() {
ctx.translate(can.width / 2, can.height / 2);
for (var i = 0; i <= 90; i += 10) {
var radians = i * (Math.PI / 180);
ctx.save();
ctx.rotate(radians);
ctx.beginPath();
ctx.strokeStyle = "#333333";
ctx.rect(0, 0, 50, 50)
ctx.stroke();
ctx.restore();
}
}
}
#can,
#can2,
#can3 {
border: 1px solid #333333
}
<canvas id="can" width="200" height="200"></canvas>
<canvas id="can2" width="200" height="100"></canvas>
<canvas id="can3" width="100" height="200"></canvas>

drawing canvas works as long i use it one time

I am drawing a canvas and rotating it based on a value, it works if i use the canvas one time on a page.
If i add it the second time to the page, only the last one gets drawn, i cant find the error in my code and i dont get a js error.
i think the problem is in the next function:
function animate(){
function drawnumbers()
{context.save();
context.fillStyle = "#000000";
context.translate(73,0);
context.font="10px Orbitron";
context.textAlign = "center";
context.rotate(((i*(180/min)))*Math.PI/180);
context.fillText(data.values[i].amount,0,3);
context.restore();
};
if (d < defer){
context.clearRect(0,0,400,400);
d++;
context.save();
var ang = ((((d-minn)*(180/angle)))*(Math.PI/180));
context.translate(38,39);
context.scale(.8,.8);
base_image = new Image();
base_image.src = 'http://oi44.tinypic.com/2hfkx8p.jpg';
context.translate(base_image.width/2, base_image.height/2);
context.rotate(ang );
context.drawImage(base_image, -base_image.width/2, -base_image.height/2);
context.restore();
context.save();
context.beginPath();
context.arc(100,100,64,1*Math.PI,2*Math.PI, false);
context.lineWidth = .4;
context.strokeStyle="#00A1DE";
context.globalAlpha = 0.7;
context.stroke();
context.restore();
context.save();
context.translate(100,100);
context.rotate(Math.PI/180);
context.strokeStyle = "#00A1DE";
context.lineWidth = .7;
for (var i=0;i < data.values.length; i++){
context.beginPath();
context.moveTo(62,0);
context.lineTo(67,0);
context.stroke();
context.globalAlpha = 0.7;
drawnumbers();
context.rotate((182/(min))*(-Math.PI/180));
}
context.restore();
context.fillStyle="white";
context.fillRect(38,101,123,75);
context.save();
context.fillStyle = "#00a1de";
context.font = "22px Orbitron";
context.textAlign = "center";
context.fillText(defer, 100, 90);
context.restore();
context.save();
context.fillStyle = "#000000";
context.font = "10px arial";
context.textAlign = "center";
context.fillText(eenheid, 100, 115);
context.restore();
}
else
{
clearTimeout(t);
};
t=setTimeout("animate()",30-d);
};
check example to better understand:
http://jsbin.com/ogEgURu/1/
I had it in a function but it remains the same problem so i think something is wrong with my code.
Can anyone see the problem i am not seeing ?
Your code is way too complex, especially since there is no good reason for this complexity.
Copying a big (>200) lines block of code to duplicate a functionality is error-prone.
You'll be able to see easily the issue once you refactored your code.
Just a few hints :
Very easy one : beautify the code.
No redundancy : If a code lies here twice or more, make a function and factorize.
Break down the code into smaller parts. For example : drawText(context, text, x,y, font ) (to print eenheid and defer), drawNumbers(context), drawRotatingImage(context, angle), ...
use closePath() each time you beginPath();
load once the image when page loads, and wait for it to be loaded before animating.
do not define a function in a loop (drawnumbers).
use a single object to store the several parameters (context, angle, ...), or
even switch to an object oriented style.
have only one animate() loop, that will call several draw(...) functions if need be.
after all this, your code will look much simpler, and the bug should vanish very quickly.
I did this work (partially), in this fiddle :
http://jsfiddle.net/gamealchemist/ztczK/1/ (edited)
The code looks like :
// parameters : settings for one gauge display
var parameters1 = {
data: data,
defer: '520',
context: context,
left: 38,
top: 30,
d: 0,
angle: 0,
scale: 0.8,
//... whatever parameter here
};
var parameters2 = ... ;
split the draw into many functions so it's much simpler to understand :
// draws a gauge
function drawGauge(param) {
preDraw(param);
drawBaseImage(param);
drawArc(param);
drawTheNumbers(param);
writeDefer(param);
writeEenheid(param);
postDraw(param);
}
// translate and scales context, and updates some values for the gauge
function preDraw(param) {
var minn = param.data.values[param.data.values.length - 1].amount;
var maxn = data.values[0].amount;
var angle = maxn - minn;
var d = param.d;
param.ang = ((((d - minn) * (180 / angle))) * (Math.PI / 180));
var ctx = param.context;
ctx.save();
ctx.translate(param.left, param.top);
ctx.scale(param.scale, param.scale);
context.fillStyle = "white";
context.fillRect(0, 60, 123, 75);
}
// restore context
function postDraw(param) {
var ctx = param.context;
ctx.restore();
param.d++;
}
function drawBaseImage(param) {
var ctx = param.context;
var ang = param.ang;
ctx.save();
ctx.translate(base_image.width / 2, base_image.height / 2);
ctx.rotate(ang);
ctx.drawImage(base_image, -base_image.width / 2, -base_image.height / 2);
ctx.restore();
}
function drawArc(param) {
var ctx = param.context;
ctx.save();
ctx.beginPath();
ctx.arc(base_image.width / 2, base_image.height / 2, 64, 1 * Math.PI, 2 * Math.PI, false);
ctx.lineWidth = .4;
ctx.strokeStyle = "#00A1DE";
ctx.globalAlpha = 10.7;
ctx.stroke();
ctx.restore();
}
function writeDefer(param) {
var ctx = param.context;
var defer = param.defer;
ctx.save();
ctx.fillStyle = "#00a1de";
ctx.font = "22px Orbitron";
ctx.textAlign = "center";
ctx.fillText(defer, base_image.width / 2, base_image.height / 2);
ctx.restore();
}
function writeEenheid(param) {
var ctx = param.context;
ctx.save();
ctx.fillStyle = "#000000";
ctx.font = "10px arial";
ctx.textAlign = "center";
ctx.fillText(eenheid, base_image.width / 2, base_image.height / 2 + 20);
ctx.restore();
}
function drawTheNumbers(param) {
var ctx = param.context;
var dataValues = param.data.values;
var count = dataValues.length;
ctx.save();
ctx.translate(base_image.width / 2, base_image.height / 2);
ctx.rotate(Math.PI / 180);
ctx.strokeStyle = "#00A1DE";
ctx.lineWidth = .7;
ctx.fillStyle = "#000000";
ctx.font = "10px Orbitron";
ctx.textAlign = "center";
ctx.globalAlpha = 0.7;
for (var i = 0; i < count; i++) {
ctx.beginPath();
ctx.moveTo(62, 0);
ctx.lineTo(67, 0);
ctx.stroke();
ctx.closePath();
ctx.fillText(dataValues[i].amount, 60, 3);
ctx.rotate(-Math.PI / count);
}
context.restore();
}
then animate becomes very simple, even with several gauges :
function animate() {
context.clearRect(0, 0, canvasWidth, canvasHeight);
drawGauge(parameters1);
drawGauge(parameters2);
setTimeout(animate, 15);
};
base_image.onload = animate();

Unable to apply fill for path drawn rectangle

I am trying to form a cell object using paths in HTML5 Canvas. But I am unable to apply fill for the path I made. Tried out several things but not able to solve it.
class Cell {
constructor({ i, j }) {
this.i = i;
this.j = j;
this.visited = false;
this.walls = [true, true, true, true]; // top right bottom left
this.show = canvasContext => this._show(canvasContext);
}
_show(ctx) {
const x = this.j * cellSize;
const y = this.i * cellSize;
if (!this.visited) {
ctx.fillStyle = "green";
} else {
ctx.fillStyle = "yellow";
}
// ctx.fillStyle = "green";
ctx.strokeStyle = "black";
ctx.beginPath();
if (this.walls[0]) {
ctx.moveTo(x, y);
ctx.lineTo(x + cellSize, y); // top
}
if (this.walls[3]) {
ctx.moveTo(x, y);
ctx.lineTo(x, y + cellSize); // left
}
if (this.walls[1]) {
ctx.moveTo(x + cellSize, y);
ctx.lineTo(x + cellSize, y + cellSize); // right
}
if (this.walls[2]) {
ctx.moveTo(x, y + cellSize);
ctx.lineTo(x + cellSize, y + cellSize); // bottom
}
ctx.fill();
ctx.stroke();
// ctx.fill();
}
}
See the Pen Maze Generator by Dhanushu (#dhanushuUzumaki) on CodePen.
The problem appears to be that you aren't creating a closed shape, you are just creating a series of lines that don't form a shape.
This is a closed path. If you took a pencil and drew the points, you'd see that it would be closed.
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = "#F00"; // red
ctx.strokeStyle = "#0F0"; // green
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(100, 50);
ctx.lineTo(100, 100);
ctx.lineTo(50, 100);
ctx.closePath();
ctx.fill();
ctx.stroke();
canvas {
border: 1px solid #000;
}
<canvas />
This is an open path, who's stroke looks the same:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = "#F00"; // red
ctx.strokeStyle = "#0F0"; // green
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(100, 50);
ctx.moveTo(100, 50);
ctx.lineTo(100, 100);
ctx.moveTo(100, 100);
ctx.lineTo(50, 100);
ctx.moveTo(50, 100);
ctx.lineTo(50, 50);
ctx.closePath();
ctx.fill();
ctx.stroke();
canvas {
border: 1px solid #000;
}
<canvas />
By calling moveTo() you are essentially picking the pencil up, and breaking the shape. There is nothing to fill, because it's not one contiguous path. It's just 4 separate lines that are close to one another.
For your particular problem, I see two ways to solve this. The first would be to stop using moveTo() so often and just draw the lines. However, for your particular problem, that could be tricky.
Instead, what you might draw to do is draw individual rectangles to fill in each block of your grid. It seems like that logic might be easier to work out.

Categories

Resources