Set Custom icon to context menu for multiple charts (Highcharts) - highcharts

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;
};

Related

Highcharts Negative Values Column Graph Bottom Radius

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/

Is there a way to check if an XYZ triplet is a valid color?

The XYZ color space encompasses all possible colors, not just those which can be generated by a particular device like a monitor. Not all XYZ triplets represent a color that is physically possible. Is there a way, given an XYZ triplet, to determine if it represents a real color?
I wanted to generate a CIE 1931 chromaticity diagram (seen bellow) for myself, but wasn't sure how to go about it. It's easy to, for example, take all combinations of sRGB triplets and then transform them into the xy coordinates of the chromaticity diagram and then plot them. You cannot use this same approach in the XYZ color space though since not all combinations are valid colors. So far the best I have come up with is a stochastic approach, where I generate a random spectral distribution by summing a random number of random Gaussians, then converting it to XYZ using the standard observer functions.
Having thought about it a little more I felt the obvious solution is to generate a list of xy points around the edge of spectral locus, corresponding to pure monochromatic colors. It seems to me that this can be done by directly inputting the visible frequencies (~380-780nm) into the CIE XYZ standard observer color matching functions. Treating these points like a convex polygon you could determine if a point is within the spectral locus using one algorithm or another. In my case, since what I really wanted to do is simply generate the chromaticity diagram, I simply input these points into a graphics library's polygon drawing routine and then for each pixel of the polygon I can transform it into sRGB.
I believe this solution is similar to the one used by the library that Kel linked in a comment. I'm not entirely sure, as I am not familiar with Python.
function RGBfromXYZ(X, Y, Z) {
const R = 3.2404542 * X - 1.5371385 * Y - 0.4985314 * Z
const G = -0.969266 * X + 1.8760108 * Y + 0.0415560 * Z
const B = 0.0556434 * X - 0.2040259 * Y + 1.0572252 * Z
return [R, G, B]
}
function XYZfromYxy(Y, x, y) {
const X = Y / y * x
const Z = Y / y * (1 - x - y)
return [X, Y, Z]
}
function srgb_from_linear(x) {
if (x <= 0.0031308) {
return x * 12.92
} else {
return 1.055 * Math.pow(x, 1/2.4) - 0.055
}
}
// Analytic Approximations to the CIE XYZ Color Matching Functions
// from Sloan http://jcgt.org/published/0002/02/01/paper.pdf
function xFit_1931(x) {
const t1 = (x - 442) * (x < 442 ? 0.0624 : 0.0374)
const t2 = (x -599.8) * (x < 599.8 ? 0.0264 : 0.0323)
const t3 = (x - 501.1) * (x < 501.1 ? 0.0490 : 0.0382)
return 0.362 * Math.exp(-0.5 * t1 * t1) + 1.056 * Math.exp(-0.5 * t2 * t2) - 0.065 * Math.exp(-0.5 * t3 * t3)
}
function yFit_1931(x) {
const t1 = (x - 568.8) * (x < 568.8 ? 0.0213 : 0.0247)
const t2 = (x - 530.9) * (x < 530.9 ? 0.0613 : 0.0322)
return 0.821 * Math.exp(-0.5 * t1 * t1) + 0.286 * Math.exp(-0.5 * t2 * t2)
}
function zFit_1931(x) {
const t1 = (x - 437) * (x < 437 ? 0.0845 : 0.0278)
const t2 = (x - 459) * (x < 459 ? 0.0385 : 0.0725)
return 1.217 * Math.exp(-0.5 * t1 * t1) + 0.681 * Math.exp(-0.5 * t2 * t2)
}
const canvas = document.createElement("canvas")
document.body.append(canvas)
canvas.width = canvas.height = 512
const ctx = canvas.getContext("2d")
const locus_points = []
for (let i = 440; i < 650; ++i) {
const [X, Y, Z] = [xFit_1931(i), yFit_1931(i), zFit_1931(i)]
const x = (X / (X + Y + Z)) * canvas.width
const y = (Y / (X + Y + Z)) * canvas.height
locus_points.push([x, y])
}
ctx.beginPath()
ctx.moveTo(...locus_points[0])
locus_points.slice(1).forEach(point => ctx.lineTo(...point))
ctx.closePath()
ctx.fill()
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
for (let y = 0; y < canvas.height; ++y) {
for (let x = 0; x < canvas.width; ++x) {
const alpha = imageData.data[(y * canvas.width + x) * 4 + 3]
if (alpha > 0) {
const [X, Y, Z] = XYZfromYxy(1, x / canvas.width, y / canvas.height)
const [R, G, B] = RGBfromXYZ(X, Y, Z)
const r = Math.round(srgb_from_linear(R / Math.sqrt(R**2 + G**2 + B**2)) * 255)
const g = Math.round(srgb_from_linear(G / Math.sqrt(R**2 + G**2 + B**2)) * 255)
const b = Math.round(srgb_from_linear(B / Math.sqrt(R**2 + G**2 + B**2)) * 255)
imageData.data[(y * canvas.width + x) * 4 + 0] = r
imageData.data[(y * canvas.width + x) * 4 + 1] = g
imageData.data[(y * canvas.width + x) * 4 + 2] = b
}
}
}
ctx.putImageData(imageData, 0, 0)

Data labels with "callout" shape misses pointing triangle for short text

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/

Highcharts annotations outside the plot area incorrectly placed

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

How to position the customed tootip of HighCharts?

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?

Resources