Synchronized charts example with Highstock crashes with "Cannot read property 'category' of undefined" - highcharts

I want the functionality of the "Synchronized charts" example, but with Highstock. But when trying to accomplish this, I get "highstock.src.js:9991 Uncaught TypeError: Cannot read property 'category' of undefined"
This also holds directly for the example: http://www.highcharts.com/demo/synchronized-charts doesn't work when converted to Highstock: http://jsfiddle.net/9gq47g0w/
(Since StackOverflow demands me to post some code along with the fiddle, here's from Highstock, noting the point where it crashes with **):
/**
* Refresh the tooltip's text and position.
* #param {Object} point
*/
refresh: function (point, mouseEvent) {
...
// shared tooltip, array is sent over
if (shared && !(point.series && point.series.noSharedTooltip)) {
...
textConfig = {
x: ** point[0].category, ** <- here!
y: point[0].y
};
...
}
...
},

Here you can find an example of synchronized highstock charts:
http://jsfiddle.net/vw77cooj/20/
This example is using custom functions for synchronizing extremes and tooltips on charts:
function syncExtremes(e) {
var thisChart = this.chart;
if (e.trigger !== 'syncExtremes') { // Prevent feedback loop
Highcharts.each(Highcharts.charts, function(chart) {
if (chart !== thisChart) {
if (chart.xAxis[0].setExtremes) { // It is null while updating
chart.xAxis[0].setExtremes(e.min, e.max, undefined, false, {
trigger: 'syncExtremes'
});
}
}
});
}
}
$('#container').bind('mousemove touchmove touchstart', function(e) {
Highcharts.each(Highcharts.charts, function(chart) {
event = chart.pointer.normalize(e.originalEvent);
point = chart.series[0].searchPoint(event, true);
if (point) {
point.onMouseOver(); // Show the hover marker
chart.tooltip.refresh(point); // Show the tooltip
chart.xAxis[0].drawCrosshair(event, point); // Show the crosshair
}
});
});
In case of having multiple series on your chart you may change function responsible for synchronizing your tooltip:
function syncTooltip(container, p) {
var i = 0,
j = 0,
data,
series,
points = [];
for (; i < chartSummary.length; i++) {
if (container.id != chartSummary[i].container.id) {
series = chartSummary[i].series
Highcharts.each(series, function(s) {
Highcharts.each(s.data, function(point) {
if (point.x === p && point.series.yAxis.options.index !== 1) {
points.push(point)
}
})
});
chartSummary[i].tooltip.refresh(points);
}
};
}
http://jsfiddle.net/ZArZM/316/

For a nice working example on Highstock, please follow this example with MouseOver and MouseOut:
var onMouseOver = function onMouseOver() {
var x = this.x,
interactedChart = this.series.chart,
points = [],
charts = Highcharts.charts,
each = Highcharts.each;
each(charts, function(chart) {
if (chart !== interactedChart) {
each(chart.series, function(series) {
each(series.data, function(point) {
if (point.x === x && point.series.yAxis.options.index !== 1) {
points.push(point)
}
})
});
each(points, function(p) {
p.setState('hover');
});
chart.tooltip.refresh(points);
}
});
}
http://jsfiddle.net/ska1r5wq/
You can avoid many tooltip issues from examples provided on the web.
Source : https://github.com/highcharts/highcharts/issues/6595

Related

Redraw Highcharts Organization Chart after collapse

I'm using Highcharts to create organization charts that where each node can be collapsed when clicked as in provided example : http://jsfiddle.net/vegaelce/83uktasc/
It works well but it would be better if it is possible to "redraw" the chart once a node is fold/unfold (to optimise the space left and realign the nodes).
I tried without success :
chart.redraw();
Have you any idea how to make this ?
Thanks in advance
You need to set new data to implement the required feature. Nodes are hidden on svg level and they are not ignored in the redraw.
function getData(to) {
const data = [
...
];
if (to) {
const filters = [to];
return data.filter(el => {
const matched = filters.find(filter => filter === el[0]);
if (matched) {
filters.push(el[1]);
}
return !matched;
});
}
return data;
}
Highcharts.chart('container', {
...,
plotOptions: {
series: {
point: {
events: {
click: function() {
if (this.linksFrom.length) {
this.series.setData(getData(this.title || this.name));
} else {
this.series.setData(getData());
}
}
}
}
}
}
});
Live demo: http://jsfiddle.net/BlackLabel/5njf0cwq/
API Reference: https://api.highcharts.com/class-reference/Highcharts.Series#setData

Replacing highcharts.each in my code as it is being deprecated

I have some highcharts which are synchronized and use the following code to synchronise the crosshairs as the mouse is moved:
//catch mousemove event and have all charts' crosshairs move along indicated values on x axis
function syncronizeCrossHairs(chart) {
['mousemove', 'touchmove', 'touchstart'].forEach(function(eventType) {
var container = $(chart.container),
offset = container.offset(),
x;
container[0].addEventListener(eventType,
(function(evt) {
x = evt.clientX - chart.plotLeft - offset.left;
Highcharts.charts.forEach(ch => {
var e = ch.pointer.normalize(evt), // Find coordinates within the chart
points = [];
ch.series.forEach(s => {
var point = s.searchPoint(e, true);
if (point) {
point.setState();
if (s.visible) {
points.push(point)
}
}
})
if (points) {
var number = 0;
Highcharts.each(points, function(p, i) {
if (!p.series.visible) {
points.splice(i - number, 1);
number++;
}
})
if (points.length) {
ch.tooltip.refresh(points); // Show the tooltip
}
}
ch.xAxis[0].drawCrosshair(x, points[0])
})
}))
})
}
I am now getting the following console message:
Highcharts error #32: www.highcharts.com/errors/32/?Highcharts.each=Array.forEach
Highcharts.each: Array.forEach
Can someone advise how I can replace the Highcharts.each command in my code?
Thanks
I think that changing:
Highcharts.each(points, function(p, i) {
if (!p.series.visible) {
points.splice(i - number, 1);
number++;
}
})
to:
points.forEach((p, i) => {
if (!p.series.visible) {
points.splice(i - number, 1);
number++;
}
})
Should be enough. Please test it in your application and let me know if it helped.
Wherever you have a Highcharts.each just change it to a for loop. Here's a before and after example from my project with synced charts:
Before (Using the deprecated Highcharts.each way):
Highcharts.each(Highcharts.charts, function (chart) {
if (chart && chart !== thisChart) {
if (chart.xAxis[0].setExtremes) {
chart.xAxis[0].setExtremes(e.min, e.max, undefined, false, {
trigger: "syncExtremes"
});
}
}
});
And after, just simply changing it to a for loop:
for (let i = 0; i < Highcharts.charts.length; i++) {
let chart = Highcharts.charts[i];
if (chart && chart !== thisChart) {
if (chart.xAxis[0].setExtremes) {
chart.xAxis[0].setExtremes(e.min, e.max, undefined, false, {
trigger: "syncExtremes"
});
}
}
}

I have synchronized charts with synch’d zooming. I would like to zoomout in steps (not just back to 1:1). Does anyone have a working example of this?

I am currently synchronizing my charts as below:
$('#container').bind('mousemove touchmove', function (e) {
var chart,
point,
points,
i;
for (i = 0; i < Highcharts.charts.length; i++) {
chart = Highcharts.charts[i];
e = chart.pointer.normalize(e); // Find coordinates within the chart
points = [];
Highcharts.each(chart.series, function(series){
point = series.searchPoint(e, true);
if (point) {
points.push(point);
point.onMouseOver(); // Show the hover marker
}
});
if (points.length > 0) {
chart.tooltip.refresh(points); // Show the tooltip
chart.xAxis[0].drawCrosshair(e, points[0]); // Show the crosshair
}
}
});
// ==================================================================================
// * Override the reset function, we don't need to hide the tooltips and crosshairs.
// * Synchronize zooming through the setExtremes event handler.
Highcharts.Pointer.prototype.reset = function () {};
// ====================================================================
function syncExtremes(e) {
var thisChart = this.chart;
if (e.trigger !== 'syncExtremes') { // Prevent feedback loop
Highcharts.each(Highcharts.charts, function (chart) {
if (chart !== thisChart) {
if (chart.xAxis[0].setExtremes) { // It is null while updating
chart.xAxis[0].setExtremes(e.min, e.max, undefined, false, { trigger: 'syncExtremes' });
}
}
});
}
}
You can calculate intermediate extremes and set them by setExtremes method, for example:
document.getElementById('zoomOut').addEventListener('click', function() {
var chart = Highcharts.charts[0],
yAxis = chart.yAxis[0],
xAxis = chart.xAxis[0],
yDistance = (yAxis.max - yAxis.min) / 2,
xDistance = (xAxis.max - xAxis.min) / 2;
yAxis.setExtremes(yAxis.min - yDistance, yAxis.max + yDistance);
xAxis.setExtremes(xAxis.min - xDistance, xAxis.max + xDistance);
});
Live demo: http://jsfiddle.net/BlackLabel/9vxgpn4z/
API Reference: https://api.highcharts.com/class-reference/Highcharts.Axis#setExtremes

Highcharts large data set clustering

I have tens of thousands (possibly hundreds of thousands) of points that I need plotted with Highcharts. Is there a way where I can cluster the data on the server, so it will show less than 1000 points, but as you zoom in it will make AJAX calls to the server to get the data for that zoomed region (it would probably run through the same cluster algorithm). How would this interface with the Highcharts API?
There is a highstock demo that does this http://www.highcharts.com/stock/demo/lazy-loading.
But you can do the same thing with highcharts http://jsfiddle.net/RHkgr/
The important bit is the afterSetExtremes function
...
xAxis : {
events : {
afterSetExtremes : afterSetExtremes
},
...
/**
* Load new data depending on the selected min and max
*/
function afterSetExtremes(e) {
var url,
currentExtremes = this.getExtremes(),
range = e.max - e.min;
var chart = $('#container').highcharts();
chart.showLoading('Loading data from server...');
$.getJSON('http://www.highcharts.com/samples/data/from-sql.php?start='+ Math.round(e.min) +
'&end='+ Math.round(e.max) +'&callback=?', function(data) {
chart.series[0].setData(data);
chart.hideLoading();
});
}
Here is an improvement for Barbara's answer,
It registers to the setExtremes event,
to know if this is a reset zoom event.
If it is - it gets the entire dataset,
thus allowing reset zoom to work correctly.
It also allows zooming in both x and y.
http://jsfiddle.net/DktpS/8/
var isReset = false;
...
xAxis: {
events: {
afterSetExtremes : afterSetExtremes,
setExtremes: function (e) {
if (e.max == null || e.min == null) {
isReset = true;
}
else
{
isReset = false;
}
}
},
minRange: 3600 * 1000 // one hour
},
series: [{
data: data,
dataGrouping: {
enabled: false
}
}]
});
});
});
/**
* Load new data depending on the selected min and max
*/
function afterSetExtremes(e) {
var url,
currentExtremes = this.getExtremes(),
range = e.max - e.min;
var chart = $('#container').highcharts();
var min = 0;
var max = 1.35e12;
if(!isReset)
{
min = e.min;
max = e.max;
}
chart.showLoading('Loading data from server...');
$.getJSON('http://www.highcharts.com/samples/data/from-sql.php?start=' + Math.round(min) +
'&end=' + Math.round(max) + '&callback=?', function (data) {
chart.series[0].setData(data);
chart.hideLoading();
});
}
In case when you will not have a limit of points, you can increase turboThreshold paramter.

