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

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);
};

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

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

Need help in simplifying the code to synchronise tooltips and crosshairs for Highcharts,

I have created a series of synchronised charts, the code to link the crosshair and tooltip is rather complicated:
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;
//remove old plot line and draw new plot line (crosshair) for this chart
var xAxis1 = chart1.xAxis[0],
points = [],
points1 = [],
points2 = [],
points3 = [],
e = chart1.pointer.normalize(evt); // Find coordinates within the chart
chart1.series.forEach(s => {
var point = s.searchPoint(e, true)
if (point) {
point.setState();
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) {
chart1.tooltip.refresh(points); // Show the tooltip
}
}
xAxis1.drawCrosshair(x, points[0])
/*----- second chart ------*/
var xAxis2 = chart2.xAxis[0];
chart2.series.forEach(s => {
var point = s.searchPoint(e, true)
if (point) {
point.setState();
points1.push(point)
}
})
if (points1[0]) {
var number = 0;
Highcharts.each(points1, function(p, i) {
if (!p.series.visible) {
points1.splice(i - number, 1);
number++;
}
})
if (points1.length) {
chart2.tooltip.refresh(points1); // Show the tooltip
}
}
xAxis2.drawCrosshair(x, points1[0])
/*----- third chart ------*/
var xAxis3 = chart3.xAxis[0];
chart3.series.forEach(s => {
var point = s.searchPoint(e, true)
if (point) {
point.setState();
points2.push(point)
}
console.log(points2)
})
if (points2[0]) {
var number = 0;
Highcharts.each(points1, function(p, i) {
if (!p.series.visible) {
points2.splice(i - number, 1);
number++;
}
})
if (points2.length) {
chart3.tooltip.refresh(points2); // Show the tooltip
}
}
xAxis3.drawCrosshair(x, points2[0])
/* ----- fourth chart ------ */
var xAxis4 = chart4.xAxis[0];
chart4.series.forEach(s => {
var point = s.searchPoint(e, true)
if (point) {
point.setState();
points3.push(point)
}
})
if (points3[0]) {
var number = 0;
Highcharts.each(points3, function(p, i) {
if (!p.series.visible) {
points3.splice(i - number, 1);
number++;
}
})
if (points3.length) {
chart4.tooltip.refresh(points3); // Show the tooltip
}
}
xAxis4.drawCrosshair(x, points3[0])
}))
})
}
How ever I have found a better example here: http://jsfiddle.net/mushigh/a3kjrz6u/
$('#container').bind('mousemove touchmove touchstart', function(e) {
var chart,
point,
i,
event;
for (i = 0; i < Highcharts.charts.length; i = i + 1) {
chart = Highcharts.charts[i];
event = chart.pointer.normalize(e.originalEvent); // Find coordinates within the chart
point = chart.series[0].searchPoint(event, true); // Get the hovered point
if (point) {
point.onMouseOver(); // Show the hover marker
chart.tooltip.refresh(point); // Show the tooltip
chart.xAxis[0].drawCrosshair(event, point); // Show the crosshair
}
}
});
/**
* Override the reset function, we don't need to hide the tooltips and crosshairs.
*/
Highcharts.Pointer.prototype.reset = function() {
return undefined;
};
How do I adapt my code here: https://jsfiddle.net/ashenshugar/716jx4n9/
To use the simplified code in my example
I don't think that the demo which you have found is a good approach to your requirements.
The demo needs to the specific data structure, like: https://github.com/highcharts/highcharts/blob/master/samples/data/activity.json
The demo is an example to show tooltip for only one series per chart, meanwhile, you have got a few and a shared tooltip, so finally, you will need to do the same calculations to get the array of points for shared tooltip.
Except that, I think that a better approach is to clean up your code.
Notice that the functionalities to calculate points for each chart are similar and can be paste into the loop:
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();
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])
})
}))
})
}
And notice that also your afterSetextreme callback can be change to trigger this function:
function setExtremes(chart, min, max) {
Highcharts.charts.forEach(ch => {
if (ch !== chart) {
ch.xAxis[0].setExtremes(min, max)
}
})
}
Also, you can define options which are common for each chart, like it is done here: https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/gauge-solid/
Finally demo: https://jsfiddle.net/BlackLabel/hv7azdgm/

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

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

jQuery UI multiple selectable tooltips are collapsing

I'm new to jQuery UI.
I'm trying to create a selectable jQuery UI tooltip. The tooltip is associated with the links on a page.
When the link is surrounded by just text, it works fine. But when there are few links next to each other, the functionality overlaps and tooltips don't show smoothly anymore.
you can find the code on http://jsfiddle.net/zumot/Hc3FK/2/
Below the JavaScript code
$("[title][data-scan]").bind("mouseleave", function (event) {
event.stopImmediatePropagation();
var fixed = setTimeout('$("[title][data-scan]").tooltip("close")', 100);
$(".ui-tooltip").click(function () {
alert("I am clickable");
return false;
});
$(".ui-tooltip").hover(
function () {
clearTimeout(fixed);
},
function () {
$("[title][data-scan]").tooltip("close");
});}).tooltip({
items: "img, [data-scan], [title]",
content: function () {
var element = $(this);
if (element.is("[data-scan]")) {
var text = element.attr("href");
return "<a href='http://www.google.com'>You are trying to open a tooltip <span>" + text + "</span></a>";
}
if (element.is("[title]")) {
return element.attr("title");
}
if (element.is("img")) {
return element.attr("alt");
}
},
position: {
my: "right center",
at: "left center",
delay: 200,
using: function (position, feedback) {
$(this).css(position);
$("<div>")
.addClass(feedback.vertical)
.addClass(feedback.horizontal)
.appendTo(this);
}
}});
My attempt to fix the issue was by making the variable fixed global (to make it accessible by other jQuery UI properties), and on Open event, hide any other previously opened tooltips and clear the timeout id saved in fixed variable.
You can find the solution here http://jsfiddle.net/zumot/dVGWB/
, though to see the code working properly, you'll have to run it directly on your browser.
Here's the snapshort of the fixed code.
// Make the timeout id variable global
var fixed = 0;
$("[title][data-scan]").tooltip({
items: "img, [data-scan], [title]",
content: function () {
var element = $(this);
if (element.is("[data-scan]")) {
var text = element.attr("href");
return "<a href='http://www.google.com'>You are trying to open a tooltip <span>" + text + "</span></a>";
}
if (element.is("[title]")) {
return element.attr("title");
}
if (element.is("img")) {
return element.attr("alt");
}
},
open: function (event, ui) {
// When opening a new div, hide any previously opened tooltips first.
$(".ui-tooltip:not([id=" + ui.tooltip[0].id + "])").hide();
// clear timeout as well if there's any.
if (tf > 0) {
clearTimeout(tf)
};
},
position: {
my: "right center",
at: "left center",
delay: 200,
using: function (position, feedback) {
$(this).css(position);
$("<div>")
.addClass(feedback.vertical)
.addClass(feedback.horizontal)
.appendTo(this);
}
}
}).bind("mouseleave", function (event) {
// stop defeulat behaviour
event.stopImmediatePropagation();
fixed = setTimeout('$("[title][data-scan]").tooltip("close")', 100);
$(".ui-tooltip").hover(
function () {
clearTimeout(tf);
}, function () {
$("[title][data-scan]").tooltip("close");
})
});

Resources