I am trying to reposition data labels in HighCharts line charts so that they don't overlap.
You can do this initially by attaching a repositioning function to the load event:
var repositionLabels = function(series) {
var s1 = series[0].data;
var s2 = series[1].data;
var i=0, l=s1.length, higher;
for(; i<l; i++) {
higher = s1[i].y > s2[i].y;
s1[i].dataLabel.attr('y', higher ? s1[i].dataLabel.y-10 : s1[i].dataLabel.y+30 );
s2[i].dataLabel.attr('y', !higher ? s2[i].dataLabel.y-10 : s2[i].dataLabel.y+30 );
}
}
For the upper line, data labels are positioned above the line; for the lower line, the data labels are positioned below. So they don't overlap.
The problem
The problem is that if you click on a legend item to show/hide a line, the data labels revert to their default (bad, overlapping) positions. Here's a demo: http://jsfiddle.net/supertrue/QbGPV/984/
I would expect that attaching the same function to the redraw event would keep new label positions intact, but it doesn't. Nor does attaching it to plotOptions.line.events.legendItemClick.
How can I keep the good label positions intact after legend items are clicked?
I don't know if this is the best solution. I delayed the positioning and then it kinda worked.
See fiddle
setTimeout(function () {
positionLabels(chart.series);
}, 500);
Edit
Updated Fiddle
It appears that the series animation is interfering with the label positioning. If you set:
chart: { renderTo: 'container', type: 'line', animation:false,
then it all works fine. I'm guessing that the animation is running after you have positioned the labels, and is re-positioning them for you.
It looks like you'll either have to turn off animation, or use the setTimeout method posted by #anpsmn.
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 made a small fiddle based on the highcharts demos at http://jsfiddle.net/w53woene/1/
The issue I have boils down to four things:
1) I need to rotate the labels (this is based on a feature request so I can't ignore it).
labels: {
rotation: -45
}
2) The fact that the last label is really really long. This is based on data which is given for each chart so it's not always the case but it's the case around 30%-40% of the time so quite often.
3) Labels shouldn't be wrapped or shortened with an ellipsis.
4) I need to add spacing between the chart and the legend to adapt to the labels size but only when the label is going to be too long and colliding with the legend.
This only seems to happen when the chart: { inverted: true } and if it's not inverted the legend behaves normally, increasing the distance from the chart as needed.
Ideally I'd like to know if there's a specific option I'm missing that achieves this natively, i.e., I would have expected floating: false to achieve this the same way it does when the chart is not inverted, but it doesn't seem to. If this isn't possible I'd like to know how I can adjust the chart size (excluding the legend) in order to achieve this via JavaScript.
The problem is that vertical axis add space on the left size, but doesn't add on the bottom. I would wrap labels for a better readability: http://jsfiddle.net/w53woene/2/ - otherwise it may happen that you will have so long label that chart will get 0 pixels for plotting area.
If you really need non-wrapped text, then I would wrap Axis.prototype. getOffset to add extra space:
(function(H) {
H.wrap(H.Axis.prototype, 'getOffset', function(p) {
p.call(this);
if (this.isXAxis) {
var lastTick = this.tickPositions[this.tickPositions.length - 1],
lastLabel = this.ticks[lastTick].label,
height = lastLabel.getBBox(true).height;
this.labelDiff = height - this.chart.marginBottom;
if (this.labelDiff > this.chart.axisOffset[2]) {
this.chart.axisOffset[2] = this.labelDiff;
} else {
this.labelDiff = 0;
}
} else {
this.offset -= this.chart.xAxis[0].labelDiff;
}
});
})(Highcharts)
And live demo: http://jsfiddle.net/vwegeuvy/
I would like my chart area to be extended on the top so I've added a negative margin to the chart. However, this causes series at times to be drawn over the contextButtons and can make them impossible to click. I'd like to just increase the z-index or do something that makes them appear above the series without having to make my own buttons that live outside the chart. Does anyone have a way to do this?
Example: http://jsfiddle.net/tu67e1ce/1/
chart: {
marginTop: -25,
events: {
load: function() {
this.exportSVGElements[0].toFront();
this.exportSVGElements[1].toFront();
}
}
},
So method toFront() will bring items on top of the SVG container. this.exportSVGElements[0]/[1] are button/symbol. Of course both of them are inner object of the Highcharts core.
I need add another color for xAxis labels when it's hover (like number 19 in the picture). I saw this property later but I lost it.
Need a help =)
And can I set a property (z-index) for xAxis tick? Cause its color must be white, and now I can't see this color, when its z-index less than z-index chart's area.
For your first question, I would use a shared tooltip and the point mouseOver event to change the corresponding axis label:
plotOptions: {
series: {
point: {
events: {
// on mouseOver make the xaxis label red
mouseOver: function(){
var xAxis = this.series.chart.xAxis[0];
$(xAxis.ticks[this.x].label.element).css('fill','red');
},
// on mouseOut make it gray again
mouseOut: function(){
var xAxis = this.series.chart.xAxis[0];
$(xAxis.ticks[this.x].label.element).css('fill','#606060');
}
}
}
}
},
Example here.
For your second question, z index with SVG is a little tricky. It doesn't have a true zIndex you can re-order but rather it's dictated by the order in which the elements are drawn. I can't see it matters, though, because the labels are drawn on top of the chart area already (or else you wouldn't see them at all).
There are plenty examples of hiding an Y-Axis without hiding the series - But i need the opposite of that.
I want to make a series invisible while still displaying the y-axis and i dont know how!
Why?
Because i have 2 diagrams which are perfectly aligned by their x-Axis:
But when i disable both Temperature fields, the axis will be lost, and the diagrams are incorrect in size:
I also don't like the idea, of disabling all and loosing whole diagram style:
There is no problem by having an empty diagram, but having a blank box isn't the style i want to reach.
I tried to manipulate the axis in within the legendItemClick event, but selecting the correct axis and set their value visible didn't work out. How can I solve this problem?
I think you have two options:
play around with margins - to make them fixed
set min and max for axis, so labels will remain on a chart: example
if you don't know min/max values at start you can change them within the legendItemClick as following:
plotOptions: {
series: {
events: {
legendItemClick: function() {
var temperatur = this.chart.series[0];
var taupunkt = this.chart.series[1];
var other = this.name=="Lufttemperatur"?taupunkt:temperatur;
var element = this;
if(this.name == "Lufttemperatur" || this.name == "Taupunkt") {
if(this.visible && !other.visible) {
//both will be invisible soon
this.chart.yAxis[0].update({
min:element.yAxis.min,
max:element.yAxis.max
});
} else {
//one will be visible
this.chart.yAxis[0].update({
min:null,
max:null
});
}
}
}
}
},
}
This will only set min/max if there is no one of your series visible, but deletes it if you have one visible