Slow Highcharts performance with thousands of series - performance

I am using Highcharts in my project and sometimes I need to display 2000+ series on the chart.
The time of adding series and redrawing a chart is really high. Is there any way to make it to display faster?
What I have already done:
Series added without redrawing, then I call chart.redraw()
Disabled animation
Disabled dataGrouping
Here are the results (adding 2000 and 4000 series to an empty chart):
Chrome 44.0.2403.157 m:
Total series count: 2000
Series added: 5775ms
Chart redrawed: 11351ms
Total series count: 4000
Series added: 36497ms
Chart redrawed: 51985ms
Firefox 40.0.2
Total series count: 2000
Series added: 1769ms
Chart redrawed: 7405ms
Total series count: 4000
Series added: 6153ms
Chart redrawed: 23464ms
IE 11
Total series count: 2000
Series added: 14547ms
Chart redrawed: 50153ms
Total series count: 4000
Series added: 66558ms
Chart redrawed: 229382ms (229 seconds!!)
Could you please tell me, is there any way to increase the speed?
Thank you.
Here is the code jsfiddle
$(function () {
var addManySeries = function(count)
{
var seriesToAdd = count;
var d = [];
var temp = [];
for (var i = 0; i < seriesToAdd; i++) {
temp = [];
for (var j = 0; j < 10; j++) {
temp.push(Math.random() * 1000);
}
d.push({data: temp, name: 'Series ' + i.toString()});
}
$('#log').append("<br/>Total series count: " + (chart.series.length + d.length).toString());
var s = Date.now();
for (var i = 0; i < seriesToAdd; i++) {
chart.addSeries(d[i], false);
}
$('#log').append("<br/>Series added: " + (Date.now() -s).toString() + "ms");
chart.redraw();
console.log((Date.now() - s).toString());
$('#log').append("<br/>Chart redrawed: " + (Date.now() - s).toString()+ "ms");
};
var chart_options = {
chart:{
type:'line',
renderTo: 'container',
animation: false
},
plotOptions: {
line: {
animation: false,
shadow: false,
marker:{
enabled: false
}
}
},
tooltip:{
animation : false
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle'
},
series: {
dataGrouping: false
}
};
chart = new Highcharts.Chart(chart_options);
var chart_options = {
chart:{
type:'line',
renderTo: 'container',
animation: false
},
plotOptions: {
line: {
animation: false,
shadow: false,
marker:{
enabled: false
}
}
},
tooltip:{
animation : false
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle'
},
series: {
dataGrouping: false
}
};
chart = new Highcharts.Chart(chart_options);
$('#add2000Series').click(function() {
addManySeries(2000);
});
$('#add4000Series').click(function() {
addManySeries(4000);
});
});

A few things:
1) 2000 series x 10 points is 20000 elements in SVG, try to test pushing native element and measure
2) dataGrouping is available only in Highstock and should be defined in plotOptions not series
3) Why you use a addSeries multiple time, instead of prepare a array of series objects in native javascript?
4) You can use a boost module

Related

Odd animation with multiple series

