I'm trying to implement synchronised charts in my application using the example code from Highcharts here:
https://www.highcharts.com/demo/synchronized-charts
I have a column layout using the Materialize framework and the charts are positioned side by side in a row. After doing some playing around with the Highcharts example, it seems the charts don't behave the same horizontally as they do vertically. The labels are not synced across the charts and the crosshairs don't move in sync either.
After doing some reading I've found a similar question has been asked before:
Highcharts Sync charts horizontally
However, as someone pointed out in the comments on the "marked correct" answer, the solution doesn't work for responsive charts like mine. This person was advised to ask a separate SO question, but as I can't find it, I'm asking it.
So far this is the closest I can get to my charts properly working:
https://jsfiddle.net/6h7aL2rw/1/
However, as you can see as you move the cursor to the end of the first chart, the tooltip and crosshairs are not fully synchronised and are behind by a few points on each chart.
The code I've changed from the original example is the following:
$('#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
event.chartX = (event.chartX + 3 * $('.chart').width()) % $('.chart').width();
point = chart.series[0].searchPoint(event, true); // Get the hovered point
if (point) {
point.highlight(e);
}
}
});
As was pointed out in the original question though, I don't understand the significance of this:
event.chartX + 3 * $('.chart').width()
But I feel that it's this bit of code that is the problem preventing the charts from being in sync.
That problem is caused by the gaps between the charts, please check an example without them: https://jsfiddle.net/BlackLabel/5Lgrc08z/
As a solution you can set event.chartX to e.offsetX:
$('#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
event.chartX = e.offsetX;
point = chart.series[0].searchPoint(event, true); // Get the hovered point
if (point) {
point.highlight(e);
}
}
});
Live demo: https://jsfiddle.net/BlackLabel/ts7w4kxu/
Related
I have a Highcharts instance that is rendered within a scrollable container. The tooltip.outside option is also set to true so that the tooltip is always on top regardless if it fits the chart svg.
When you scroll however, you can see tooltip following the scrolling. Moreover, when you hover over the series, the tooltip renders in various positions.
Is there a way to fix it? FYI if you set tooltip.outside to false everything works just fine. I'm sure the issue revolves around the fact that when set to true, the calculation to determine where to render the tooltip is no longer correct as the position changed with the scrolling.
So to sum up the 2 issues that appear:
The tooltip follows the scrolling
On re-hovering, the position on the tooltip on the series is wrong.
See gif around the issue: https://imgur.com/a/Zj3NstL
See example: https://jsfiddle.net/tcqeo415/2/
If you comment out my CSS code in the example, you should be able to see how it should work
This answer is pretty similar to #ppotaczek one in terms of the idea just a different implementation.
When the mouse enters a chart, the chart position is cached so if you scroll but your mouse is still within the chart, it doesn't recalculate the position but due to the scrolling the position has changed.
A solution would be to use tooltip.positioner and disable the caching by nullifying the chartsPosition.
positioner: function (w, h, point) {
this.chart.pointer.chartPosition = null;
return this.getPosition(w, h, point);
},
This will force the chart to recalculate the charts position. Note that this might be DOM expensive.
Then, if you want to maintain the scrolling behaviour checkout #ppotaczeck's answer. The code below will remove any tooltips on scroll (works only for the first example)
document.body.addEventListener("scroll", function() {
Highcharts.charts[0].tooltip.hide(0);
}, true)
Example: https://jsfiddle.net/gv1m6tjy/
A chart does not recalculate it's position because it does not track the scroll event.
To fix this, you can recalculate chart.pointer.chartPosition.top. Below code works only for the first chart:
(function (H) {
H.wrap(H.Tooltip.prototype, 'refresh', function (proceed, points) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
this.points = points;
});
}(Highcharts));
document.getElementById('container1').addEventListener('mouseenter', function(){
var chart = Highcharts.charts[0];
if (chart && chart.pointer && !chart.startChartPosY) {
chart.pointer.getChartPosition();
chart.startChartPosY = chart.pointer.chartPosition.top;
}
});
document.getElementById('outer').addEventListener('scroll', function(e){
var H = Highcharts,
chart = H.charts[H.hoverChartIndex],
tooltip = chart.tooltip;
if (chart && chart.pointer) {
chart.pointer.chartPosition.top = chart.startChartPosY - this.scrollTop;
}
if (tooltip && !tooltip.isHidden) {
tooltip.refresh(tooltip.points);
}
});
Live demo: https://jsfiddle.net/BlackLabel/ao0c21g6/3/
Docs: https://www.highcharts.com/docs/extending-highcharts/extending-highcharts
I'm using the syncExtremes function to synchronise multiple charts (and their zooms) as implemented in the example below:
https://jsfiddle.net/pz2bak0u
However I've come across two issues I can't seem to find my way around. The first one is that if you zoom multiple times in different charts without reseting and then click on one of the Reset zoom buttons, the other Reset zoom buttons hang around (and I would like them to disappear as they don't have any functionality when clicked).
I then modified the fiddle to allow xy zooming:
https://jsfiddle.net/47dz68Lt
If you zoom in on the first chart, then the second, and then the third, and try unzooming the first chart, the other two stay halfway zoomed.
I've tried
chart.xAxis[0].setExtremes(null, null);
but haven't had any luck.
Is it even posible to reset all xy zooming in all charts at the same time as in the x zooming example?
Currently you reset only x-axes extremes, with xy zoom type, you need to also reset y-axes extremes:
var H = Highcharts;
H.wrap(H.Chart.prototype, 'zoomOut', function(proceed) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
H.charts.forEach(function(chart) {
if (chart !== this) {
chart.yAxis[0].setExtremes(null, null, undefined, false);
}
}, this);
});
To control the display of the resetZoomButton, you can wrap showResetZoom method, for example:
H.wrap(H.Chart.prototype, 'showResetZoom', function(proceed) {
var visibleBtn = false;
H.charts.forEach(function(chart) {
if (chart.resetZoomButton) {
visibleBtn = true;
}
});
if (!visibleBtn) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
}
});
Live demo: https://jsfiddle.net/BlackLabel/bg7L1nev/
Docs: https://www.highcharts.com/docs/extending-highcharts/extending-highcharts
I need to dynamically sync the xAxis crosshairs across multiple HighStocks charts.
The example http://jsfiddle.net/BlackLabel/hh90ps4c/28/ demonstrates how to sync the controls inside one chart. I cloned the demo into this http://jsfiddle.net/jakobvinther/ayf5gst2/ ...and replaced the single chart by a table with two charts. The JavaScript code was almost just duplicated for the second chart.
Out of the box, zooming, panning and the rangeSelector sliders in the two charts are nicely synced (I did not change any code to achieve that).
The problem is that the xAxis crosshairs in the two charts are not synced, they work inside each chart individually. How can that be done?
/* thanks */
If the charts are not in one column, the problem is the mouse event x coordinate. You can refer to the first chart in the column to get the coordinates you need:
$('#container1').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];
// Find coordinates within the chart
event = Highcharts.charts[0].pointer.normalize(e);
// Get the hovered point
point = chart.series[0].searchPoint(event, true);
if (point) {
point.highlight(e);
}
}
});
Live demo: http://jsfiddle.net/BlackLabel/8krwuof9/
I am working on a web application displaying multiple charts over the same categories. If you want an example, think population for different cities over time. The charts all have crosshairs enabled for the X axis.
I've been asked if, when a user hovers over a chart and moves the mouse - therefore moving the crosshairs - it is possible to have the crosshairs of all the other charts as well, in parallel, kinda mirroring the movement.
Off the top of my head it should not be impossible - capture the position on the X axis whenever the mouse is moved, then set/move the crosshairs on all charts to the same value for all the other charts - but that would work only if moving crosshairs programmatically is possible.
Is this possible in a non trivial way?
Edit: I created a partially working version on jsfiddle, based on the jsfiddle linked to in the answer, and replicating the two column layout of the charts in our web app: http://jsfiddle.net/basiliosz/sgc8jg34/
The crosshairs are moved only in charts directly above and below the one where the user is hovering the mouse cursor, but not in the other column. This is the crucial code snippet from the event handler:
for (i = 0; i < noOfCharts; i = i + 1) {
chart = separateCharts[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.highlight(event);
}
}
where Point.prototype.highlight is defined as this:
Highcharts.Point.prototype.highlight = function (ev) {
this.onMouseOver(); // Show the hover marker
this.series.chart.tooltip.refresh(this); // Show the tooltip
this.series.chart.xAxis[0].drawCrosshair(ev, this); // Show the crosshair
};
There is an internal function for drawing crosshair which can be used (it is not part of the official API)
/**
* Draw the crosshair
*
* #param {Object} e The event arguments from the modified pointer event
* #param {Object} point The Point object
*/
Axis.prototype.drawCrosshair: function(e, point) {}
example: http://jsfiddle.net/6rbhxmrp/
You can also the official demo with synchronizing charts https://www.highcharts.com/demo/synchronized-charts
The example with a disabled tooltip: http://jsfiddle.net/vwm4oe6k/
I want to create a jqPlot line chart which has the ability to change orientation between vertical and horizontal orientation. I was able to achieve this using CSS rules, by rotating the div element containing the chart.
My work up to now: http://jsfiddle.net/GayashanNA/A4V4y/14/
But the problem is I also want to track the mouse-pointer and mouse clicks on points on chart after the orientation is flipped because i want to annotate those points. I am unable to do this when the chart is in vertical orientation. Can anyone suggest a method to do this? Or am i approaching the problem in a wrong way?
(Note: I am able to do this in horizontal orientation, you can observe it if you try to click on a point on the above chart.)
Thanks and help is much appreciated.
I've never used jqPlot, but I guess your problem is trying to use css rotate(), since the cursor plugin is using the mouse position to determine where to draw the lines, and element's size doesn't change when transformed by rotate(), it still have the same width and height values.
If you take a look at the code, you will see:
if (c.showVerticalLine) {
c.shapeRenderer.draw(ctx, [[gridpos.x, 0], [gridpos.x, ctx.canvas.height]]);
}
if (c.showHorizontalLine) {
c.shapeRenderer.draw(ctx, [[0, gridpos.y], [ctx.canvas.width, gridpos.y]]);
}
So it seems like the library is always drawing the lines based on mouse position over the original element, which of course, won't match the position after being transformed by rotate(), and XY coordinates are going to be transformed to YX after rotate().
I would try to change the size of your original element, though I don't know if the library lets you specify in which sides are the labels going to be drawn.
I finally found a solution for the problem. But i had to change jqPlot library to achieve this. To help anyone else who run in to the same problem, i'll put my solution here.
First i had to insert the following code in to the jqPlot class of the jquery.jqplot.js file, which is the core library.
function jqPlot() {
//add the following code segment
var verticallyOriented = false;
this.setVertical = function(state){
verticallyOriented = state;
}
//don't change other code that isn't mentioned here
//now you have to change the logic in the getEventPosition function
//to make sure the new orientation is detected
function getEventPosition(ev) {
//change the line starting with var gridPos = ...
//to the following code segment
//depending on the orientation the event position calculating algorithm is changed
if(verticallyOriented){
var gridPos = {x:ev.pageY - go.top , y:plot.eventCanvas._elem.height() - ev.pageX + go.left};
} else {
var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
}
//no change to other code is needed
}
}
You can view a working example here: http://jsfiddle.net/GayashanNA/yZwxu/
Gist for the changed library file: https://gist.github.com/3755694
Please correct me if i have done something wrong.
Thanks.