I am trying to synchronize shared tooltip across multiple charts, each having multiple series.
The problem is in the below example, the tooltip always shows the 3 series, even though at that particular point there are only two series present.
1) How do I make sure that a series is shown in tooltip only when it is actually present?
2) How do I make sure the tooltip is closed when we move out of the chart?
JSFiddle: https://jsfiddle.net/qoL7fx27/1/
Code for synchronization in fiddle:
$('#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];
var points = [];
// Find coordinates within the chart
event = chart.pointer.normalize(e.originalEvent);
// Get the hovered point
for(var j=0; j<chart.series.length; j++) {
point = chart.series[j].searchPoint(event, true);
points.push(point);
}
chart.tooltip.refresh(points);
}
});
Here is my solution. It's perfectly working for me. I made adjustments based on Synchronisation of multiple charts
Demo here
The following code shows/hides the tooltip and makes sure they are aligned on mousemove and mouseleave.
Note that I found that I only need to find the first point searched and use it to show/hide the tooltip. It's because all my time series share the same x values.
$("#container").bind("mousemove mouseleave", function(e) {
for (let i = 0; i < Highcharts.charts.length; ++i) {
let hart = Highcharts.charts[i];
let event = chart.pointer.normalize(e.originalEvent); // Find coordinates within the chart
let point;
for (let j = 0; j < chart.series.length && !point; ++j) {
point = chart.series[j].searchPoint(event, true);
}
if (!point) return;
if (e.type === "mousemove") {
point.onMouseOver();
chart.xAxis[0].drawCrosshair(event, point); // Show the crosshair
} else {
point.onMouseOut();
chart.tooltip.hide(point);
chart.xAxis[0].hideCrosshair();
}
}
});
Keep reseting the reset function so as to disallow HighCharts resetting the points -- we take over the control.
Highcharts.Pointer.prototype.reset = function() {
return undefined;
};
1) How do I make sure that a series is shown in tooltip only when it is actually present?
The unwanted behavior is caused by searchPoint function - it returns the nearest point even though the x position doesn't mach with other points. So if the series has only one point it'll be always found.
Solution:
Manually select points to display in tooltip.formatter:
formatter: function() {
var outputString = '';
this.points.forEach(function(point) {
if (point.x === this.x) {
outputString += "<span style='color:" + point.color + "'>\u25CF</span> " + point.series.name + ": <b>" + point.y + "</b><br/>";
}
}, this);
return outputString;
}
API reference: https://api.highcharts.com/highcharts/tooltip.formatter
2) How do I make sure the tooltip is closed when we move out of the chart?
Restore the default Highcharts.Pointer.prototype.reset function by removing these lines:
Highcharts.Pointer.prototype.reset = function() {
return undefined;
};
Demo for both questions: https://jsfiddle.net/BlackLabel/2mxxrk5n/
Update:
I posted the wrong answer for the second question. This code hides tooltips:
$('#container').bind('mouseout', function(e) {
Highcharts.charts.forEach(function(chart) {
chart.tooltip.hide();
// undo point highlight
chart.series.forEach(function(series) {
series.points.forEach((point) => point.setState(''));
});
});
});
can you please tell me how to highlight the corresponding points in each chart? As of now, the tooltip shows correctly, however the points are not highlighted in three charts
This piece of highlights points:
points.forEach(function(point_) {
if (point_) {
point_.highlight(e);
}
}, this);
To achieve the desired behavior you must provide a logic for filtering points that should be Highlighted. Here's a very simplified example adjusted to this particular case:
// provide a logic for filtering points
if(points[0] && points[1].x > 0) {
points.pop(); // remove the unwanted point
}
Related
I want plotlines to be rendered the last(right now area is overlaying them). If i use Zindex width increases and it doesn't look as neath, i also try line-width.
I did this approach for markers to be above plotlines but it doesnt work for plotlines above area. Am i doing something wrong?
componentDidRender() {
if(this.shadowRoot) {
var markers0 = this.shadowRoot.querySelector('.highcharts-markers.highcharts-series-0');
var plotLines0 = this.shadowRoot.querySelector('.highcharts-plot-lines-4');
var area0 = this.shadowRoot.querySelector('.highcharts-area-series');
/// plotlines below markers - works
if(plotLines0 && plotLines0.parentNode && markers0)
plotLines0.parentNode.insertBefore(markers0, plotLines0.nextSibling);
// markers below area - works
// markers0.parentNode.insertBefore(area0, markers0.nextSibling);
//area below plotlines- doesnt work
//if(area0 && area0.parentNode && plotLines0)
//area0.parentNode.insertBefore(plotLines0, area0.nextSibling);
if i run that code for area below plotlines nothing changes.
You can remove the second to last grid line by:
chart: {
events: {
load: function() {
var prevTick;
Highcharts.objectEach(this.yAxis[0].ticks, function(tick) {
if (tick.isLast) {
prevTick.gridLine.destroy();
}
prevTick = tick;
});
}
}
},
Live demo: http://jsfiddle.net/BlackLabel/obam5yxg/
Right now my graph's label is placed in a default position, but my label texts are rather long and its sticking out from the graph.
If possible, I want to place it so that the labels don't stick out from the graph.
Pic of Current situation & Ideal graph
Or if the above is not possible, I want to fix the visible labels. Is this possible?
right now, the "best" label gets lost/hidden when the graph width is reduced. The "too much" label also gets lost when the width is largely reduced.
I have been searching all over stackoverflow and amcharts website but I can't seem to find a good solution.
Is there any way to solve either of the problems...?
// tried these but doesnt work for what I want to do
va.labelOffset = -5;
va.position = "bottom";
va.inside = false;
full code in JSfiddle
In order to keep the chart from hiding the labels on resize, you need to disable the valueAxis' autoGridCount and set the gridCount to the number of tick marks you want to see. You'll also need to remove the labelFrequency setting.
va.autoGridCount = false;
va.gridCount = 3;
//va.labelFrequency = 5;
As for label positioning, you are limted to rotating and vertical offsets out of the box. As a workaround, you can position the labels by modifying the SVG nodes directly through the drawn event, for example:
// prior to chart.write
chart.addListener("drawn", function(e) {
var textNodes = e.chart.valueAxes[0].labelsSet.node.childNodes;
var transform;
//Position "too little"
transform = parseTransform(textNodes[0].getAttribute('transform'));
transform.translate[0] = parseFloat(transform.translate[0]) + 25;
textNodes[0].setAttribute('transform', serializeTransform(transform));
// Position "too much"
transform = parseTransform(textNodes[2].getAttribute('transform'));
transform.translate[0] = parseFloat(transform.translate[0]) - 25;
textNodes[2].setAttribute('transform', serializeTransform(transform));
});
// ...
// from http://stackoverflow.com/questions/17824145/parse-svg-transform-attribute-with-javascript
function parseTransform(a) {
var b = {};
for (var i in a = a.match(/(\w+\((\-?\d+\.?\d*e?\-?\d*,?)+\))+/g)) {
var c = a[i].match(/[\w\.\-]+/g);
b[c.shift()] = c;
}
return b;
}
//serialize transform object back to an attribute string
function serializeTransform(transformObj) {
var transformStrings = [];
for (var attr in transformObj) {
transformStrings.push(attr + '(' + transformObj[attr].join(',') + ')');
}
return transformStrings.join(',');
}
Updated fiddle
I am trying to resize my markers every time the map is zoomed in or out.
Currently I am using this approach:
Iterate all markers in zoomend method.
get current icon size
Perform some calculation to get the new marker size according the zoom size.
set the new dimension to the icon object.
map.on('zoomend', function() {
zoomEndLevel = map.getZoom();
var difference = zoomEndLevel - zoomStartLevel;
console.log("difference in zoom " + difference);
markerArray.forEach(function(marker) {
var icon = marker.options.icon;
var oldX = icon.options.iconSize[0];
var oldY = icon.options.iconSize[1];
var newX = getNewIconAxis(oldX, difference);
var newY = getNewIconAxis(oldY, difference);
console.log(newX + " " + newY);
icon.options.iconSize = [ newX, newY ];
marker.setIcon(icon);
});
});
map.on('zoomstart', function() {
zoomStartLevel = map.getZoom();
});
function getNewIconAxis(value, zoomChange) {
if (zoomChange > 0) {
for (var i = zoomChange; i > 0; i--) {
value = value * 2;
}
} else {
for (var i = zoomChange; i < 0; i++) {
value = value / 2;
}
}
return value;
}
Problem :
This code works fine if I zoom in and out 1 level at once. If I scroll in and out my mouse too frequently then this code given strange outputs. Sometimes the marker size becomes too large or too small.
Question :
1) Is this the only way to make the markers resizable on different zoom levels?
2) If yes then what am I missing here or what changes should be made to make it work perfectly.?
Note : Tagging google maps also because it's more of a logical issue with map (either google or leaflet or mapbox) rather than api specific issue.
Looks like there are several previous posts that you guide you:
Mapbox,leaflet: Increase marker size on Zoom
is there a way to resize marker icons depending on zoom level in leaflet?
https://gis.stackexchange.com/questions/171609/resize-divicons-svgs-at-zoom-levels-leaflet
As for your bug, instead of reading the current icon size value at "zoomstart" event, you might better remember the previous zoom value and read it only at "zoomend" event. I am not sure the "zoomstart" event is fired only once when you scroll (to zoom in/out) successively, while the "zoomend" event may be fired only at the very end.
http://www.highcharts.com/docs/chart-concepts/tooltip says
The tooltip appears when hovering over a point in a series.
But what if I need a custom tooltip to show at the last point even though not hover on the point to demonstrate some loading information like this:
You can set a flag on serie, which should have a label. Then in load event get last point from serie and print label by renderer.
load: function () {
var chart = this,
series = this.series,
len, lastPoint;
$.each(series, function (i, s) {
if (s.options.showLabel) {
len = s.data.length - 1;
lastPoint = s.data[len];
chart.renderer.text('Label', lastPoint.plotX + chart.plotLeft, lastPoint.plotY + chart.plotTop)
.css({
color: '#4572A7'
})
.add();
}
});
}
Example: http://jsfiddle.net/nf7ne/55/
I currently have html enabled tooltips that also display "sub graphs". However, it would be nice if it was possible to have all tooltips pop up in a fixed location or have an offset that adjusted their relative poition.
This is an example of the kind of tooltip that I have (blank data). I'd like to move it to the right. Any suggestions would be appreciated, including any javascript trickery.
whilst the answer is very good it is a little outdated now. Google has implemented CSS control so there is greater flexibility without the need to hack the JavaScript.
.google-visualization-tooltip { position:relative !important; top:0 !important;right:0 !important; z-index:+1;}
will provide a tooltip fixed at the bottom of the chart, live example: http://www.taxformcalculator.com/federal-budget/130000.html
alternatively you could just tweak the left margin...
.google-visualization-tooltip { margin-left: 150px !important; z-index:+1;}
Note that pulling the container forward with z-index reduces (but does not stop entirely) visibility flicker as the mouse moves. The degree of flicker will vary on chart size, call etc. Personally, I prefer to fix the tool tip and make it part of the design as per the first example. Hope this helps those who are deterred by the JS hack (which is good but really no longer necessary).
The tooltip position is set inline, so you need to listen for DOM insertion of the tooltip and change the position manually. Mutation events are deprecated, so use a MutationObserver if it is available (Chrome, Firefox, IE11) and a DOMNodeInserted event handler if not (IE 9, 10). This will not work in IE8.
google.visualization.events.addOneTimeListener(myChart, 'ready', function () {
var container = document.querySelector('#myChartDiv > div:last-child');
function setPosition () {
var tooltip = container.querySelector('div.google-visualization-tooltip');
tooltip.style.top = 0;
tooltip.style.left = 0;
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
for (var i = 0; i < m.length; i++) {
if (m[i].addedNodes.length) {
setPosition();
break; // once we find the added node, we shouldn't need to look any further
}
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
The MutationObserver should be fine, but the events may need some work; I didn't test them.
I had more or less the same question as Redshift, having been trying to move the tooltip relative to the node being hovered over. Using asgallant's fantastic answer I've implemented his code as below.
I haven't been able to test whether this works with the MutationObserver because during my testing in Firefox, Chrome and IE11 it always fails that test and uses addEventListener. The docs suggest it should work though.
I had to introduce a timeout to actually manipulate the styles as otherwise the left and top position of the element was always reported as 0. My assumption is that the event fired upon addition of the node but the DOM wasn't quite ready. This is just a guess though and I'm not 100% happy with implementing it in this way.
var chart = new google.visualization.LineChart(document.getElementById('line_chart'));
google.visualization.events.addOneTimeListener(chart, 'ready', function () {
var container = document.querySelector('#line_chart > div:last-child');
function setPosition(e) {
if (e && e.target) {
var tooltip = $(e.target);
setTimeout(function () {
var left = parseFloat(tooltip.css('left')) - 49;
var top = parseFloat(tooltip.css('top')) - 40;
tooltip.css('left', left + 'px');
tooltip.css('top', top + 'px');
$(".google-visualization-tooltip").fadeIn(200);
}, 1);
}
else {
var tooltip = container.querySelector('.google-visualization-tooltip');
var left = parseFloat(tooltip.style.left) - 49;
var top = parseFloat(tooltip.style.top) - 40;
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
$(".google-visualization-tooltip").fadeIn(200);
}
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
if (m.length && m[0].addedNodes.length) {
setPosition(m);
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
chart.draw(data, options);
}
EDIT: Updated to get the MutationObserver working following asgallant's comment.