Show Text on Clicking Two Points on Highcharts? - javascript

Given an existing Highcharts (in a React component using highcharts-react-official), how can we make it such that when the user clicks on 2 points on the chart, the difference in values between these 2 points will be calculated and shown on the chart in a text box?
To illustrate the desired result, below is an image that shows a green text box with the percentage difference in values after the user clicks on 2 points on the line chart.
How can we achieve this using Highcharts?

That feature doesn't require any react logic. You only need to use click callback function for a point and addAnnotations method. Example:
point: {
events: {
click: function() {
const point = this;
const chart = point.series.chart;
const previousPoint = chart.clickedPoint;
if (previousPoint) {
chart.clickedPoint = null;
chart.addAnnotation({
shapes: [{
type: 'path',
strokeWidth: 1,
stroke: 'red',
dashStyle: 'dash',
points: [{
x: point.x,
y: point.y,
xAxis: 0,
yAxis: 0
}, {
x: previousPoint.x,
y: previousPoint.y,
xAxis: 0,
yAxis: 0
}]
}],
labels: [{
allowOverlap: true,
format: 100 - (previousPoint.y * 100 / point.y) + '%',
shape: 'rect',
point: {
x: (point.x < previousPoint.x ? point.x : previousPoint.x) +
Math.abs(point.x - previousPoint.x) / 2,
y: (point.y < previousPoint.y ? point.y : previousPoint.y) +
Math.abs(point.y - previousPoint.y) / 2,
xAxis: 0,
yAxis: 0
}
}]
});
} else {
chart.clickedPoint = this;
}
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/gzcrp0x4/
API Reference: https://api.highcharts.com/class-reference/Highcharts.Chart#addAnnotation

Related

Style dash in highchart graphic

I'm trying to change the line style of a chart, but I'm not getting it. I'm
looking to change the black line in the middle of the chart, to the style "DASH"
I already put the "dashStyle: 'dash'" in several places, but without success.
The example is the one of the highcharts
I'm trying to change the line style of a chart, but I'm not getting
it.
It is not possible because what you call line is actually an SVG rectangle element. That's why it can't be dashed.
However, you can achieve what you want by adding some custom logic and using Highcharts.SVGRenderer. Simply remove default rectangle graphic of each series point and render several rectangles with breaks to create a dashed line. Check the code and demo posted below.
Code:
Highcharts.setOptions({
chart: {
inverted: true,
marginLeft: 135,
type: 'bullet',
events: {
render: function() {
var chart = this,
point = chart.series[0].points[0],
width = point.shapeArgs.height,
height = point.shapeArgs.width,
x = chart.plotLeft,
xEnd = x + width,
y = chart.plotTop + point.shapeArgs.x,
dashWidth = 15,
dashBreakWidth = 5,
dashColor = '#000',
dashElem,
dashAmount,
realDashBreakWidth;
if (chart.dashedColl) {
chart.dashedColl.forEach(function(oldDashElem) {
oldDashElem.destroy();
});
}
point.graphic.element.remove();
chart.dashedColl = [];
dashAmount = Math.floor(width / (dashWidth + dashBreakWidth));
realDashBreakWidth = (width - dashAmount * dashWidth) / (dashAmount - 1);
while (x < xEnd) {
dashElem = chart.renderer.rect(x, y, dashWidth, height)
.attr({
fill: dashColor
})
.add()
.toFront();
chart.dashedColl.push(dashElem);
x += dashWidth + realDashBreakWidth;
}
}
}
},
title: {
text: null
},
legend: {
enabled: false
},
yAxis: {
gridLineWidth: 0
},
plotOptions: {
series: {
pointPadding: 0.25,
borderWidth: 0,
color: 'red',
targetOptions: {
width: '200%'
}
}
},
credits: {
enabled: false
},
exporting: {
enabled: false
}
});
Demo:
https://jsfiddle.net/BlackLabel/7azfso1r/
API reference:
https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#rect
https://api.highcharts.com/class-reference/Highcharts.Point#remove
https://api.highcharts.com/highcharts/chart.events.render

How can I contain a label on a Highcharts area chart to the series' area

For some reason no matter what we try the labels on our area chart series seem to have a mind of their own. Even though a short label looks like it could fit inside the area of the data, it puts it right on the end line, bleeding out of the area.
We suspect it might be due to having min and max dates that are beyond the series min and max, but these buffer zones are a requirement.
Is there an option to make labels be contained to their own series and not bleed off into whitespace?
Below is the example chart configuration and here is the JSFiddle: http://jsfiddle.net/sLqu34cn/
Highcharts.chart('container', {
chart: {
type: "area",
height: 200
},
legend: {
enabled: false
},
plotOptions: {
area: {
stacking: "percent",
pointPlacement: "on"
},
series: {
lineWidth: 0,
fillOpacity: 1,
marker: {
enabled: false
},
label: {
style: {
color: "white",
textOutline: "1px black"
}
}
}
},
series: [
{
name: "Two",
data: [[1532217600000, 1], [1532822400000, 0]],
color: "#41B6E6"
},
{
name: "Three",
data: [[1532217600000, 0], [1532822400000, 2]],
color: "#0072CE"
}
],
xAxis: {
tickWidth: 1,
title: {
enabled: false
},
labels: {
format: "{value: %b %e}"
},
max: 1533243166375,
min: 1530478366375,
type: "datetime"
},
yAxis: {
tickInterval: 20,
title: {
text: null
},
labels: {
format: "{value}%"
},
max: 100,
min: 0
},
tooltip: {}
});
Probably not the perfect solution, but you can create some customization to position the series labels. This is an example how to manually calculate the center of area in triangle shape:
Highcharts.wrap(Highcharts.Chart.prototype, 'drawSeriesLabels', function(proceed) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
var chart = this,
plotTop = chart.plotTop,
plotLeft = chart.plotLeft,
series = chart.series,
height = chart.yAxis[0].height,
x1,
x2,
y1,
y2;
x1 = ((series[0].graphPath[1] + plotLeft) * 2 + series[0].graphPath[4] + plotLeft) / 3;
y1 = (height + plotTop + series[0].graphPath[2] + plotTop + series[0].graphPath[5] + plotTop) / 3;
x2 = (series[1].graphPath[1] + plotLeft + (series[1].graphPath[4] + plotLeft) * 2) / 3;
y2 = ((series[1].graphPath[2] + plotTop) * 2 + series[1].graphPath[5] + plotTop) / 3;
series[0].labelBySeries.attr({
x: x1,
y: y1,
align: 'center'
});
series[1].labelBySeries.attr({
x: x2,
y: y2,
align: 'center'
});
});
Live demo: http://jsfiddle.net/BlackLabel/34z8od5f/
Docs: https://www.highcharts.com/docs/extending-highcharts/extending-highcharts

Highcharts: multiple heatmaps with shared color bar

In Highcharts, is it possible to have multiple heatmaps in the same chart? For example, I would like to obtain something similar to the picture below:
I know I can make a chart for each heatmap and then align all the charts with CSS, but the thing is that I want the heatmaps to have a shared color bar.
You could create single chart and transform its data. Live example below.
$(function() {
$('#container').highcharts({
chart: {
type: 'heatmap',
height: 1000,
width: 1000
},
title: null,
plotOptions: {
series: {
borderColor: 'white'
}
},
colorAxis: {
min: 0,
max: 1,
minColor: 'white',
maxColor: Highcharts.getOptions().colors[5]
},
legend: {
enabled: false
},
xAxis: {
visible: false
},
yAxis: {
visible: false
},
series: [{
name: 'Sales per employee',
borderWidth: 1,
data: [...Array(43*43)].map((u, i) => {
const x = Math.floor(i/43) // Get x value from 0 to 42
const y = i%43 // Get y value from 0 to 42
const zeroX = !((x+1) % 9) || !((x+2) % 9) // if point should not be displayed
const zeroY = !((y+1) % 9) || !((y+2) % 9) // if point should not be displayed
const v = zeroX || zeroY ? 0 : Math.random() // if point should not be displayed then set its value to 0
return [x, y, v] // return x y an value of each point
})
}]
});
});
https://jsfiddle.net/k4dvz5r2/show/

HighCharts - Add vertical lines from a desire point

Need to draw vertical lines from a desired point rather than starting from 0.
plotLines: [{
color: '#FF0000',
width: 1,
value: 4
}, {
color: '#FF0000',
width: 1,
value: 7
}],
Here is the fiddler link: http://jsfiddle.net/bpswt3tr/4/
My requirement is to draw first vertical line from when y value is 110.2 and 2nd line from when y value is 135.6 instead of starting from zero. i.e above the plot line only. Please suggest how can I achieve this? Thanks.
Considering the documentation it is unlikely that HighCharts supports this by default, as you are only allowed to associate a value of the current axis with the line.
You might need a preprocessing step that inverts you function to get the appropriate X values. Something like:
invert(data, Y) -> list of X values with data[X] = Y
You can do this on the chart.events.load call. If you know these are the points you want to add marker elements to then it is fairly straightforward. You first get the current max label value for the yAxis. Then you add a series to the chart with the starting point being your series' value and the second point being the max viewable yAxis value. Then do the same for the second point you want to add a bar to. Then, you need to re-set the yAxis max value to the initial state because highcharts will try to increase the scale to accommodate the new points.
chart: {
events: {
load: function () {
var yMAx = this.yAxis[0].max;
console.log(yMAx);
this.addSeries({
data: [{
x: 4,
y: 110.2,
marker: {
symbol: 'triangle'
}
}, {
x: 4,
y: yMAx,
marker: {
symbol: 'triangle-down'
}
}, ],
showInLegend: false,
color: 'red',
marker: {
enabled: true
}
});
this.addSeries({
data: [{
x: 7,
y: 135.6,
marker: {
symbol: 'triangle'
}
}, {
x: 7,
y: yMAx,
marker: {
symbol: 'triangle-down'
}
}, ],
showInLegend: false,
color: 'red',
marker: {
enabled: true
}
});
this.yAxis[0].update({
max: yMAx
});
}
}
}
Sample demo.

How can I change the width of the bars in a highchart?

With a bar chart like this one, is is possible to change the width of the bars to represent another data attribute, say the weight of the fruits. The heavier the fruit is, the thicker the bar.
You play with the script here. I am open to other javascript plotting libraries that could do that as long as they are free.
$(function () {
var chart;
$(document).ready(function() {
chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
type: 'column'
},
title: {
text: 'Column chart with negative values'
},
xAxis: {
categories: ['Apples', 'Oranges', 'Pears', 'Grapes', 'Bananas']
},
tooltip: {
formatter: function() {
return ''+
this.series.name +': '+ this.y +'';
}
},
credits: {
enabled: false
},
series: [{
name: 'John',
data: [5, 3, 4, 7, 2]
// I would like something like this (3.5, 6 etc is the width) :
// data: [[5, 3.4], [3, 6], [4, 3.4], [7, 2], [2, 5]]
}, {
name: 'Jane',
data: [2, -2, -3, 2, 1]
}, {
name: 'Joe',
data: [3, 4, 4, -2, 5]
}]
});
});
});​
pointWidth is what you require to set the width of the bars. try
plotOptions: {
series: {
pointWidth: 15
}
}
This display bars with the width of 15px. Play around here. Just made an edit to the already existing code.
I use a set of area charts to simulate a variable-width-column/bar-chart. Say, each column/bar is represented by a rectangle area.
See my fiddle demo (http://jsfiddle.net/calfzhou/TUt2U/).
$(function () {
var rawData = [
{ name: 'A', x: 5.2, y: 5.6 },
{ name: 'B', x: 3.9, y: 10.1 },
{ name: 'C', x: 11.5, y: 1.2 },
{ name: 'D', x: 2.4, y: 17.8 },
{ name: 'E', x: 8.1, y: 8.4 }
];
function makeSeries(listOfData) {
var sumX = 0.0;
for (var i = 0; i < listOfData.length; i++) {
sumX += listOfData[i].x;
}
var gap = sumX / rawData.length * 0.2;
var allSeries = []
var x = 0.0;
for (var i = 0; i < listOfData.length; i++) {
var data = listOfData[i];
allSeries[i] = {
name: data.name,
data: [
[x, 0], [x, data.y],
{
x: x + data.x / 2.0,
y: data.y,
dataLabels: { enabled: true, format: data.x + ' x {y}' }
},
[x + data.x, data.y], [x + data.x, 0]
],
w: data.x,
h: data.y
};
x += data.x + gap;
}
return allSeries;
}
$('#container').highcharts({
chart: { type: 'area' },
xAxis: {
tickLength: 0,
labels: { enabled: false}
},
yAxis: {
title: { enabled: false}
},
plotOptions: {
area: {
marker: {
enabled: false,
states: {
hover: { enabled: false }
}
}
}
},
tooltip: {
followPointer: true,
useHTML: true,
headerFormat: '<span style="color: {series.color}">{series.name}</span>: ',
pointFormat: '<span>{series.options.w} x {series.options.h}</span>'
},
series: makeSeries(rawData)
});
});
Fusioncharts probably is the best option if you have a license for it to do the more optimal Marimekko charts…
I've done a little work trying to get a Marimekko charts solution in highcharts. It's not perfect, but approximates the first Marimekko charts example found here on the Fusion Charts page…
http://www.fusioncharts.com/resources/chart-tutorials/understanding-the-marimekko-chart/
The key is to use a dateTime axis, as that mode provides you more flexibility for the how you distribute points and line on the X axis which provides you the ability to have variably sized "bars" that you can construct on this axis. I use 0-1000 second space and outside the chart figure out the mappings to this scale to approximate percentage values to pace your vertical lines. Here ( http://jsfiddle.net/miken/598d9/2/ ) is a jsfiddle example that creates a variable width column chart.
$(function () {
var chart;
Highcharts.setOptions({
colors: [ '#75FFFF', '#55CCDD', '#60DD60' ]
});
$(document).ready(function() {
var CATEGORY = { // number out of 1000
0: '',
475: 'Desktops',
763: 'Laptops',
1000: 'Tablets'
};
var BucketSize = {
0: 475,
475: 475,
763: 288,
1000: 237
};
chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
type: 'area'
},
title: {
text: 'Contribution to Overall Sales by Brand & Category (in US$)<br>(2011-12)'
},
xAxis: {
min: 0,
max: 1000,
title: {
text: '<b>CATEGORY</b>'
},
tickInterval: 1,
minTickInterval: 1,
dateTimeLabelFormats: {
month: '%b'
},
labels: {
rotation: -60,
align: 'right',
formatter: function() {
if (CATEGORY[this.value] !== undefined) {
return '<b>' + CATEGORY[this.value] + ' (' +
this.value/10 + '%)</b>';
}
}
}
},
yAxis: {
max: 100,
gridLineWidth: 0,
title: {
text: '<b>% Share</b>'
},
labels: {
formatter: function() {
return this.value +'%'
}
}
},
tooltip: {
shared: true,
useHTML: true,
formatter: function () {
var result = 'CATEGORY: <b>' +
CATEGORY[this.x] + ' (' + Highcharts.numberFormat(BucketSize[this.x]/10,1) + '% sized bucket)</b><br>';
$.each(this.points, function(i, datum) {
if (datum.point.y !== 0) {
result += '<span style="color:' +
datum.series.color + '"><b>' +
datum.series.name + '</b></span>: ' +
'<b>$' + datum.point.y + 'K</b> (' +
Highcharts.numberFormat(
datum.point.percentage,2) +
'%)<br/>';
}
});
return (result);
}
},
plotOptions: {
area: {
stacking: 'percent',
lineColor: 'black',
lineWidth: 1,
marker: {
enabled: false
},
step: true
}
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'top',
x: 0,
y: 100,
borderWidth: 1,
title: {
text : 'Brand:'
}
},
series: [ {
name: 'HP',
data: [
[0,298],
[475,109],
[763,153],
[1000,153]
]
}, {
name: 'Dell',
data: [
[0,245],
[475,198],
[763,120],
[1000,120]
]
}, {
name: 'Sony',
data: [
[0,335],
[475,225],
[763,164],
[1000,164]
]
}]
},
function(chart){
// Render bottom line.
chart.renderer.path(['M', chart.plotLeft, chart.plotHeight + 66, 'L', chart.plotLeft+chart.plotWidth, chart.plotHeight + 66])
.attr({
'stroke-width': 3,
stroke: 'black',
zIndex:50
})
.add();
for (var category_idx in CATEGORY) {
chart.renderer.path(['M', (Math.round((category_idx / 1000) * chart.plotWidth)) + chart.plotLeft, 66, 'V', chart.plotTop + chart.plotHeight])
.attr({
'stroke-width': 1,
stroke: 'black',
zIndex:4
})
.add();
}
});
});
});
It adds an additional array to allow you to map category names to second tic values to give you a more "category" view that you might want. I've also added code at the bottom that adds vertical dividing lines between the different columns and the bottom line of the chart. It might need some tweaks for the size of your surrounding labels, etc. that I've hardcoded in pixels here as part of the math, but it should be doable.
Using a 'percent' type accent lets you have the y scale figure out the percentage totals from the raw data, whereas as noted you need to do your own math for the x axis. I'm relying more on a tooltip function to provide labels, etc than labels on the chart itself.
Another big improvement on this effort would be to find a way to make the tooltip hover area and labels to focus and be centered and encompass the bar itself instead of the right border of each bar that it is now. If someone wants to add that, feel free to here.
If I got it right you want every single bar to be of different width. I had same problem and struggled a lot to find a library offering this option. I came to the conclusion - there's none.
Anyways, I played with highcharts a little, got creative and came up with this:
You mentioned that you'd like your data to look something like this: data: [[5, 3.4], [3, 6], [4, 3.4]], with the first value being the height and the second being the width.
Let's do it using the highcharts' column graph.
Step 1:
To better differentiate the bars, input each bar as a new series. Since I generated my data dynamically, I had to assign new series dynamically:
const objects: any = [];
const extra = this.data.length - 1;
this.data.map((range) => {
const obj = {
type: 'column',
showInLegend: false,
data: [range[1]],
animation: true,
borderColor: 'black',
borderWidth: 1,
color: 'blue'
};
for (let i = 0; i < extra; i++) {
obj.data.push(null);
}
objects.push(obj);
});
this.chartOptions.series = objects;
That way your different series would look something like this:
series: [{
type: 'column',
data: [5, 3.4]
}, {
type: 'column',
data: [3, 6]
}, {
type: 'column',
data: [4, 3.4]
}]
Step 2:
Assign this as plot options for highcharts:
plotOptions: {
column: {
pointPadding: 0,
borderWidth: 0,
groupPadding: 0,
shadow: false
}
}
Step 3:
Now let's get creative - to have the same starting point for all bars, we need to move every single one to the graph's start:
setColumnsToZero() {
this.data.map((item, index) => {
document.querySelector('.highcharts-series-' + index).children[0].setAttribute('x', '0');
});
}
Step 4:
getDistribution() {
let total = 0;
// Array including all of the bar's data: [[5, 3.4], [3, 6], [4, 3.4]]
this.data.map(item => {
total = total + item[0];
});
// MARK: Get xAxis' total width
const totalWidth = document.querySelector('.highcharts-axis-line').getBoundingClientRect().width;
let pos = 0;
this.data.map((item, index) => {
const start = item[0];
const width = (start * totalWidth) / total;
document.querySelector('.highcharts-series-' + index).children[0].setAttribute('width', width.toString());
document.querySelector('.highcharts-series-' + index).children[0].setAttribute('x', pos.toString());
pos = pos + width;
this.getPointsPosition(index, totalWidth, total);
});
}
Step 4:
Let's get to the xAxis' points. In the first functions modify the already existing points, move the last point to the end of the axis and hide the others. In the second function we clone the last point, modify it to have either 6 or 3 total xAxis points and move each of them to the correct position
getPointsPosition(index, totalWidth, total) {
const col = document.querySelector('.highcharts-series-' + index).children[0];
const point = (document.querySelector('.highcharts-xaxis-labels').children[index] as HTMLElement);
const difference = col.getBoundingClientRect().right - point.getBoundingClientRect().right;
const half = point.getBoundingClientRect().width / 2;
if (index === this.data.length - 1) {
this.cloneNode(point, difference, totalWidth, total);
} else {
point.style.display = 'none';
}
point.style.transform = 'translateX(' + (+difference + +half) + 'px)';
point.innerHTML = total.toString();
}
cloneNode(ref: HTMLElement, difference, totalWidth, total) {
const width = document.documentElement.getBoundingClientRect().width;
const q = total / (width > 1000 && ? 6 : 3);
const w = totalWidth / (width > 1000 ? 6 : 3);
let val = total;
let valW = 0;
for (let i = 0; i < (width > 1000 ? 6 : 3); i++) {
val = val - q;
valW = valW + w;
const clone = (ref.cloneNode(true) as HTMLElement);
document.querySelector('.highcharts-xaxis-labels').appendChild(clone);
const half = clone.getBoundingClientRect().width / 2;
clone.style.transform = 'translateX(' + (-valW + difference + half) + 'px)';
const inner = Math.round(val * 100) / 100;
clone.innerHTML = inner.toString();
}
}
In the end we have a graph looking something like this (not the data from this given example, but for [[20, 0.005], [30, 0.013333333333333334], [20, 0.01], [30, 0.005555555555555555], [20, 0.006666666666666666]] with the first value being the width and the second being the height):
There might be some modifications to do to 100% fit your case. F.e. I had to adjust the xAxis' points a specific starting and end point - I spared this part.

Categories