I want to draw vertical lines at points on the x axis in dygraphs.
They should:
Span the entire y axis automatically, but not prevent other series from being automatically zoomed in on the y axis when manually zoomed to a range of the x axis
Be completely vertical
have no horizontal lines joining them
In that sense, a lot like having multiple extra axis lines drawn at random points.
But it would be helpful if they have the same features of regular series plots in that when it is hovered over, a label displays its value along the x axis.
In case it matters, they will be plotted on the same graph as other series lines, and may or may not have common x axis values with those series.
How might it be achieved?
A way to draw vertical lines, is to use the underlayCallback. It allows you to draw the background of the graph.
Here is a sample that draw 3 vertical lines at X=100, X=200 and X=500 :
<script src="https://cdnjs.cloudflare.com/ajax/libs/dygraph/1.1.1/dygraph-combined.js"></script>
<div id="div_g" style="width:400px; height:200px;"></div>
<script>
// A basic sinusoidal data series.
var data = [];
for (var i = 0; i < 1000; i++) {
var base = 10 * Math.sin(i / 90.0);
data.push([i, base]);
}
new Dygraph(
document.getElementById("div_g"),
data, {
labels: ['X', 'Y'],
underlayCallback: function(canvas, area, g) {
canvas.strokeStyle = 'red';
var lines = [100, 200, 500];
for (var idx = 0; idx < lines.length; idx++) {
var canvasx = g.toDomXCoord(lines[idx]);
var range = g.yAxisRange();
canvas.beginPath();
canvas.moveTo(canvasx, g.toDomYCoord(range[0]));
canvas.lineTo(canvasx, g.toDomYCoord(range[1]));
canvas.stroke();
canvas.closePath();
}
}
}
);
</script>
Related
Plotly surface data is arranged as a 2d Array (a matrix) whose indices correspond to x and y values and whose elements indicate the z values. E.g. if element [0][0] equals 10, that indicates an (x,y,z) coordinate of (0,0,10).
The problem: Because Array indices start at zero, it seems impossible to graph surfaces that have negative x or y values.
Here is a CodePen with three surfaces plotted. The surfaces look fine because only two octants are shown in the graph - the (+x,+y) quadrant. If all octants are displayed in the graph (CodePen), then it ends up looking incomplete because the plotted surfaces won't extend into the remaining 3 quadrants.
The general form of surface data is:
{ z : dataArray,
type : 'surface',
opacity : 0.9 }
Is there a way to give the surface data an xstart or ystart, or the like, so that full 3d surfaces can be drawn?
You can add your own x and y coordinates, Plotly just assumes them if you do not provide them.
From the documentation:
x (data array)
Sets the x coordinates.
y (data array)
Sets the y coordinates.
Code is based on the example here.
z = [[8.83,8.89,8.81,8.87,8.9,8.87],
[8.89,8.94,8.85,8.94,8.96,8.92],
[8.84,8.9,8.82,8.92,8.93,8.91],
[8.79,8.85,8.79,8.9,8.94,8.92],
[8.79,8.88,8.81,8.9,8.95,8.92],
[8.8,8.82,8.78,8.91,8.94,8.92],
[8.75,8.78,8.77,8.91,8.95,8.92],
[8.8,8.8,8.77,8.91,8.95,8.94],
[8.74,8.81,8.76,8.93,8.98,8.99],
[8.89,8.99,8.92,9.1,9.13,9.11],
[8.97,8.97,8.91,9.09,9.11,9.11],
[9.04,9.08,9.05,9.25,9.28,9.27],
[9,9.01,9,9.2,9.23,9.2],
[8.99,8.99,8.98,9.18,9.2,9.19],
[8.93,8.97,8.97,9.18,9.2,9.18]];
var x = [];
var y = [];
for (var i = 0; i < z.length; i += 1) {
x[i] = [];
y[i] = [];
for (var j = 0; j < z[i].length; j += 1) {
x[i].push(j + i - 10);
y[i].push(j - 3);
}
}
Plotly.newPlot('myDiv', [{z: z,
x: x,
y: y,
type: 'surface'}]);
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="myDiv"></div>
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.
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/
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.
Too much code to paste here so....
Please see JS Fiddle at http://jsfiddle.net/1a2s35m2/ for current code.
I am attempting to create a horizontal bar chart using flot. As you will see from the Fiddle, this works fine but I want to display the values of the bars within the bars themselves and not as labels in the Y Axis, as in the picture below...
I have attempted to use the "labels" plugin and also the barnumbers plugin but these don't seem to work. (Barnumbers comes close but displays 0 1 2 3 as the values.
Any ideas?
I'm really starting to sound like a broken record on here, but as your charts become really complicated forget the plugins and do it yourself.
Here's modified code from my links above catered for how you drew your plots:
// after initial plot draw, then loop the data, add the labels
// I'm drawing these directly on the canvas, NO HTML DIVS!
// code is un-necessarily verbose for demonstration purposes
var ctx = somePlot.getCanvas().getContext("2d"); // get the context
var allSeries = somePlot.getData(); // get your series data
var xaxis = somePlot.getXAxes()[0]; // xAxis
var yaxis = somePlot.getYAxes()[0]; // yAxis
var offset = somePlot.getPlotOffset(); // plots offset
ctx.font = "12px 'Segoe UI'"; // set a pretty label font
ctx.fillStyle = "black";
for (var i = 0; i < allSeries.length; i++){
var series = allSeries[i];
var dataPoint = series.datapoints.points; // one point per series
var x = dataPoint[0];
var y = dataPoint[1];
var text = x + '%';
var metrics = ctx.measureText(text);
var xPos = xaxis.p2c(x)+offset.left - metrics.width; // place at end of bar
var yPos = yaxis.p2c(y) + offset.top - 2;
ctx.fillText(text, xPos, yPos);
}
Updated fiddle.