I was trying to adapt the spline animation for time series (https://www.highcharts.com/demo/dynamic-update) for multiple series.
I modified the example here https://jsfiddle.net/2wj3fquL/
Highcharts.chart('container', {
chart: {
type: 'spline',
animation: Highcharts.svg, // don't animate in old IE
marginRight: 10,
events: {
load: function () {
// set up the updating of the chart each second
var series = this.series;
setInterval(function () {
var x = (new Date()).getTime(), // current time
y = Math.random();
series[0].addPoint([x, y], true, true);
y = Math.random();
series[1].addPoint([x, y], true, true);
}, 1000);
}
}
},
time: {
useUTC: false
},
title: {
text: 'Live random data'
},
accessibility: {
announceNewData: {
enabled: true,
minAnnounceInterval: 15000,
announcementFormatter: function (allSeries, newSeries, newPoint) {
if (newPoint) {
return 'New point added. Value: ' + newPoint.y;
}
return false;
}
}
},
xAxis: {
type: 'datetime',
tickPixelInterval: 150
},
yAxis: {
title: {
text: 'Value'
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
},
tooltip: {
headerFormat: '<b>{series.name}</b><br/>',
pointFormat: '{point.x:%Y-%m-%d %H:%M:%S}<br/>{point.y:.2f}'
},
legend: {
enabled: false
},
exporting: {
enabled: false
},
series: [{
name: 'Random data',
data: (function () {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}, {
name: 'other data',
data: (function () {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}]
});
Unfortunately the effect is odd since one series moves smooth while the other doesn't ...
How could I solve this problem?
Thanks
That is because the chart is redrawn twice. Disable redraw in the first addPoint method call.
events: {
load: function() {
...
setInterval(function() {
...
series[0].addPoint([x, y], false, true);
series[1].addPoint([x, y], true, true);
}, 1000);
}
}
Live demo: https://jsfiddle.net/BlackLabel/0n2yw57m/
API Reference: https://api.highcharts.com/class-reference/Highcharts.Series#addPoint

How to integrate KendoUI chart with SignalR

i want to create a real time chart, using Kendo ui chart and signalr. I see this example, but has no code. So i try alone.
A little demonstration of my code:
At first I created a kendo chart
function queueActivityChart() {
$("#queueActivityChart").kendoChart({
legend: {
visible: true
},
seriesDefaults: {
labels: {
visible: true,
format: "{0}",
background: "transparent"
}
},
series: [{
type: "line",
field: "Incoming",
categoryField: "DateTime",
}],
valueAxis: {
labels: {
format: "{0}"
},
line: {
visible: false
}
},
categoryAxis: {
labels:
{
rotation: -90,
dateFormats:
{
seconds: "ss",
minutes: "HH:mm:ss",
hours: "HH:mm",
days: "dd/MM",
months: "MMM 'yy",
years: "yyyy"
}
}, type: "Date", field: "DateTime", baseUnit: "seconds"
}
});
var chart = $("#queueActivityChart").data("kendoChart");
chart.options.transitions = false;
}
$(document).ready(queueActivityChart);
$(document).bind("kendo:skinChange", queueActivityChart);
Then I have this part of code, that get from server data
$scope.signalRData = [];
$scope.signalR.on('receiveCounters', function (data) {
$scope.queueData = data;
for (var i = 0; i < data.length; i++) {
$scope.signalRData.push(data[i]);
}
while ($scope.signalRData.length > 12) {
$scope.signalRData.splice(0, 1);
}
$("#queueActivityChart").data("kendoChart").setDataSource(
new kendo.data.DataSource({
group: {
field: "Name"
},
data: $scope.signalRData
}));
});
This works! And I get a picture of the latest updated items.
But the problem is that this chart is like to put one picture in front of other. I mean that this is the first time that load Data Source; that creates a chart of my data, the second time my data has changed, some values are still in my array some others has move out, the third too.
It seems like it puts a picture of my current data in front of the
previous data. It's not smoothie and cannot use chart's legend
property because I initialize my Data Source everytime.
Can someone help me how can create a smoothie kendo chart with real time data like the kendo official example? Also can somehow to add scroller to bottom?
I looked at the code for the benchmark and I think you may be missing in your chart which is renderAs: "canvas"
Also, in the example, the data is kept locally (saved) and then moved so it creates that "smooth" effect you may be talking about.
Here is the code that you can be of interest:
function step() {
addPoint();
$("#chart").data("kendoChart").refresh();
frames++;
if (playing) {
kendo.animationFrame(step);
}
}
function addPoint() {
var stockData,
change,
lastValue;
// Shift existing categories to the left and add the next date at the end
lastDate = new Date(lastDate.getTime() + TICKS_PER_DAY);
categoryList.push((lastDate.getMonth() + 1) + "/" + (lastDate.getDay() + 1));
if (categoryList.length > POINTS) {
categoryList.shift();
}
for (var i = 0; i < stocks.length; i++) {
stockData = stocks[i];
change = (Math.random() > 0.5 ? 1 : - 1) * Math.random() * 10;
lastValue = stockData[stockData.length - 1] || Math.random() * 10;
// Add a new pseudo-random data point
stockData.push(Math.min((i + 1) * 20, Math.max((i + 1) * 10, lastValue + change)));
// Shift the data points of each series to the left
if (stockData.length > POINTS) {
stockData.shift();
}
}
}
Check out the source code of your example for the full source code and use the dojo to test our their code and play around with it easily

ZoomRange Highstock works not correct?

I made a Highstock diagramm and got aproblem with zooming on the yAxis.
I have a Button and 2 textfield to get the wanted min/max values for the axis. With min:0, max: 100 it works well. With min:0, max:80 it doesn't (max will still be 100 in the Diagramm).
If I use the mouse for zooming it works well (even a min of: 3.7 and a max of 3.894 is possible). But using the mouse is not an Option, because in the later Diagramm there will be 3 yAxes with individual zoom.
$(function () {
var seriesOptions = [],
seriesCounter = 0,
names = ['MSFT', 'AAPL', 'GOOG'];
/**
* Create the chart when all data is loaded
* #returns {undefined}
*/
function createChart() {
$('#container').highcharts('StockChart', {
rangeSelector: {
selected: 4
},
chart:{
zoomType: 'xy'
},
yAxis: [
{
labels: {
format: '{value}',
},
height: '100%',
opposite: false,
plotLines: [{
value: 0,
width: 2,
color: 'silver'
}]
},
],
plotOptions: {
series: {
compare: 'percent'
}
},
tooltip: {
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
valueDecimals: 2
},
series: seriesOptions
},
function(chart){
$('#btn').click(function(){
var min = temp_min.value,
max = temp_max.value;
chart.yAxis[0].setExtremes((min),(max));
});
});
}
$.each(names, function (i, name) {
$.getJSON('https://www.highcharts.com/samples/data/jsonp.php?filename=' + name.toLowerCase() + '-c.json&callback=?', function (data) {
if(seriesCounter==0){
seriesOptions[i] = {
name: name,
data: data,
yAxis: 0
};
} else {
seriesOptions[i] = {
name: name,
data: data,
yAxis: 0
};
}
// As we're loading the data asynchronously, we don't know what order it will arrive. So
// we keep a counter and create the chart when all the data is loaded.
seriesCounter += 1;
if (seriesCounter === names.length) {
createChart();
}
});
});
});
JSFiddle
Another Question: Is it possible to set up a scrollbar for the yAxis as well?
Thanks for your help, Patrick
This is related with fact that tickInterval is not regular, so is rounded to value (like 100). The solution is using tickPositioner which calculates ticks, based on extremes which you define.
tickPositioner: function (min,max) {
var positions = [],
tick = Math.floor(min),
increment = Math.ceil((max - min) / 5);
for (tick; tick - increment <= max; tick += increment) {
positions.push(tick);
}
return positions;
},
http://jsfiddle.net/6s11kcwd/
The scrollbar is supported only for xAxis.

Kendo treelist - trying to set a column template

I'm working with a Kendo treelist widget, and disappointed to see there's no rowTemplate option as there is on the Kendo grid.
I see a columnTemplate option (i.e. http://docs.telerik.com/kendo-ui/api/javascript/ui/treelist#configuration-columns.template ), but this will affect the entire column.
However, I need to drill into each cell value and set a css color property based on a ratio ( i.e. If value/benchmark < .2, assign <span style='color:red;'> , but my color value is dynamic.
There's a dataBound: and dataBinding: event on the treelist, but I'm still trying to figure out how to intercept each cell value and set the color once I've done my calculation.
var treeOptions = {
dataSource: ds,
columns: colDefs,
selectable: true,
scrollable: true,
resizable: true,
reorderable: true,
height: 320,
change: function (e) {
// push selected dataItem
var selectedRow = this.select();
var row = this.dataItem(selectedRow);
},
dataBound: function (e) {
console.log("dataBinding");
var ds = e.sender.dataSource.data();
var rows = e.sender.table.find("tr");
}
};
and this is where I'm building out the `colDefs' object (column definitions):
function parseHeatMapColumns(data, dimId) {
// Creates the Column Headers of the heatmap treelist.
// typeId=0 is 1st Dimension; typeId=1 is 2nd Dimension
var column = [];
column.push({
"field": "field0",
"title": "Dimension",
headerAttributes: { style: "font-weight:" + 'bold' + ";" },
attributes : { style: "font-weight: bold;" }
});
var colIdx = 1; // start at column 1 to build col headers for the 2nd dimension grouping
_.each(data, function (item) {
if (item.typeId == dimId) {
// Dimension values are duplicated, so push unique values (i.e. trade types may have dupes, whereas a BkgLocation may not).
var found = _.find(column, { field0: item.field0 });
if (found == undefined) {
column.push({
field: "field2",
title: item.field0,
headerAttributes: {
style: "font-weight:" + 'bold'
}
,template: "<span style='color:red;'>#: field2 #</span>"
});
colIdx++;
}
}
});
return column;
}
**** UPDATE ****
In order to embed some logic within the template :
function configureHeatMapColumnDefs(jsonData, cols, model) {
var colDef = '';
var dimId = 0;
var colorProp;
var columns = kendoGridService.parseHeatMapColumns(jsonData, dimId);
// iterate columns and set color property; NB: columns[0] is the left-most "Dimension" column, so we start from i=1.
for (var i = 1; i <= columns.length-1; i++) {
columns[i]['template'] = function (data) {
var color = 'black';
if (data.field2 < 1000) {
color = 'red';
}
else if (data.field2 < 5000) {
color = 'green';
}
return "<span style='color:" + color + ";'>" + data.field2 + "</span>";
};
}
return columns;
}
Advice is appreciated.
Thanks,
Bob
In the databound event you can iterate through the rows. For each row you can get the dataItem associated with it using the dataitem() method (http://docs.telerik.com/kendo-ui/api/javascript/ui/treelist#methods-dataItem)
Once you have the dataitem, calculate your ration and if the row meets the criteria for color, change the cell DOM element:
dataBound: function (e) {
var that = e.sender;
var rows = e.sender.table.find("tr");
rows.each(function(idx, row){
var dataItem = that.dataItem(row);
var ageCell = $(row).find("td").eq(2);
if (dataItem.Age > 30) {
//mark in red
var ageText = ageCell.text();
ageCell.html('<span style="color:red;">' + ageText + '</span>');
}
}
DEMO
UPDATE: you can also do this with a template:
$("#treelist").kendoTreeList({
dataSource: dataSource,
height: 540,
selectable: true,
columns: [
{ field: "Position"},
{ field: "Name" },
{ field: "Age",
template: "# if ( data.Age > 30 ) { #<span style='color:red;'> #= data.Age # </span> #}else{# #= data.Age # #}#"
}
],
});
DEMO

Pan and zoom in kendo boxplot chart

I am trying to use the pan and zoom functionality in kendo box plot chart, can this be achieved for box plot chart.
http://demos.telerik.com/kendo-ui/bar-charts/pan-and-zoom
You can apply the exact same methods to the boxplot.
DEMO
CODE:
// Minimum/maximum number of visible items
var MIN_SIZE = 6;
var MAX_SIZE = 18;
// Optional sort expression
// var SORT = { field: "val", dir: "asc" };
var SORT = {};
// Minimum distance in px to start dragging
var DRAG_THR = 50;
// State variables
var viewStart = 0;
var viewSize = MIN_SIZE;
var newStart;
// Drag handler
function onDrag(e) {
var chart = e.sender;
var ds = chart.dataSource;
var delta = Math.round(e.originalEvent.x.initialDelta / DRAG_THR);
if (delta != 0) {
newStart = Math.max(0, viewStart - delta);
newStart = Math.min(data.length - viewSize, newStart);
ds.query({
skip: newStart,
page: 0,
pageSize: viewSize,
sort: SORT
});
}
}
function onDragEnd() {
viewStart = newStart;
}
// Zoom handler
function onZoom(e) {
var chart = e.sender;
var ds = chart.dataSource;
viewSize = Math.min(Math.max(viewSize + e.delta, MIN_SIZE), MAX_SIZE);
ds.query({
skip: viewStart,
page: 0,
pageSize: viewSize,
sort: SORT
});
// Prevent document scrolling
e.originalEvent.preventDefault();
}
$("#chart").kendoChart({
dataSource: {
data: data,
pageSize: viewSize,
page: 0,
sort: { }
},
title: {
text: "Ozone Concentration (ppm)"
},
legend: {
visible: false
},
series: [{
type: "boxPlot",
lowerField: "lower",
q1Field: "q1",
medianField: "median",
q3Field: "q3",
upperField: "upper",
meanField: "mean",
outliersField: "outliers"
}],
categoryAxis: {
field: "year",
majorGridLines: {
visible: false
}
},
transitions: false,
drag: onDrag,
dragEnd: onDragEnd,
zoom: onZoom
});

Resources