Highcharts: How to exempt a series from redraw zoom calculations? - javascript

In this jsfiddle the chart has a nice zoom effect every time the visiblity of a series is toggled.
But when I add the UpperLimit series this effect is lost because that series has the lowest and highest x-values.
How can I make the chart zoom in on the series of my choice and keep other series from affecting zoom boundaries?
{
name: 'UpperLimit',
color: '#FF0000',
dashStyle: 'ShortDash',
showInLegend: false,
//affectsZoomBox: false, //No such thing :(
data: [
[1, 100],
[10, 100]
]
},

I don't think it is possible using configuration of the series. However, if you are willing to hack a bit it will be possible to exclude one or more series from the calculation of axis extremes:
chart.xAxis[0].oldGetSeriesExtremes = chart.xAxis[0].getSeriesExtremes;
chart.xAxis[0].getSeriesExtremes = function() {
var axis = this;
var originalSeries = axis.series;
var series = [];
for (var i = 0; i < originalSeries.length; i++) {
// Filter out the series you don't want to include in the calculation
if (originalSeries[i].name != 'UpperLimit') {
series.push(originalSeries[i]);
}
}
axis.series = series;
axis.oldGetSeriesExtremes();
axis.series = originalSeries;
}
The code shows how to overwrite the getSeriesExtremes function on the axis object. The method is replaced with a wrapper that removes the series that should be excluded, then calls the original function and then restores the original series to the axis.
This is clearly a hack. However, it works on my machine...

Related

Chartjs animate x-axis

