I want to add a series to a highchart scatterplot where I am naming each point in the series. I create a chart in the following way:
var chart; // globally available
makeCharts = function(){
chart = new Highcharts.Chart({
chart: {
renderTo: 'container1',
type: 'scatter'
},
series: [{
name: 'a',
data: [{
'id': 'point1',
'x': 1,
'y': 2
}, {
'id': 'point2',
'x': 2,
'y': 5
}]
}]
});
}
I would like to be able to update the points on the chart using something like:
chart.series[0].setData([{id:['point3', 'point4', 'point5'], y:[0,1,2], x:[1,2,3]}])
but this is not correct. Is it possible to update a chart using this approach where each point has an ID?
EDIT:
Just to clarify, I would like to be able to pass the arrays directly, rather than adding the data point by point using addPoint(). I could loop through an array and use addPoint() doing something like this:
id:['point3', 'point4', 'point5'];
y:[0,1,2];
x:[1,2,3];
for (i=0; i<x.length; i++)
{
chart.series[0].addPoint({
x: x[[i],
y: y[i],
id: id[i]
});
}
However, this is very slow. It's much quicker to add data using the following approach:
chart.series[0].setData([[1,0],[2,1],[3,2]]);
I have found that I can add data like this:
chart.series[0].setData([[1,0, 'point3'],[2,1, 'point4'],[3,2, 'point5']]);
but then the only way that I can access the id when the point is selected, is through this.point.config[2]. With the following approach I am unable to use chart.get('pointID') to identify a point as I did not set the ID. I want to be able to identify the point using just the ID.
Well broadly speaking there are two ways in which you can modify the chart data dynamically
Series.setData() Use this approach when you want to completely replace the existing data with some new data
Series.addPoint() Use this approach when you want to add a subset of the points dynamically. This method is not just for adding one point at a time, if you read the documentation carefully again you will find that this method takes a boolean redraw argument, and the argument detail is as following
redraw: Boolean
Defaults to true. Whether to redraw the chart after
the point is added. When adding more than one point, it is highly
recommended that the redraw option beset to false, and instead
chart.redraw() is explicitly called after the adding of points is
finished.
In your case, since you want to add a few points dynamically, but retaining the existing points, you should go with approach 2. But you need to use it inside a loop, with the redraw being set to false (hence solving the problem of being slow) and then after the loop, call the redraw method explicitly
Code
var id = ['point3', 'point4', 'point5'],
y = [0, 1, 2],
x = [1, 2, 3];
for (var i = 0; i < x.length; i++) {
chart.series[0].addPoint({
x: x[i],
y: y[i],
id: id[i]
},false);
}
chart.redraw();
Adding multiple points dynamically | Highcharts and Highstock # jsFiddle
Try using series.addPoint.
chart.series[0].addPoint({
x: 0,
y: 0,
id: 'anything'
});
But if you need to set data for series, use
chart.series[0].setData([{
x: 0,
y: 0,
id: 'anything'
},{
x: 2,
y: 2,
id: 'another'
}]);
As soon as you can pass your data like this:
chart.series[0].setData([[1,0, 'point3'],[2,1, 'point4'],[3,2, 'point5']]);
(as you stated in question), I can suggest you to use a little hack.
We'll need to add another statement to method applyOptions of Highcharts.Point prototype.
if (typeof options[0] === 'number' && options[2] && typeof options[2] === 'string') this.id = options[2];
Here you can see it in action.
Related
I am using a WebSocket connection to update a candlestick chart with live data.
Creating the initial candlestick chart is relatively easy:
var candleDiv = document.getElementById('candle-chart');
var data = {
x: x, //Each of these is a single dimension array of the same length
open: open,
close: close,
high: high,
low: low,
type: 'candlestick',
};
var layout = {
datarevision: candleCount,
dragmode: 'zoom',
showlegend: false,
xaxis: {
type: 'date',
range: [x[x.length - 26], x[x.length - 1]], //Only show the last 25 entries so it's not zoomed out too far.
rangeslider: {
visible: false
},
yaxis: {
autorange: true,
}
}
}
data.xaxis = 'x';
data.yaxis = 'y';
data = [data];
Plotly.plot(candleDiv, data, layout);
However, the documentation for the restyle method doesn't talk much to the update of data. More about how the data is displayed. After much tinkering, I found a reasonable workaround of updating the data variable directly:
candleDiv.data[0].open[candleDiv.data[0].open.length - 1] = updatedOpenValue;
candleDiv.data[0].close[candleDiv.data[0].close.length - 1] = updatedCloseValue;
candleDiv.data[0].high[candleDiv.data[0].high.length - 1] = updatedHighValue;
candleDiv.data[0].low[candleDiv.data[0].low.length - 1] = updatedLowValue;
Plotly.restyle(candleDiv, 'data[0]', candleDiv.data[0], [0]);
This works, except that it appears to draw the new candle on the old candle. This becomes particularly distracting when the stick changes from a green (increasing) stick to a red (decreasing) stick.
Is there a correct syntax to achieve what I am attempting to do such that I don't get display issues?
I checked out this link from this post but I couldn't get the method used to work in the context of a candlestick chart.
You may want to look at the Plotly.react method instead of Plotly.restyle: https://plot.ly/javascript/plotlyjs-function-reference/#plotlyreact
I know that I can set depth of all bars in Highcharts using depth property in column property of plotOptions likes the following code:
plotOptions: {
column : {
depth: 30
}
}
Or
# in R
hc_plotOptions(column = list(
depth = 30
)
The questions is how can I set different depth for each bar group in a bar chart (not one depth for all)? Solution can be in R (Highcharter) or in JS?
In core code the depth property is always taken from the series object options. Every group consists of the points with the same x values.
These 2 solutions came to my mind:
1. Modify the core code so that depth values are taken from points' configuration instead:
(function(H) {
(...)
H.seriesTypes.column.prototype.translate3dShapes = function() {
(...)
point.shapeType = 'cuboid';
shapeArgs.z = z;
shapeArgs.depth = point.options.depth; // changed from: shapeArgs.depth = depth;
shapeArgs.insidePlotArea = true;
(...)
};
})(Highcharts);
Series options:
series: [{
data: [{y: 5, depth: 50}, {y: 2, depth: 100}]
}, {
data: [{y: 13, depth: 50}, {y: 1, depth: 100}]
}]
Live demo: http://jsfiddle.net/kkulig/3pkon2Lp/
Docs page about overwriting core functions: https://www.highcharts.com/docs/extending-highcharts/extending-highcharts
2. Create a separate series for every point.
depth property can be applied to a series so the modification of the core wouldn't be necessary. Every series is shown in legend by default so series will have to be properly connected using linkedTo property (so that the user doesn't see as many series as points).
Points can be modified before passing them to the chart constructor or dynamically handled in chart.events.load.
Live demo: http://jsfiddle.net/kkulig/37sot3am/
load: function() {
var chart = this,
newSeries = [],
merge = Highcharts.merge,
depths = [10, 100]; // depth values for subsequent x values
for (var i = chart.series.length - 1; i >= 0; i--) {
var s = chart.series[i];
s.data.forEach(function(p, i) {
// merge point options
var pointOptions = [merge(p.options, {
// x value doesn't have to appear in options so it needs to be added manually
x: p.x
})];
// merge series options
var options = merge(s.options, {
data: pointOptions,
depth: depths[i]
});
// mimic original series structure in the legend
if (i) {
options.linkedTo = ":previous"
}
newSeries.push(options);
});
s.remove(true);
}
newSeries.forEach((s) => chart.addSeries(s));
}
API reference:
https://api.highcharts.com/highcharts/plotOptions.series.linkedTo
I am making a diagram that should show states in a timeline like diagram. I figured that the best fit for it would be horizontal bar diagram, I added data like this:
series: [{
data: [?],
name: 'state1'
}, {
data: [?],
name: 'state2'
}, {
data: [?],
name: 'state3'
}, {
data: [?],
name: 'state1'
}, {
....
}, {
data: [?],
name: 'state1'
}]
Here's what I'v got:
The chart looks like what I need, but I need to somehow group legends, for example this case there should be only 3 legends: state1, state2, state3.
How can I achieve something like this?
I would suggest a different approach, using the column range series type.
This way you can do this with only three series, with a data point for each time slot, rather than a series for each time slot. This saves from having to manipulate the legend or fake other series level actions.
This is an example I did for an earlier question that demonstrates the method (with only two states):
http://jsfiddle.net/jlbriggs/o9ck2zLn/0/
can easily be adapted to use a datetime axis as well, which it seems you might be going for.
Basing on the answer of #StAlex, I would recommend a similar solution, but without the ECMAScript 5 specific forEach:
var legend = {};
for (var i = 0; i < series.length; i++) {
var bar = series[i];
if (!legend[bar.name]) {
legend[bar.name] = 0;
}
legend[bar.name] += bar.data;
}
I assumed that data is a number. If this is not so, add a comment.
var legends = {};
series.forEach(function(bar){
legends[bar.name] = bar.data;
});
Suppose, I have the following piece of code:
var brd2 = JXG.JSXGraph.initBoard('box2', {boundingbox: [-8.75, 2.5, 8.75, -2.5]});
var ax2 = brd2.create('axis', [[0,0],[1,0]]);
How can I change second point of axis?
Something like ax2.setSecondPoint([2,0])?
In general, how can I set property of any element?
Thank you.
Axis has two properties which names are self-explanatory: point1 and point2.
You can use setPosition method on any of them, e.g.
ax2.point2.setPosition(JXG.COORDS_BY_USER,[2,0])
Now there is one catch: you will not see this change on the chart unless you set needsRegularUpdate property of the axis object to true. Finally, to refresh the chart you should execute fullUpdate() method on the board variable. The whole looks like this:
var brd2 = JXG.JSXGraph.initBoard('box2', {boundingbox: [-8.75, 2.5, 8.75, -2.5]});
var ax2 = brd2.create('axis', [[0,0],[1,0]],{needsRegularUpdate:true});
ax2.point2.setPosition(JXG.COORDS_BY_USER,[2,0]);
brd2.fullUpdate();
References:
http://jsxgraph.uni-bayreuth.de/docs/symbols/JXG.Point.html#setPosition
http://jsxgraph.uni-bayreuth.de/wiki/index.php/Options (search for "special axis options")
Now to change properties like fixed, visible, etc. you should use setAttribute method (setProperty is deprecated). Example:
// Set property directly on creation of an element using the attributes object parameter
var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-1, 5, 5, 1]};
var p = board.create('point', [2, 2], {visible: false});
// Now make this point visible and fixed:
p.setAttribute({
fixed: true,
visible: true
});
Source:
http://jsxgraph.uni-bayreuth.de/docs/symbols/JXG.GeometryElement.html#setAttribute
Last but not least a simple formula:
a + b = c
where:
a = using JavaScript debugging tools in browsers to investigate object properties
b = checking documentation for products you use
c= success :)
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.