Showing Tool-tip & Cross-hair on two independent graphs from a single hover event?

Good day,
We are trying to develop a report which contains two charts. Each chart shows different metrics on the same timescale (unix timestamp). The aim is to show the tooltip & cross hair on both charts regardless of which you hover over.
In my head I would like to get the xAxis timestamp (time) from the tooltip hover event on chart A. I would then fetch the associated series on chart B and trigger the tooltip refresh event.
As it stands, it looks like highcharts only accepts points.
I've added a mouseOver/Out event to both charts. This obtains the hoverPoints
plotOptions: {
series: {
point: {
events: {
mouseOver: function () {
$el.trigger('tooltip-open', {"$sourceEl": $el[0], "point": this});
},
mouseOut: function () {
$el.trigger('tooltip-close', {"$sourceEl": $el[0], "point": this});
}
}
}
}
}
// Where $el is the highcharts container
// ..and tool-tip-XXXXX is a custom jQuery event.
My temporary work around is:
$el.bind('tooltip-open', function (e, data) {
var $el = $(this);
if (data.$sourceEl.id == $el.id) {
return;
}
var chart = $el.highcharts();
var points = $(chart.series).map(function () {
return this.points
}).get();
var pointsForTimestamp = $.grep(points, function (p) {
return p.x == data.point.x;
});
$(pointsForTimestamp).each(function () {
this.setState('hover');
});
chart.tooltip.refresh(pointsForTimestamp);
});
$el.bind('tooltip-close', function (e, data) {
var $el = $(this);
if (data.$sourceEl.id == this.id) {
return;
}
var chart = $el.highcharts();
chart.tooltip.hide();
});
var linkGraphs = function ($elA, $elB) {
function isEventHandled(data){
data.count = data.count || 0;
if(data.count > 0){
return true
}else{
data.count++;
return false;
}
}
function bindToOpen($elA, $elB) {
$elA.bind('tooltip-open', function (e, data) {
if(!isEventHandled(data)){
$elB.trigger('tooltip-open', data);
}
});
}
function bindToClose($elA, $elB) {
$elA.bind('tooltip-close', function (e, data) {
if(!isEventHandled(data)){
$elB.trigger('tooltip-close', data);
}
});
}
bindToOpen($elA, $elB);
bindToOpen($elB, $elA);
bindToClose($elA, $elB);
bindToClose($elB, $elA);
};

Resources