I want to use a chartjs linechart to visualize my data points. Chartjs seems to animate the graph by default, but it does not animate the values on the x-axis. The x-axis only move in discrete steps.
Is there any way to enable animation on the axis also?
Thanks!
As far as I am aware, ChartJS does not support x-axis animation out-of-the-box. So you'll have to hack it. There are several ways to possibly do this, but the following methods seems to work.
If You Want to Animate the Data On the X-Axis
When a chart is updated, the following steps occur: 1) The axes are drawn, and then 2) a draw() function is called to draw the data. There are different draw() functions for different chart types, and the function for line charts is Chart.controllers.line.prototype.draw. The draw() functions take one argument, which I will call animationFraction, that indicates how complete the animation is as a fraction. For instance, if an animation is 5% complete, animationFraction will be 0.05, and if an animation is 100% complete (i.e. if the chart is in its final form), animationFraction=1. The draw() function is called at each step of the animation to update the data display.
One hack to animate the x-axis then is to monkey-patch the line chart draw() function to translate the canvas in the horizontal dimension at every draw step:
var hShift = (1-animationFraction)*ctx.canvas.width;
hShift is the horizontal shift in pixels of the chart. As defined above, the data will sweep in from the right; if you want it to sweep in from the left, you can make the above negative. You then save the canvas context state, transform the canvas using hShift, draw the chart data, and then restore the canvas to its original state so that on the next animation frame the axes will be drawn in the correct spot:
ctx.save();
ctx.setTransform(1, 0, 0, 1, hShift, 0);
ctx.oldDraw.call(this, animationFraction);
ctx.restore();
In the above, this refers to the chart object, and oldDraw refers to the original line chart drawing function that was saved previously:
var oldDraw = Chart.controllers.line.prototype.draw;
You can additionally setup your new draw() function to read new animation options that allow you to set whether the x-axis and y-axis are animated:
var oldDraw = Chart.controllers.line.prototype.draw;
Chart.controllers.line.prototype.draw = function(animationFraction) {
var animationConfig = this.chart.options.animation;
if (animationConfig.xAxis === true) {
var ctx = this.chart.chart.ctx;
var hShift = (1-animationFraction)*ctx.canvas.width;
ctx.save();
ctx.setTransform(1, 0, 0, 1, hShift,0);
if (animationConfig.yAxis === true) {
oldDraw.call(this, animationFraction);
} else {
oldDraw.call(this, 1);
}
ctx.restore();
} else if (animationConfig.yAxis === true) {
oldDraw.call(this, animationFraction);
} else {
oldDraw.call(this, 1);
}
}
You can then create a line chart with both axes animated with:
var lineChart = new Chart(ctx, {
type: 'line',
data: data,
options: {
animation: {
duration: 5000,
xAxis: true,
yAxis: true,
}
}
});
See https://jsfiddle.net/16L8sk2p/ for a demo.
If You Want to Animate the X-Axis Limits
If you want to animate the x-axis limits--i.e. move the data, axis ticks, and tick labels, then you can use the following strategy. It's a bit quirky, so it might take some effort to work out the kinks for any given use-case, but I believe it should work generally. First, you'll need to convert the line plot to a scatter plot. Line charts have categorical x-axes that move in steps, so you can't set the axis limits to be between ticks, which is what you'll need to do to get the animation. So you'll need to use a line scatter plot instead, since scatter plots can have arbitrary axis limits. You can do this by numbering each data point, and assigning that number to the x-value for that data point. For instance, to generate a random dataset, you could do:
var DATA_POINT_NUM = 58;
var data = {
labels: [],
datasets: [
{
data: [],
},
]
}
for (var i=0; i<DATA_POINT_NUM; i++) {
data.datasets[0].data.push({ x: i,
y: Math.random()*10
});
data.labels.push(String.fromCharCode(65+i));
}
You'll then need to write a function to convert between the assigned x-values of your data points, and the data point labels (i.e. the categories that will be on the charts x-axis):
function getXAxisLabel(value) {
try {
var xMin = lineChart.options.scales.xAxes[0].ticks.min;
} catch(e) {
var xMin = undefined;
}
if (xMin === value) {
return '';
} else {
return data.labels[value];
}
}
where lineChart is our Chart object, which will be defined below. Note that ChartJS draws the chart slightly differently if there's a label at x-axis's minimum value, so you'll need to write this function to return an empty string if the value==the minimum value of the x-axis. You can then define the Chart object:
var lineChart = new Chart(ctx, {
type: 'line',
data: data,
options: {
animation: false,
scales: {
xAxes: [{
type: 'linear',
position: 'bottom',
ticks: {
min: 0,
max: 10,
callback: getXAxisLabel, // function(value) { return data.labels[value]; },
autoSkip: false,
maxRotation: 0,
},
}]
}
}
});
ticks.callback is set to our getXAxisLabel function above. When ChartJS draws the x-axis, it will pass the x-values of the data points to the callback function and then use the resulting string as the value on the x-axis. In this way, we can draw a scatter chart like a line chart. I've also set autoSkip=false and maxRotation=0 to make sure the axis labels get drawn in a consistent way.
You can then animate the chart by adjusting the x-axis ticks.min and ticks.max values and calling the chart's .update() method. To illustrate this, the code below scans along the charts x-axis, showing ten data points at a time.
var xMin = 0; // Starting minimum value for the x-axis
var xLength = 10; // Length of the x-axis
var animationDuration = 5000; // Duration of animation in ms
// Calculate animation properties
var framesPerSec = 100;
var frameTime = 1000/framesPerSec;
var xStep = (DATA_POINT_NUM-xMin+xLength)/(animationDuration/1000*framesPerSec);
function nextFrame() {
var xMax = xMin+xLength;
if (xMax < DATA_POINT_NUM-1) {
if (xMax+xStep > DATA_POINT_NUM-1) {
xMax = DATA_POINT_NUM-1;
xMin = xMax-xLength;
}
lineChart.options.scales.xAxes[0].ticks.min = xMin;
lineChart.options.scales.xAxes[0].ticks.max = xMax;
lineChart.update();
setTimeout(nextFrame, frameTime);
xMin += 0.1;
}
}
nextFrame();
Putting it all together: https://jsfiddle.net/qLhojncy/
I am no expert in javascript but I found an example for Chartjs that, when inserted a new data point, updates the x-axis via animation as it seems, maybe it helps you: example.
Example source: sitepoint.com

Square Line Chart / Step Chart jqplot

