Display negative values in canvas line graph - javascript

I am using html5 canvas element to draw line chart. The chart works fine with positive values. But when provided negative values, the chart is not drawn correctly.
This is what I have tried. Any help will be appreciated.
http://jsfiddle.net/nshX6/142/
function getMinY () {
var min = 0;
for(var i = 0; i < data.values.length; i ++) {
if(data.values[i].Y < min) {
min = data.values[i].Y;
}
}
return Math.ceil(min);
}

Here are some tool functions you can use to build a flexible graph.
Your flexible graph will be able to show any range of data and it will always fit on the available canvas size.
calcSourceMinMax: Calculates the minimum and maximum value from a data array.
mapRange: Takes any data value and maps it into a proportional value that is guaranteed to be inside the minimum and maximum of the graphs displayable width & height. This allows your data array to contain any range of values and still never fall outside the graphing display area.
getDisplayXY: Takes a specified x,y data value and finds its display X,Y coordinate on the graph.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var xPadding = 40;
var yPadding = 30;
// Notice I changed The X values
var data = { values:[
{ X: 0, Y: -120 },
{ X: 2, Y: 28 },
{ X: 3, Y: 18 },
{ X: 4, Y: 34 },
{ X: 5, Y: 40 },
{ X: 6, Y: 80 },
{ X: 7, Y: 80 }
]};
// calc the drawable graph boundaries
var graphLeft=xPadding;
var graphRight=canvas.width-xPadding;
var graphTop=yPadding;
var graphBottom=canvas.height-yPadding;
// graph styling
var dotRadius=3;
// calc the min & max values of data.values (calc both X & Y ranges)
var rangeX=calcSourceMinMax(data.values,'X');
var rangeY=calcSourceMinMax(data.values,'Y');
// draw the graph content
var starting=getDisplayXY(data.values[0].X,data.values[0].Y);
dot(starting,dotRadius);
for(var i=1;i<data.values.length;i++){
var ending=getDisplayXY(data.values[i].X,data.values[i].Y);
connector(starting,ending);
dot(ending,dotRadius);
starting=ending;
}
// draw the graph axes
var y0=getDisplayXY(graphLeft,0).displayY;
ctx.beginPath();
ctx.moveTo(graphLeft,graphTop);
ctx.lineTo(graphLeft,graphBottom);
ctx.moveTo(graphLeft,y0);
ctx.lineTo(graphRight,y0);
ctx.strokeStyle='#D3E';
ctx.stroke();
// draw the graph legends
ctx.textAlign='right';
ctx.textBaseline='middle';
var y0=getDisplayXY(graphLeft,0).displayY;
var yMin=getDisplayXY(graphLeft,rangeY.min).displayY;
var yMax=getDisplayXY(graphLeft,rangeY.max).displayY;
var xMax=getDisplayXY(graphRight,rangeX.max).displayX;
ctx.fillText(rangeY.min,graphLeft-10,yMin);
ctx.fillText(0,graphLeft-10,y0);
ctx.fillText(rangeY.max,graphLeft-10,yMax);
ctx.fillText(rangeX.max,graphRight+10,y0);
///////////////////////////////////
// HELPER FUNCTIONS
///////////////////////////////////
//
function getDisplayXY(valueX,valueY){
// calc the display X & Y from data.values[i]
x=mapRange(valueX,rangeX.min,rangeX.max,graphLeft,graphRight);
// Note: canvas y values increase going downward
// so swap graphTop & graphBottom
y=mapRange(valueY,rangeY.min,rangeY.max,graphBottom,graphTop);
return({displayX:x,displayY:y})
}
//
function connector(starting,ending){
ctx.beginPath();
ctx.moveTo(starting.displayX,starting.displayY);
ctx.lineTo(ending.displayX,ending.displayY);
ctx.stroke();
}
//
function dot(position,radius){
ctx.beginPath();
ctx.moveTo(position.displayX,position.displayY);
ctx.arc(position.displayX,position.displayY,radius,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
// map source values into a designated range
function mapRange(value, sourceLow, sourceHigh, mappedLow, mappedHigh) {
return mappedLow + (mappedHigh - mappedLow) * (value - sourceLow) / (sourceHigh - sourceLow);
}
// mapping helper function
function calcSourceMinMax(a,prop){
var min=1000000;
var max=-1000000;
for(var i=0;i<a.length;i++){
var value=a[i][prop];
if(value<min){min=value;}
if(value>max){max=value;}
}
return({min:min,max:max});
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=350 height=300></canvas>
You will want to style the graph according to your design needs. This minimal example shows a legend containing the x,y axes and the min,max ranges of values.
Also, the x-axis is put at the y=0 value. You will also want to check that there is indeed a y=0 in the range of your y values. If not, you might move the x-axis at the bottom of your graph.
Good luck with your project!

Try modifaying this parameters :
var xPadding = 30;
var yPadding = 30;
To :
var xPadding = 30;
var yPadding = 100;
After that you need to change Y step to be bigger.
EDIT
In case that you have dynamic data you need to normalize values.
step 1 : find min value for Y
step 2 : if min value is less than zero (0) you need to normalize all values to be positive. Add that value to every element of array
step 3 : shift Y axes to it's new position.

Related

How would one go about making this grid clickable?

I've created the grid below using a canvas as well as lines going through it, how would one go about making this grid clickable?
The ideal result would be when I click one of the boxes it turns yellow, and a value inside an array would be changed from 0 to 1. I'm fairly certain I know how to set up a 2D array but that is the general idea. Thanks for any help.
var cellCount=20;
var currentGrid = new Array(cellCount).fill().map(() => new Array(cellCount).fill(0));
var nextGrid = new Array(cellCount).fill().map(() => new Array(cellCount).fill(0));
for(var i=0;i<20;i++){
for(var j=0;j<20;j++){
currentGrid[i][j]=0;
nextGrid[i][j]=0;
}
}
var canvas=document.getElementById('grid');
var ctx=canvas.getContext('2d');
function drawGrid(h,w,id){
for(var x=0;x<w;x++){
ctx.moveTo(0,x*20);
ctx.lineTo(h,x*20);
ctx.lineWidth=1;
ctx.strokeStyle='rgb(211,211,211)';
ctx.stroke();
}
for(var y=0;y<h;y++){
ctx.moveTo(y*20,0);
ctx.lineTo(y*20,w);
ctx.lineWidth=1;
ctx.strokeStyle='rgb(211,211,211)';
ctx.stroke();
}
}
function getPosition(event)
{
if (event.x != undefined && event.y != undefined)
{
x = event.x;
y = event.y;
}
}
canvas.addEventListener("mousedown", getPosition, false);
drawGrid(421,421,'grid');
Looking at the code you provided, it appears there is no loop, meaning there will be no other updates to the grid visually. Also, the draw function appears that it would draw offscreen, as you are multiplying your values by 20, which should probably be cellCount.
First I would recommend setting up a 'game loop'. A pattern that involves updating and drawing in a loop:
function loop(tick) {
update(tick);
draw(tick);
requestAnimationFrame(loop);
}
// start the loop
requestAnimationFrame(loop);
Then you'll need to define the update and draw function. In this example you may not have anything to update yet, but you might in the future. The draw function would first erase the background and then draw the grid:
function draw(tick) {
// clear background
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw the grid
drawGrid(numCols, numRows);
}
Now you'll need to define numCols and numRows which is simply the width or height divided by the cellSize. This tells you how many cells in the height and width of the canvas. I see you have cellCount instead of cellSize, but cellSize can be calculated the opposite way by taking canvas height (or width) and dividing it by cellCount (assuming the canvas is square).
var numCols = Math.floor(canvas.width / cellSize);
var numRows = Math.floor(canvas.height / cellSize);
As for the click handler; you'll want to figure out which cell they clicked in by dividing the x and y you've captured by the cellSize.
// assuming x and y are the coordinates on the canvas where the user clicked
var cellX = Math.floor(x / cellSize);
var cellY = Math.floor(y / cellSize);
// now set the cell
nextGrid[cellY][cellX] = 1;

Canvas Graph plotting data incorrectly

I have made a simple graph in a canvas but am having difficulty with two issues.
The first issue is setting the vertical axis with an appropriate scale automatically with enough room for each data value in an array. Ideally i'd like the numbers to be more rounded to the nearest million or thousand etc depending on it's actual value ranges rather than a value like 33145 as the first scale line.
Currently one value is too high for the scale and is not being drawn on the canvas because it is out of bounds.
The second issue, is the points don't seem to be plotting in their correct location, which I am unsure where my mistake was.
I made a JSFiddle as for the most part it might be a bit confusing without seeing it in action:
http://jsfiddle.net/ezttywzr/
This is how i plot my data and draw my vertical axis:
Vertical Axis:
var x = 0,
y,
range = data.max() - data.min(),
valueStep = range / 10,
// get width of largest number
margin = 3 + ctx.measureText(data.min() + (valueStep*10)).width,
pixelStep = (graph.height-40) / 10,
verticalP = pixelStep,
output;
// draw left hand values
for(var i = 0; i < 11; i++){
output = data.min() + (valueStep*i);
y = graph.height-20 - (verticalP + i*pixelStep);
ctx.fillText(output,x,y+6);
ctx.beginPath();
ctx.moveTo(margin, y);
ctx.lineTo(x2,y);
ctx.stroke();
}
Data Plotting:
var y = graph.height,
x = margin,
pos,
valueStep = (graph.width-(margin*2)) / data.length,
pixelRange = graph.height-20,
pp = range / pixelRange;
for(var i = 0; i < data.length; i++){
x += valueStep;
pos = x - (valueStep/2);
ctx.beginPath();
ctx.moveTo(x, graph.height-20);
ctx.lineTo(x, graph.height);
ctx.stroke();
ctx.fillText('Week '+(i+1),pos-(ctx.measureText('Week '+(i+1)).width/2),y);
ctx.beginPath();
ctx.arc(pos,(graph.height-20)-(verticalP+(data[i]/pp)),2,0,2*Math.PI);
ctx.stroke();
ctx.fill();
}
Nice job so far.
I made a few changes: http://jsfiddle.net/ezttywzr/2/
To get the scale I used
STEP = data.max() / NUM_HORIZONTAL_LINES
Where NUM_HORIZONTAL_LINES is the number of horizontal lines you want above the x-axis. In this case I used 10.
This means the first line will be 1 * STEP, the second will be 2 * STEP, the third will be 3 * STEP and so on..
This scale is convenient because it guarantees that the max value fits on the graph. In fact, the max value is on the top line because of the way we defined the scale.
Once we have our scale it's easy to calculate the position of the points relative to the x-axis. It's simply:
(PIXELS_PER_STEP / STEP) * VALUE
To go a step further you can do some math to round the top point of the graph up and pick a scale with that has nice round numbers.

Chart.js Radar Labels Coordinates

is there any way to get the coordinates for the labels in the radar chart?
I'm trying to put images instead of text in the labels area.
You can calculate it using the scale property. Here is something that draws a blue circle at those points after the animation completes.
onAnimationComplete: function () {
for (var i = 0; i < this.scale.valuesCount; i++) {
// get the poitn position
var pointLabelPosition = this.scale.getPointPosition(i, this.scale.calculateCenterOffset(this.scale.max) + 5);
// draw a circle at that point
this.chart.ctx.beginPath();
this.chart.ctx.arc(pointLabelPosition.x, pointLabelPosition.y, 5, 0, 2 * Math.PI, false);
this.chart.ctx.fillStyle = '#77e';
this.chart.ctx.fill();
this.chart.ctx.stroke();
}
}
Fiddle - http://jsfiddle.net/1jgmbyb7/

Javascript: can't represent sinusoidal graph

I want to make a box to move as a sinusoidal graph.
At the point where i am now i simply can't represent the box into the canvas. At the beginning I was able to, but after working out the trigonometry part the box disappeared and a get no error...
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="canvas" width="600" height="300" style="background-color:red"></canvas>
<script type="text/javascript">
var canvas = document.querySelector("canvas");//isoute me document.getElementsByTagName()
var ctx = canvas.getContext("2d");
var can_width = canvas.width;
var can_height = canvas.height;
var x,y;
function PVector(x_,y_){
var y = [];
var x = [0, Math.PI/6, Math.PI/4, Math.PI/3, Math.PI/2, 2/3*Math.PI, 3/4*Math.PI,
5/6*Math.PI, Math.PI, 7/6*Math.PI, 5/4*Math.PI, 4/3*Math.PI, 3/2*Math.PI,
5/3*Math.PI, 7/4*Math.PI, 11/6*Math.PI, 2*Math.PI];
for (var i=0, len=x["length"]; i<len; i++){
var A;
A = Math.sin(x[i]);
y.push(A);
}console.log(y);console.log(x);
return{
x:x,
y:y
};
}
var Point = {
location : {x:0, y: can_height/2},//initial location
velocity : new PVector(x,y),
display : ctx.fillRect(can_width/2,can_height/2 , 25, 25),//initial position of the box
step : function(){
this.location.x += this.velocity.x;
this.location.y += this.velocity.y;
},
display : function(){
ctx.fillRect(this.location.x, this.location.y, 25, 25);
}
};
function update(){
Point["step"]();
ctx.clearRect(0,0, can_width, can_height);
Point["display"]();
window.setTimeout(update, 1000/30);
}
function init(){
update();
}
init();
</script>
</body>
Problem
In your PVector object you are returning Arrays for x and y, while you use them as values in the step() method. This will cause the entire array to be added as a string.
Solution
You need something that traverse that array. Here is an example, it may not be the result you're after, but it shows the principle which you need to apply:
// first note the return type here:
function PVector(x_,y_){
var y = [];
var x = [0, Math.PI/6, Math.PI/4, Math.PI/3, Math.PI/2, 2/3*Math.PI,
...snipped---
return {
x:x, // x and y are arrays
y:y
};
}
var Point = {
location: {
x: 0,
y: can_height / 2,
step: 0 // some counter to keep track of array position
}, //initial location
velocity: new PVector(x, y),
step: function () {
this.location.step++; // increase step for arrays
// using modulo will keep the step as a valid value within the array length:
// if step = 7 and length = 5, index will become 2 (sort of "wrap around")
var indexX = this.location.step % this.velocity.x.length;
var indexY = this.location.step % this.velocity.y.length
this.location.x += this.velocity.x[indexX];
this.location.y += this.velocity.y[indexY];
},
...
Updated fiddle
Tip: I would as Robin in his answer, recommend to simplify the sinus calculation. Sinus-tables are good when performance is needed and the browser can't keep up (ie. will thousands of objects), but in simpler scenario, direct calculation will work too.
If your goal is just to have a box moving in a sinusoidal graph, it can be done simpler.
This jsfiddle shows a slightly simpler example of a box moving in a sinusoidal graph where I just removed parts of your code and calculate the path with Math.sin and use time instead of precalculated values for x.
function update(){
time += 0.1;
ctx.clearRect(0,0, can_width, can_height);
x = time;
y = (can_height/2)+(can_height/2)*Math.sin(time);
console.log(x, y);
ctx.fillRect(x*16, y, 25, 25);
window.setTimeout(update, 1000/30);
}
The variables are modified to make it look ok on the canvas. You can edit the addition to time, and the altitude and base line for y, to fit your needs.
If you need to follow the specification in your code, look at the answer by Ken.
Since you want a sinusoidal move, it makes sense to use... the sin function.
The formula for a sinusoidal move is :
y = maxValue * sin ( 2 * PI * frequency * time ) ;
where the frequency is in Herz (== 'number of times per second') and time is in second.
Most likely you'll use Date.now(), so you'll have a time in millisecond, that you need to convert. Since the value of PI should not change in the near future, you can compute once the magic number
k = 2 * PI / 1000 = 0.006283185307179587
and the formula becomes :
y = sin( k * frequency * Date.now() );
Here's a simple example on how to use the formula :
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
var can_width = canvas.width;
var can_height = canvas.height;
// magic number
var k = 0.006283185307179587;
// oscillations per second
var frequency = 1/5;
// ...
var amplitude = can_width / 8;
function animate() {
ctx.clearRect(0,0,can_width, can_height);
ctx.fillStyle= '#000';
var y = amplitude * Math.sin ( k * frequency * Date.now() );
ctx.fillRect(can_width/2, can_height/2 + y, 20, 20 );
}
setInterval(animate, 30);
<canvas id="canvas" width="400" height="200" style="background-color:red"></canvas>

How to remove the y axis ticks in flot

I am using flot to generate bar graphs.
Here is my code bar graph code
I need to make the y axis tick to disappear.
I need to put some label on the top of each bar
How to do it?
Okay, after a lot of mucking around with Flot and downloading the source, I finally figured out a good starting point for you.
The jsFiddle demo is here.
The guts of the code is using a hook for drawSeries which draws the label:
function drawSeriesHook(plot, canvascontext, series) {
var ctx = canvascontext,
plotOffset = plot.offset(),
labelText = 'TEST', // customise this text, maybe to series.label
points = series.datapoints.points,
ps = series.datapoints.pointsize,
xaxis = series.xaxis,
yaxis = series.yaxis,
textWidth, textHeight, textX, textY;
// only draw label for top yellow series
if (series.label === 'baz') {
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
ctx.lineWidth = series.bars.lineWidth;
ctx.fillStyle = '#000'; // customise the colour here
for (var i = 0; i < points.length; i += ps) {
if (points[i] == null) continue;
textWidth = ctx.measureText(labelText).width; // measure how wide the label will be
textHeight = parseInt(ctx.font); // extract the font size from the context.font string
textX = xaxis.p2c(points[i] + series.bars.barWidth / 2) - textWidth / 2;
textY = yaxis.p2c(points[i + 1]) - textHeight / 2;
ctx.fillText(labelText, textX, textY); // draw the label
}
ctx.restore();
}
}
See the comments for where you can customise the label.
To remove the y-axis ticks, that is just a simple option setting. In addition, you can work out the maximum y-value for each of the bar stacks and then add about 100 to that to set a maximum Y value that will allow for the space taken up by the labels. The code for all of that then becomes:
// determine the max y value from the given data and add a bit to allow for the text
var maxYValue = 0;
var sums = [];
$.each(data,function(i,e) {
$.each(this.data, function(i,e) {
if (!sums[i]) {
sums[i]=0;
}
sums[i] += this[1]; // y-value
});
});
$.each(sums, function() {
maxYValue = Math.max(maxYValue, this);
});
maxYValue += 100; // to allow for the text
var plot = $.plot($("#placeholder"), data, {
series: {
stack: 1,
bars: {
show: true,
barWidth: 0.6,
},
yaxis: {
min: 0,
tickLength: 0
}
},
yaxis: {
max: maxYValue, // set a manual maximum to allow for labels
ticks: 0 // this line removes the y ticks
},
hooks: {
drawSeries: [drawSeriesHook]
}
});
That should get you started. You can take it from here, I'm sure.

Categories

Resources