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/
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 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/
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 using jVectorMap, everything works fine without zooming.
But when a user zoomed in the page I need to allow the user to scroll the the map using a vertical and horizontal scroll bar.
I have tried to add overflow-y: scroll; And other many options to do the scrolling but nothing works perfectly.
I can set the width and height of div to get the scroll bar but it is not related with map zoom in and zoom out.
So I am expecting a scroll bar horizontally and vertically which using that user can see the full map if even it is zoomed.
I have seen a map with below image in the internet
But No idea how can I add a scroll button control like this in jVector map.
Can someone help me to resolve this issue.?
You need two steps:
To understand how the map is translated inside the container, initialize the Map with the onViewportChange event:
$("#map").vectorMap({
map: "world_mill",
// set map properties, series, and so on
//...
onViewportChange: function(event, scaleFactor,transX,transY){
// look at the values here:
console.log("Viewport changed",scaleFactor,transX,transY);
}
});
To the point:
to apply a map translation, set your desired X and Y panning, at the end invoke the applyTransform function:
Example:
var worldMap = $("#map").vectorMap("get", "mapObject");
worldMap.transX = -100;
worldMap.applyTransform();
Additional information:
Luckily, jVectorMap will do the range checking for you, so for your pan buttons you can also simply use somethng like:
worldMap.transX -= (10 * worldMap.scale); // move left
worldMap.transX += (10 * worldMap.scale); // move right
worldMap.transY -= (10 * worldMap.scale); // move up
worldMap.transY += (10 * worldMap.scale); // move down
You will find the range check in the applyTransform function in jVectorMap source code.
Credits: Kirill Lebedev, the great author of jVectorMap.
Lastly, the re-center button:
You can get the center of the map as follows:
var mapCX = (worldMap.width / 2) * worldMap.scale + worldMap.transX * worldMap.scale;
var mapCY = (worldMap.height / 2) * worldMap.scale + worldMap.transY * worldMap.scale;
As you haven't provide any source code, I can't help further, but if you have understand the concept, the transformation between your scrollbar range and the map translation is trivial easy.
I'm building in some custom functionality where users can click on data points in a line chart to add notes to that date. This is a bit misleading as the notes aren't actually attached to the metrics themselves but rather the date it lands on. In other words, if I have 6 series on one line chart that spans the dates 01/01/12 - 01/08/12, a single note on 01/05/12 will apply to all 6 series. So, as you can imagine clicking on a data point on one of the 6 series or the date 01/05/12 would mislead the user to believe that this note would be applied to that data point, not the entire date and any series that lands on that date.
So, to remedy this usability issue I've decided that the best visual cue would be something like this:
There would be a clickable icon at the top of each xAxis gridLine that would need to scale with the xAxis gridLine (like if a user selects an area to zoom in on).
Suggestions on best way to pull this off? I only need a suggestion for how best to add the icon to every line... I have all post-click functionality already built.
Building on Mark's suggestion using redraw event to position the images and using load event to create them. Adding them on load is necessary to make them available during export and you would not want to create new images on each redraw either.
These chart events are used:
events: {
load: drawImages,
redraw: alignImages
}
In the drawImages function I'm using the inverse translation for the xAxis to position the images on the chart:
x = chart.plotLeft + chart.xAxis[0].translate(i, false) - imageWidth / 2,
y = chart.plotTop - imageWidth / 2;
and then adding them and setting a click handler, zIndex, pointer cursor:
chart.renderer.image('http://highcharts.com/demo/gfx/sun.png', x, y, imageWidth, imageWidth)
.on('click', function() {
location.href = 'http://example.com'
})
.attr({
zIndex: 100
})
.css({
cursor: 'pointer'
})
.add();
In alignImages the attr function is used to set new x and y values for the images which are calculated the in the same way as in drawImages.
Full example on jsfiddle
Screenshot:
Couple of ideas. First, I would use the chart redraw event to know when the chart is being redrawn (say on a zoom). Then second, explicitly place your images at the axis locations of interest. To get those query directly out of the DOM.
Using jQuery:
$('.highcharts-axis') //return an array of the two axis.
They will have svg "text element" children with (x, y) positions.