I need to plot 3 series of data, the first is a line and the other 2 are just dots. However the line should be a step chart (instead of the line drawing from point to point, it should draw the line horizontal and then up to the value
I am stuck as to how to get this with JQPlot.
$(document).ready(function(){
var plot1 = $.jqplot ('chart1', [[3,7,9,1,4,6,8,2,5]]);
});
The above code would produce the blue line on the below graph, instead I need the green line.
Unfortunately, I am not allowed to make comments. Therefore I have to write a new answer.
The already given answer suggests to subtract a small amount from the x value (0.001 in this example) to prevent the triangle effect. But this is not quite accurate and can only be seen as workaround.
The triangle effect is caused by the sorting performed by jqPlot. Sorting is required by most chart types, including line charts. If the data is already sorted before feeding it to jqPlot, sorting can be disabled for jqPlot by setting the sortData attribute to false, see jqPlot.sortData
This will prevent the sorting issues and therefore no triangle effect occurs!
You may also want to hide the point markers as jqPlot doesn't know the difference between the real points and our injected artificial points.
var data = [3, 7, 9, 1, 4, 6, 8, 2, 5];
var points = [[1, data[0]]];
for (var i = 1; i < data.length; i++) {
points.push([i + 1, data[i - 1]]);
points.push([i + 1, data[i]]);
}
var plot1 = $.jqplot('chart1', [points], {
sortData: false,
seriesDefaults: {
showMarker: false
}
});
Try it in a fiddle
If you want to get also the point markers right, the only option I see is changing the rendering logic, e.g. writing a step chart plugin.
For reference, see also the answers in the following post: jqPlot step chart not plotting in series order
You need to specify both the x and y value for each point on the graph. The tricky thing is, if two points have the same x value, jqplot may reverse them, which winds up looking like a triangle plot rather than a square. So, the solution is to take each point after the first, subtract a small amount from the x value (in my example, 0.001), and make that the x value for a new point that has the same y value as the point before it. Here's a hard-coded example:
var plot1 = $.jqplot ('chart1', [[
[1,3], [1.999,3],
[2,7], [2.999,7],
[3,9], [3.999,9],
//...
]]);
Try it in a fiddle.
To create such a list in code, just loop over the original data set and add the necessary extra steps:
var data = [3,7,9,1,4,6,8,2,5];
var points = [[1, data[0]]], len = data.length;
for (var i = 1; i < len; i++) {
points.push([i + .999, data[i - 1]]);
points.push([i + 1, data[i]]);
}
var plot1 = $.jqplot ('chart1', [points]);
Try it in an updated fiddle.

I can't get my line graph to plot correctly using jqplot

I want to plot a graph with the following as the xaxis:
var xaxis = [31.1,31.2,31.3,31.4,31.5, 32.1,32.2,32.3,32.4,32.5];
notice how there is a skip between 31.5 and 32.1. However, when I plot my line graph, there is a large space between these two points. Here's my code:
$(document).ready(function(){
var cust1 = [[31.1,10],[31.2,15],[31.3,25],[31.4, 60],[31.5,95]];
var cust2 = [[31.1,0],[31.2,15],[31.3,30],[31.4, 50],[31.5,85]];
var data = [];
data.push(cust1);
data.push(cust2);
var xaxis = [31.1,31.2,31.3,31.4,31.5, 32.1,32.2,32.3,32.4,32.5];
var plot3 = $.jqplot('line-chart', data,
{
title:'Design Progress',
axes: {
xaxis: {
//renderer: $.jqplot.LineRenderer,
label: 'Work Weeks',
ticks: xaxis
},
yaxis: {
label: "Percent Complete",
max: 100,
min: 0
}
}
}
);
});
I think it's because I'm not specifying a renderer option in my xaxis options. However, I've tried to use $.jqplot.LineRenderer and $.jqplot.CategoryAxisRenderer without any luck (I even set my xaxis values as strings but that didn't work). Anybody know what's going on?
Here's a pic to further clarify:
Reason why it happens : jQuery flot library is building the graph with values that determined by your data.
When you provide such data, the plugin will set the axis values to be as same as the text and with the borders of the numbers you gave.
what you can do, is set the text to be different than the axis value.
You can easily do it by options.xaxis.ticks.push([value, "the text"]).
Pay attention that you are the one who is going to set which label will have which axis value, and this calls for setting the options parameter before calling the $plot

Flot Re-scale Yaxis min/max on X-axis scale

