I try to modify the default styles of tooltip. I want the bottom of the tooptip with a sharp corner, like callout box. I found a example that supports pointers on the right and left side. I modified some code, achieve the pointer at bottom, but the pointer can't direct at selected point.Code link at jsFiddle: http://jsfiddle.net/yuezheng/AxqKY/90/. How to position the box at the top of the selected point?
(function (Highcharts) {
Highcharts.Renderer.prototype.symbols.callout = function (x, y, w, h, options) {
var distance = 15,
arrowLength = 10,
halfDistance = 7.5,
r = options ? options.r : 5,
anchorX = (options && options.anchorX) || x,
anchorY = (options && options.anchorY) || y,
top = ['M', x + r, y, 'L', x + w - r, y],
topRight = ['C', x + w, y, x + w, y, x + w, y + r],
right = ['L', x + w, y + h - r],
rightBottom = ['C', x + w, y + h, x + w, y + h, x + w - r, y + h],
//bottom = ['L', x + r, y + h],
bottomLeft = ['C', x, y + h, x, y + h, x, y + h - r],
left = ['L', x, y + r],
leftTop = ['C', x, y, x, y, x + r, y],
bottom = ['L', x + w/2 + arrowLength, y + h,
x + w/2, y + h + arrowLength,
x + w/2 - arrowLength, y + h,
x, y + h];
/*
if (anchorX > w) {
right = [
'L', x + w, anchorY - halfDistance,
x + w + arrowLength, anchorY,
x + w, anchorY + halfDistance,
x + w, y + h - r
];
} else if (anchorX < 0) {
left = [
'L', x, anchorY + halfDistance,
x - arrowLength, anchorY,
x, anchorY - halfDistance,
x, y + r
];
}
*/
return top
.concat(topRight)
.concat(right)
.concat(rightBottom)
.concat(bottom)
.concat(bottomLeft)
.concat(left)
.concat(leftTop);
};
}(Highcharts));
var chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
type: 'line'
},
xAxis: {
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
},
tooltip: {
shape: 'callout',
borderColor: '#AAA',
borderWidth: 1,
shadow: false,
borderRadius: 5
},
series: [{
data: [29.9, 71.5, 106.4, 129.2, 144.0, 176.0, 135.6, 148.5, 216.4, 194.1, 95.6, 54.4]
}]
});
Maybe better will be disable all hgihcharts options, set useHTML and add all elements as HTMl elemtns with CSS styles?
Related
I am having column graph which contains positive and negative values column graph, I need to give the border radius top only for the positive and negative Graph. But if I'm trying to add the border radius top for the negative column graph it was not working. Kindly let me know how to give the only border radius top for negative column graph using highcharts in react js.
refer image here
For example ,in the image provided, for I want border radius at -5% for negative value .For positive value I want border radius at top of bar.
I prepared a custom code that adds the wanted border radius for positive value - on the top, for negative - on the bottom of the column.
$(function() {
'use strict';
(function(factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory;
} else {
factory(Highcharts);
}
}(function(Highcharts) {
(function(H) {
H.wrap(H.seriesTypes.column.prototype, 'translate', function(proceed) {
const options = this.options;
const topMargin = options.topMargin || 0;
const bottomMargin = options.bottomMargin || 0;
proceed.call(this);
H.each(this.points, function(point) {
console.log(point)
if (options.customRadius) {
const w = point.shapeArgs.width;
const h = point.shapeArgs.height;
const x = point.shapeArgs.x;
const y = point.shapeArgs.y;
let radiusTopLeft,
radiusTopRight,
radiusBottomRight,
radiusBottomLeft;
if (point.y > 0) {
radiusTopLeft = H.relativeLength(options.customRadius, w);
radiusTopRight = H.relativeLength(options.customRadius, w);
radiusBottomLeft = 0;
radiusBottomRight = 0;
} else {
radiusTopLeft = 0;
radiusTopRight = 0;
radiusBottomRight = H.relativeLength(options.customRadius, w);
radiusBottomLeft = H.relativeLength(options.customRadius, w);
}
const maxR = Math.min(w, h) / 2
radiusTopLeft = radiusTopLeft > maxR ? maxR : radiusTopLeft;
radiusTopRight = radiusTopRight > maxR ? maxR : radiusTopRight;
radiusBottomRight = radiusBottomRight > maxR ? maxR : radiusBottomRight;
radiusBottomLeft = radiusBottomLeft > maxR ? maxR : radiusBottomLeft;
point.dlBox = point.shapeArgs;
point.shapeType = 'path';
point.shapeArgs = {
d: [
'M', x + radiusTopLeft, y + topMargin,
'L', x + w - radiusTopRight, y + topMargin,
'C', x + w - radiusTopRight / 2, y, x + w, y + radiusTopRight / 2, x + w, y + radiusTopRight,
'L', x + w, y + h - radiusBottomRight,
'C', x + w, y + h - radiusBottomRight / 2, x + w - radiusBottomRight / 2, y + h, x + w - radiusBottomRight, y + h + bottomMargin,
'L', x + radiusBottomLeft, y + h + bottomMargin,
'C', x + radiusBottomLeft / 2, y + h, x, y + h - radiusBottomLeft / 2, x, y + h - radiusBottomLeft,
'L', x, y + radiusTopLeft,
'C', x, y + radiusTopLeft / 2, x + radiusTopLeft / 2, y, x + radiusTopLeft, y,
'Z'
]
};
}
});
});
}(Highcharts));
}));
Demo: http://jsfiddle.net/BlackLabel/okn8qhdb/
I want to apply a custom image to a context menu for multiple charts, This is the code I found to apply a custom image to a context menu. Here is the link to fiddle
Highcharts.SVGRenderer.prototype.symbols.download = function(x, y, w, h) {
var path = [
// Arrow stem
'M', x + w * 0.5, y,
'L', x + w * 0.5, y + h * 0.7,
// Arrow head
'M', x + w * 0.3, y + h * 0.5,
'L', x + w * 0.5, y + h * 0.7,
'L', x + w * 0.7, y + h * 0.5,
// Box
'M', x, y + h * 0.9,
'L', x, y + h,
'L', x + w, y + h,
'L', x + w, y + h * 0.9
];
return path;
};
Highcharts.chart('container', {
exporting: {
buttons: {
contextButton: {
symbol: 'download'
}
}
}
});
This code snippet changes the symbol based on the unique id, How do i apply the same across multiple charts whose unique id is not known?
The example which you have shared defines this symbol as a 'download' which saves this symbol in the Highcharts core options and makes it possible to get it whenever you want to use it, even multiple times.
Demo: https://jsfiddle.net/BlackLabel/7rt21zLe/
Highcharts.SVGRenderer.prototype.symbols.download = function (x, y, w, h) {
var path = [
// Arrow stem
'M', x + w * 0.5, y,
'L', x + w * 0.5, y + h * 0.7,
// Arrow head
'M', x + w * 0.3, y + h * 0.5,
'L', x + w * 0.5, y + h * 0.7,
'L', x + w * 0.7, y + h * 0.5,
// Box
'M', x, y + h * 0.9,
'L', x, y + h,
'L', x + w, y + h,
'L', x + w, y + h * 0.9
];
return path;
};
For short labels (1-2 symbols) callout shape is shown without pointer which may be confusing with complex charts. Can this be fixed somehow?
Update1:
Editor: https://stackblitz.com/edit/js-s6upn2
Result: https://js-s6upn2.stackblitz.io
Update2 (fixed workaround):
top and bottom verticalAlign handled, editor updated
Highcharts.SVGRenderer.prototype.symbols.callout = function(x, y, w, h, options) {
const arrowLength = 6;
const halfDistance = 6;
const r = Math.min((options && options.r) || 0, w, h);
// const safeDistance = r + halfDistance;
const anchorX = options && options.anchorX;
const anchorY = options && options.anchorY;
const path = [
'M', x + r, y,
'L', x + w - r, y, // top side
'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
'L', x + w, y + h - r, // right side
'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-right corner
'L', x + r, y + h, // bottom side
'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
'L', x, y + r, // left side
'C', x, y, x, y, x + r, y // top-left corner
];
path.splice(
anchorY >= 0 ? 23 : 3,
3,
'L', anchorX + halfDistance > x + w ? x + w : anchorX + halfDistance, anchorY >= 0 ? y + h : y,
anchorX, anchorY >= 0 ? y + h + arrowLength : y - arrowLength,
anchorX - halfDistance >= x ? anchorX - halfDistance : x, anchorY >= 0 ? y + h : y,
x + r, anchorY >= 0 ? y + h : y
);
return path;
};
Update3
Actually looks like this realization should handle many more corner cases, for example it breaks "split" type tooltips
So I suggest to call this shape "callout2" and use for top/bottom data labels only.
This problem looks like bug, so I reported it on Highcharts github: https://github.com/highcharts/highcharts/issues/10020
To workaround, you can overwrite the callout shape:
Highcharts.SVGRenderer.prototype.symbols.callout = function(x, y, w, h, options) {
var arrowLength = 6,
halfDistance = 6,
r = Math.min((options && options.r) || 0, w, h),
safeDistance = r + halfDistance,
anchorX = options && options.anchorX,
anchorY = options && options.anchorY,
path;
path = [
'M', x + r, y,
'L', x + w - r, y, // top side
'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
'L', x + w, y + h - r, // right side
'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-rgt
'L', x + r, y + h, // bottom side
'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
'L', x, y + r, // left side
'C', x, y, x, y, x + r, y // top-left corner
];
path.splice(
23,
3,
'L', anchorX + halfDistance, y + h,
anchorX, y + h + arrowLength,
anchorX - halfDistance, y + h,
x + r, y + h
);
return path;
}
Live demo: http://jsfiddle.net/BlackLabel/o0j76Lbt/
I want to place annotations on the y-axis but outside of the plot area. These should be placed with a connector (callout) at the same height as the PlotLines.
However, the settings do not work correctly there (see image - the red one). The alignment should be horizontal 'right' and vertical 'centered'. So that the connector on the right side is on the axis.
annotations: [{
labelOptions: {
align: 'right',
verticalAlign: 'middle',
//distance: 0,
overflow: 'allow',
crop: false
},
labels: [{
point: {
x: -6,
y: 273,
yAxis: 0
},
format: 'wrong right middle'
}]
}]
Distance gives a wrong result. If activate this the annotation jumps to another position. It just seems to work for vertical displacement.
My current workaround is, I add a callout as css style. But this is not the way to go.
https://jsfiddle.net/xreality/dskfv986/
Thanks
Maik
Highcharts annotations use callout symbol (Highcharts.SVGRenderer.prototype.symbols.callout) by default which is not designed for this usage. However, you can modify it so that it fits your needs. Check my wrap posted below and demo with it.
Wrapper on Highcharts.SVGRenderer.prototype.symbols.callout method:
(function(H) {
H.SVGRenderer.prototype.symbols.callout = function(x, y, w, h, options) {
var arrowLength = 6,
halfDistance = 6,
r = Math.min((options && options.r) || 0, w, h),
safeDistance = r + halfDistance,
anchorX = options && options.anchorX,
anchorY = options && options.anchorY,
path;
path = [
'M', x + r, y,
'L', x + w - r, y, // top side
'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
'L', x + w, y + h - r, // right side
'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-rgt
'L', x + r, y + h, // bottom side
'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
'L', x, y + r, // left side
'C', x, y, x, y, x + r, y // top-left corner
];
// Anchor on right side
if (anchorX && anchorX > w) {
// Chevron
if (
anchorY > y + safeDistance &&
anchorY < y + h - safeDistance
) {
path.splice(13, 3,
'L', x + w, anchorY - halfDistance,
x + w + arrowLength, anchorY,
x + w, anchorY + halfDistance,
x + w, y + h - r
);
// Simple connector
} else {
path.splice(13, 3,
'L', x + w, h / 2,
anchorX, anchorY,
x + w, h / 2,
x + w, y + h - r
);
}
// Anchor on left side
} else if (anchorX && anchorX < 0) {
// Chevron
if (
anchorY > y + safeDistance &&
anchorY < y + h - safeDistance
) {
path.splice(33, 3,
'L', x, anchorY + halfDistance,
x - arrowLength, anchorY,
x, anchorY - halfDistance,
x, y + r
);
// Simple connector
} else {
path.splice(33, 3,
'L', x, h / 2,
anchorX, anchorY,
x, h / 2,
x, y + r
);
}
} else if ( // replace bottom
anchorY &&
anchorY > h &&
anchorX > x + safeDistance &&
anchorX < x + w - safeDistance
) {
path.splice(23, 3,
'L', anchorX + halfDistance, y + h,
anchorX, y + h + arrowLength,
anchorX - halfDistance, y + h,
x + r, y + h
);
} else if ( // replace top
anchorY &&
anchorY < 0 &&
anchorX > x + safeDistance &&
anchorX < x + w - safeDistance
) {
path.splice(3, 3,
'L', anchorX - halfDistance, y,
anchorX, y - arrowLength,
anchorX + halfDistance, y,
w - r, y
);
} else { // add to support right arrows
path.splice(13, 3,
'L', x + w, anchorY - halfDistance,
x + w + arrowLength, anchorY,
x + w, anchorY + halfDistance,
x + w, y + h - r
);
}
return path;
}
})(Highcharts);
Demo:
https://jsfiddle.net/BlackLabel/0eydz6q3/
EDIT
For annotations that are dynamically added, we can use chart.events.render to get each element dimensions using SVGElement.getBBox() method and calculate appropriate offsets using particular annotation width and height. Nextly we have to remove the old annotations using chart.removeAnnotations('id') method and add new ones (with updated distance and x position) using chart.addAnnotations(newOptions). Check the demo posted below.
chart: {
events: {
render: function() {
var chart = this,
annotations = chart.annotations[0],
options = annotations.userOptions,
labels = options.labels,
arrowWidth = 6,
bbox;
labels.forEach(function(label, i) {
if (label.point.x === 0) {
bbox = annotations.labels[i].getBBox();
label.point.x = -(bbox.width / 2 + arrowWidth);
label.distance = -bbox.height / 2;
}
});
chart.removeAnnotation('annotations-id');
chart.addAnnotation(options);
}
}
}
Demo:
https://jsfiddle.net/BlackLabel/wf8q1odj/1/
Api reference:
https://api.highcharts.com/class-reference/Highcharts.SVGElement#getBBox
https://api.highcharts.com/class-reference/Highcharts.Chart#removeAnnotation
https://api.highcharts.com/class-reference/Highcharts.Chart#addAnnotation
Could someone guide me on how to get the no of bubbles in highcharts
You can use pointFormatter to calculate the content of the tooltip dynamically. In the callback you can check if the hovered bubble overlaps with other bubbles.
function areOverlapping(bubble1, bubble2) {
const {translateX: tx, translateY: ty } = bubble1.series.group
const x1 = tx + bubble1.plotX
const y1 = ty + bubble1.plotY
const r1 = bubble1.marker.radius
const x2 = tx + bubble2.plotX
const y2 = ty + bubble2.plotY
const r2 = bubble2.marker.radius
const x = x1 - x2
const y = y1 - y2
const r = r1 + r2
return x * x + y * y <= r * r
}
Tooltip.pointFormatter:
series: [{
tooltip: {
pointFormatter: function () {
const overlapCount = this.series.data.reduce((sum, point) => {
return sum + (point !== this && areOverlapping(this, point))
}, 0)
return 'Overlapping bubbles: ' + overlapCount
}
},
example: https://jsfiddle.net/0yzkfdjr/