I have one long unixtime, value Array which is used to initiate a flot chart, and some buttons to change the scale, what I can't seem to be able to do is get Y-axis to scale with the change in X-scale.
Here is an example chart:
http://jsfiddle.net/U53vz/
var datarows = //Data Array Here
var options = { series: { lines: { show: true }, points: { show: true } },
grid: { hoverable: true,clickable: false, },
xaxis: { mode: "time", min: ((new Date().getTime()) - 30*24*60*60*1000), max: new Date().getTime(), }
};
function castPlot() {
window.PLOT = $.plot("#placeholder", [{ data: dataRows }], options
);
};
In the official example scaling is automatic and unspecified on the Y-axis:
http://www.flotcharts.org/flot/examples/axes-time/index.html
The only alternative I can think of is looping through the dataset and calculating new Y min/max on each button press. Unless I am breaking some very obvious default function.
When calculating y-scale, flot does not look at only the "viewable" data but the whole dataset. Since the data points are still present, the y min/max respects them. So your options are:
Subset the series data down to the desired range and let flot scale both x and y.
As you suggested, calculate your own min/max on the y axis.
If you plot get any more complicated than it is now (especially if you start setting up click/hover events on it), I would also recommend you switch to redrawing instead of reiniting your plot.
var opts = somePlot.getOptions();
opts.xaxes[0].min = newXMin;
opts.xaxes[0].max = newXMax;
opts.yaxes[0].min = newYMin;
opts.yaxes[0].max = newYMax;
somePlot.setupGrid();
somePlot.draw();
EDITS
Here's one possible solution.

How to zoom into x axis for multiple Flot charts that were created

I'm trying to get zoomin to work for the Flot charts created using following code.
var options = {
yaxis: { min: 0 },
xaxis: { mode: "time" },
series:{
lines: { show: true },
points: { show: true }
},
grid: {
hoverable: true,
clickable: false,
mouseActiveRadius: 30,
backgroundColor: { colors: ["#D1D1D1", "#7A7A7A"] }
},
selection:{mode: "x"}
};
var pdata = [];
for (var key in datasets) {
pdata = [];
pdata.push(datasets[key]);
$.plot( $('<div style="width:1200px;height:600px;"></div>').appendTo('#placeholder'),pdata,options);
$('<h5 align="center">'+datasets[key]['label']+'</h5>').appendTo('#placeholder');
$('<br>').appendTo('#placeholder');
$("#placeholder").UseTooltip();
};
Here I'm creating multiple charts in a loop.
How can I add zoomin feature.
Thank you.
Follow-up to Mark's answer: unique IDs are not really a Flot limitation; that's a requirement of the HTML spec. Browsers generally let you get away with breaking this rule, but it's still not a good idea. Mark's answer is good, but here's one that doesn't require an array-search on every event:
$.each(datasets, function(key, dataset) {
var element = $('<div style="width:1200px;height:600px;"></div>')
.appendTo('#placeholder');
var plot = $.plot(element, [dataset], options);
var plotOptions = plot.getOptions();
element.bind('plotselected', function(event, ranges) {
plotOptions.xaxes[0].min = ranges.xaxis.from;
plotOptions.xaxes[0].max = ranges.xaxis.to;
plot.setupGrid();
plot.draw();
});
};
flot generally expects it's place holder div to have a unique id. You would then use this unique id to assign a specific plotselected event to that plot. The way you have your code structured, though, you are appending the real placeholder div to a parent div as you create your plots. I like your approach so we need to work around flot's limitation.
So, in your plot call give your real placeholder div a class name. This will give us something to bind the plotselected event to. Also you need to save a reference to all the plot objects you've created. I'd just use a global array.
myPlots.push(
$.plot( $('<div class="myPlot" style="width:300px;height:100px;"></div>').appendTo('#placeholder'),pdata,options)
);
Where myPlots is the global array and my class is myPlot.
After this, you can set up the plotselected handler on the jquery selector .myPlots. Next for the tricky part, you need to find your plot object reference inside the handler. The easiest way to do this, I found, is to loop your myPlots array and compare their divs to the div the event happens on:
$(".myPlot").bind("plotselected", function (event, ranges) {
for (var i = 0; i < myPlots.length; i++)
{
var aPlot = myPlots[i];
if (aPlot.getPlaceholder()[0] == event.currentTarget) //this is the correct plot
{
var opts = myPlots[i].getOptions();
opts.xaxes[0].min = ranges.xaxis.from;
opts.xaxes[0].max = ranges.xaxis.to;
myPlots[i].setupGrid();
myPlots[i].draw();
}
}
});
You'll see above I'm redrawing the plot a little different than in the flot examples. I prefer this method since you don't have to remember the data, you adjust the min/max options and you redraw.
Here's a fiddle putting this all together.

Categories