Highcharts line chart with a connector between series and label - highcharts

I am using the Highcharts Editor with some Custom code and I have a line chart with over 20 series. Each series has a label at the end (series name). Because the series are so close to each other, it is impossible to show all the corresponding labels as they overlap. I'd like to enable a connector line between each series and corresponding label.
I tried connectorAllowed: true and enabled: true but from the JS API Reference web-page these options only apply to pie charts.
Here is my code:
<div id="highcharts-3fa2171d-2ddd-49f5-898d-daef17b342b4"></div><script>
(function(){ var files = ["https://code.highcharts.com/stock/highstock.js","https://code.highcharts.com/highcharts-more.js","https://code.highcharts.com/highcharts-3d.js","https://code.highcharts.com/modules/data.js","https://code.highcharts.com/modules/exporting.js","https://code.highcharts.com/modules/funnel.js","https://code.highcharts.com/modules/annotations.js","https://code.highcharts.com/modules/solid-gauge.js"],loaded = 0; if (typeof window["HighchartsEditor"] === "undefined") {window.HighchartsEditor = {ondone: [cl],hasWrapped: false,hasLoaded: false};include(files[0]);} else {if (window.HighchartsEditor.hasLoaded) {cl();} else {window.HighchartsEditor.ondone.push(cl);}}function isScriptAlreadyIncluded(src){var scripts = document.getElementsByTagName("script");for (var i = 0; i < scripts.length; i++) {if (scripts[i].hasAttribute("src")) {if ((scripts[i].getAttribute("src") || "").indexOf(src) >= 0 || (scripts[i].getAttribute("src") === "http://code.highcharts.com/highcharts.js" && src === "https://code.highcharts.com/stock/highstock.js")) {return true;}}}return false;}function check() {if (loaded === files.length) {for (var i = 0; i < window.HighchartsEditor.ondone.length; i++) {try {window.HighchartsEditor.ondone[i]();} catch(e) {console.error(e);}}window.HighchartsEditor.hasLoaded = true;}}function include(script) {function next() {++loaded;if (loaded < files.length) {include(files[loaded]);}check();}if (isScriptAlreadyIncluded(script)) {return next();}var sc=document.createElement("script");sc.src = script;sc.type="text/javascript";sc.onload=function() { next(); };document.head.appendChild(sc);}function each(a, fn){if (typeof a.forEach !== "undefined"){a.forEach(fn);}else{for (var i = 0; i < a.length; i++){if (fn) {fn(a[i]);}}}}var inc = {},incl=[]; each(document.querySelectorAll("script"), function(t) {inc[t.src.substr(0, t.src.indexOf("?"))] = 1; }); function cl() {if(typeof window["Highcharts"] !== "undefined"){var options={"title":{"text":"30-day female mortality rates at age 45"},"subtitle":{"text":"Calendar period: 1996 - 2014"},"exporting":{},"chart":{"type":"line","height":null,"ignoreHiddenSeries":true,"showAxes":false,"spacingRight":75,"spacingTop":10},"series":[{"name":"NHL","turboThreshold":0,"_colorIndex":0,"_symbolIndex":0,"dataLabels":{"allowOverlap":false},"marker":{},"color":null},{"name":"bladder","turboThreshold":0,"_colorIndex":1,"_symbolIndex":1},{"name":"brain","turboThreshold":0,"_colorIndex":2,"_symbolIndex":2},{"name":"breast","turboThreshold":0,"_colorIndex":3,"_symbolIndex":3},{"name":"cervix","turboThreshold":0,"_colorIndex":4,"_symbolIndex":4},{"name":"colon","turboThreshold":0,"_colorIndex":5,"_symbolIndex":0},{"name":"hodgkin","turboThreshold":0,"_colorIndex":6,"_symbolIndex":1},{"name":"kidney","turboThreshold":0,"_colorIndex":7,"_symbolIndex":2},{"name":"larynx","turboThreshold":0,"_colorIndex":8,"_symbolIndex":3},{"name":"leukaemia","turboThreshold":0,"_colorIndex":9,"_symbolIndex":4,"marker":{},"color":"#546e7a"},{"name":"liver","turboThreshold":0,"_colorIndex":0,"_symbolIndex":0},{"name":"lung","turboThreshold":0,"_colorIndex":1,"_symbolIndex":1,"dataLabels":{"allowOverlap":true,"enabled":true},"allowOverlapX":false},{"name":"melanoma","turboThreshold":0,"_colorIndex":2,"_symbolIndex":2},{"name":"mesothelioma","turboThreshold":0,"_colorIndex":3,"_symbolIndex":3},{"name":"myeloma","turboThreshold":0,"_colorIndex":4,"_symbolIndex":4},{"name":"oesophagus","turboThreshold":0,"_colorIndex":5,"_symbolIndex":0},{"name":"ovary","turboThreshold":0,"_colorIndex":6,"_symbolIndex":1},{"name":"pancreas","turboThreshold":0,"_colorIndex":7,"_symbolIndex":2},{"name":"rectum","turboThreshold":0,"_colorIndex":8,"_symbolIndex":3,"dataLabels":{"allowOverlap":true}},{"name":"stomach","turboThreshold":0,"_colorIndex":9,"_symbolIndex":4,"marker":{},"color":"#651fff"},{"name":"thyroid","turboThreshold":0,"_colorIndex":0,"_symbolIndex":0,"marker":{},"color":"#cddc39"},{"name":"uterus","turboThreshold":0,"_colorIndex":1,"_symbolIndex":1,"marker":{},"color":"#d500f9"}],"plotOptions":{"series":{"dataLabels":{"enabled":true},"animation":false},"line":{"dataLabels":{"allowOverlap":true,"enabled":false,"inside":false,"shadow":false,"align":"left","crop":false,"padding":5,"verticalAlign":"middle","overflow":"none","rotation":0,"x":2,"y":0,"zIndex":6},"label":{"connectorNeighbourDistance":24,"connectorAllowed":true,"enabled":true,"onArea":false},"marker":{"enabled":true}}},"pane":{"background":[]},"responsive":{"rules":[]},"xAxis":[{"type":"category","tickmarkPlacement":"on","title":{"text":"Calendar period"},"labels":{},"opposite":false}],"yAxis":[{"title":{"text":"Mortality"},"labels":{},"type":"linear"}],"legend":{"layout":"vertical","align":"right","enabled":false,"floating":false},"credits":{"enabled":false},"tooltip":{},"data":{"csv":"\"year\";\"NHL\";\"bladder\";\"brain\";\"breast\";\"cervix\";\"colon\";\"hodgkin\";\"kidney\";\"larynx\";\"leukaemia\";\"liver\";\"lung\";\"melanoma\";\"mesothelioma\";\"myeloma\";\"oesophagus\";\"ovary\";\"pancreas\";\"rectum\";\"stomach\";\"thyroid\";\"uterus\"\n1996;2.2;1;4.8;0.7;1.3;2.5;1;2.9;1.2;3.8;7.5;5.5;0.2;2.4;2.8;2.4;2.9;6.6;1.4;3.6;1.5;0.7\n1997;2.1;1;4.6;0.6;1.3;2.3;1;2.7;1.1;3.7;7.2;5.3;0.2;2.3;2.6;2.3;2.8;6.4;1.3;3.5;1.4;0.7\n1998;2;1;4.4;0.6;1.2;2.2;1;2.6;1.1;3.5;6.9;5.1;0.2;2.2;2.4;2.2;2.7;6.2;1.3;3.3;1.4;0.7\n1999;1.9;1;4.2;0.5;1.2;2.1;0.9;2.4;1;3.4;6.7;4.9;0.2;2.1;2.2;2.1;2.6;6.1;1.2;3.2;1.3;0.6\n2000;1.9;1;4;0.5;1.2;2;0.9;2.3;1;3.3;6.4;4.7;0.2;2.1;2.1;2;2.5;5.9;1.2;3.1;1.3;0.6\n2001;1.8;1;3.8;0.5;1.1;1.9;0.8;2.1;1;3.2;6.2;4.5;0.2;2;1.9;2;2.5;5.8;1.1;3;1.2;0.6\n2002;1.7;1;3.6;0.4;1.1;1.8;0.8;2;0.9;3.1;6;4.4;0.2;1.9;1.8;1.9;2.4;5.6;1.1;2.9;1.2;0.5\n2003;1.7;1;3.5;0.4;1.1;1.7;0.8;1.9;0.9;3;5.8;4.2;0.2;1.9;1.6;1.8;2.3;5.5;1;2.8;1.1;0.5\n2004;1.6;0.9;3.3;0.4;1;1.6;0.7;1.8;0.9;2.9;5.6;4.1;0.1;1.8;1.5;1.7;2.2;5.3;1;2.7;1.1;0.5\n2005;1.5;0.9;3.1;0.3;1;1.6;0.7;1.7;0.8;2.8;5.4;3.9;0.1;1.7;1.4;1.7;2.1;5.2;0.9;2.6;1;0.5\n2006;1.5;0.9;3;0.3;1;1.5;0.7;1.6;0.8;2.7;5.2;3.8;0.1;1.7;1.3;1.6;2;5.1;0.9;2.5;1;0.5\n2007;1.4;0.9;2.8;0.3;0.9;1.4;0.6;1.5;0.8;2.6;5;3.6;0.1;1.6;1.2;1.5;2;4.9;0.9;2.4;0.9;0.4\n2008;1.4;0.9;2.7;0.3;0.9;1.3;0.6;1.4;0.7;2.5;4.8;3.5;0.1;1.6;1.1;1.5;1.9;4.8;0.8;2.3;0.9;0.4\n2009;1.3;0.9;2.6;0.2;0.9;1.3;0.6;1.3;0.7;2.5;4.6;3.4;0.1;1.5;1;1.4;1.8;4.7;0.8;2.2;0.9;0.4\n2010;1.3;0.9;2.5;0.2;0.8;1.2;0.6;1.3;0.7;2.4;4.4;3.2;0.1;1.5;1;1.4;1.8;4.6;0.7;2.2;0.8;0.4\n2011;1.2;0.9;2.3;0.2;0.8;1.2;0.5;1.2;0.6;2.3;4.3;3.1;0.1;1.4;0.9;1.3;1.7;4.4;0.7;2.1;0.8;0.4\n2012;1.2;0.9;2.2;0.2;0.8;1.1;0.5;1.1;0.6;2.2;4.1;3;0.1;1.4;0.8;1.3;1.6;4.3;0.7;2;0.8;0.4\n2013;1.1;0.9;2.1;0.2;0.7;1;0.5;1;0.6;2.2;4;2.9;0.1;1.3;0.8;1.2;1.6;4.2;0.6;1.9;0.7;0.3\n2014;1.1;0.9;2;0.2;0.7;1;0.5;1;0.6;2.1;3.8;2.8;0.1;1.3;0.7;1.2;1.5;4.1;0.6;1.9;0.7;0.3","googleSpreadsheetKey":false,"googleSpreadsheetWorksheet":false}};
Highcharts.merge(true, options, {
plotOptions: {
series: {
dataLabels: {
enabled: true,
connectorAllowed: true,
formatter: function () {
// if last point
if(this.point === this.series.data[this.series.data.length-1]) {
return '<span style="color:'+this.series.color+'">'+this.series.name;
}
}
}
}
}
});
new Highcharts.Chart("highcharts-3fa2171d-2ddd-49f5-898d-daef17b342b4",
options);}}})();
</script>
I would like to have a connector line between each series and the corresponding label in order to distinguish between each series and their names.
Your help is much appreciated!!!
Here is a JSfiddle link with my code.

Related

How to disable and enable legend items in highcharts

Trying to disable legend items in highcharts, But not able to working.
Example :
If I click Movie1 or Movie2 legend, only Movie1 and Movie2 should be enabled others Game1 and Game2 should be disabled.
If I click Game1 or Game2 legend, only Game1 and Game2 should be enabled others Movie1 and Movie2 should be disabled.
How to do it? How to use this.visible=false; for legend items?
series: {
events: {
legendItemClick: function(e) { console.log(this.name);
const series = this;
const chart = series.chart;
const hasChangedOpacity = series.hasChangedOpacity;
if(this.name == 'Movie1' || this.name == 'Movie2'){
this.name.Movie1 = true; //enabled
this.name.Movie2 = true; //enabled
this.name.Game1 = false; //disabled
this.name.Game2 = false; //disabled
}
if(this.name == 'Game1' || this.name == 'Game2'){
this.name.Movie1 = false; //disabled
this.name.Movie2 = false; //disabled
this.name.Game1 = true; //enabled
this.name.Game2 = true; //enabled
}
}
}
},
https://www.highcharts.com/demo/column-basic
Demo: https://jsfiddle.net/gd3mz8pf/9/
First of all, you can use linkedTo series property to use one legend item for multiple series.
If you want to have separate legend items, you need to trigger the same operations for all of them. For example:
series: {
events: {
legendItemClick: function(e) {
const series = this;
const chart = series.chart;
const allSeries = chart.series;
const hasChangedOpacity = series.hasChangedOpacity;
const handleItemClick = (s) => {
s.group.attr({
opacity: hasChangedOpacity ? 1 : 0.2
});
chart.legend.colorizeItem(s, hasChangedOpacity);
s.hasChangedOpacity = !hasChangedOpacity;
};
if ([0, 1].includes(series.index)) {
handleItemClick(allSeries[0]);
handleItemClick(allSeries[1]);
} else {
handleItemClick(allSeries[2]);
handleItemClick(allSeries[3]);
}
// prevent default behaviour
return false;
}
}
}
Live demo: https://jsfiddle.net/BlackLabel/vkt6uf5b/
API Reference: https://api.highcharts.com/highcharts/series.column.linkedTo

Highstock: export CVS/XML only visible/selected range

I'm facing a problem when import Highstock. I'd like to import csv/xml in a highstock graph.
It's work fine but is getting the total of data present in chart. I'd like to export CVS/XML only visible/selected range.
Here what I found in Highstock forum:
https://www.highcharts.com/forum/viewtopic.php?f=12&t=41056
jsfiddle: jsfiddle example
The example in JSFiddle works but is getting an number in Unix Timestemp.
var defined = Highcharts.defined,
each = Highcharts.each,
pick = Highcharts.pick,
win = Highcharts.win,
doc = win.document,
seriesTypes = Highcharts.seriesTypes,
downloadAttrSupported = doc.createElement('a').download !== undefined;
Highcharts.Chart.prototype.getDataRows = function (multiLevelHeaders) {
var time = this.time,
csvOptions = (this.options.exporting && this.options.exporting.csv) ||
{},
xAxis,
xAxes = this.xAxis,
rows = {},
rowArr = [],
dataRows,
topLevelColumnTitles = [],
columnTitles = [],
columnTitleObj,
i,
x,
xTitle,
// Options
columnHeaderFormatter = function (item, key, keyLength) {
if (csvOptions.columnHeaderFormatter) {
var s = csvOptions.columnHeaderFormatter(item, key, keyLength);
if (s !== false) {
return s;
}
}
if (!item) {
return 'Category';
}
if (item instanceof Highcharts.Axis) {
return (item.options.title && item.options.title.text) ||
(item.isDatetimeAxis ? 'DateTime' : 'Category');
}
if (multiLevelHeaders) {
return {
columnTitle: keyLength > 1 ? key : item.name,
topLevelColumnTitle: item.name
};
}
return item.name + (keyLength > 1 ? ' (' + key + ')' : '');
},
xAxisIndices = [];
// Loop the series and index values
i = 0;
this.setUpKeyToAxis();
each(this.series, function (series) {
var keys = series.options.keys,
pointArrayMap = keys || series.pointArrayMap || ['y'],
valueCount = pointArrayMap.length,
xTaken = !series.requireSorting && {},
categoryMap = {},
datetimeValueAxisMap = {},
xAxisIndex = Highcharts.inArray(series.xAxis, xAxes),
mockSeries,
j;
// Map the categories for value axes
each(pointArrayMap, function (prop) {
var axisName = (
(series.keyToAxis && series.keyToAxis[prop]) ||
prop
) + 'Axis';
categoryMap[prop] = (
series[axisName] &&
series[axisName].categories
) || [];
datetimeValueAxisMap[prop] = (
series[axisName] &&
series[axisName].isDatetimeAxis
);
});
if (
series.options.includeInCSVExport !== false &&
!series.options.isInternal &&
series.visible !== false // #55
) {
// Build a lookup for X axis index and the position of the first
// series that belongs to that X axis. Includes -1 for non-axis
// series types like pies.
if (!Highcharts.find(xAxisIndices, function (index) {
return index[0] === xAxisIndex;
})) {
xAxisIndices.push([xAxisIndex, i]);
}
// Compute the column headers and top level headers, usually the
// same as series names
j = 0;
while (j < valueCount) {
columnTitleObj = columnHeaderFormatter(
series,
pointArrayMap[j],
pointArrayMap.length
);
columnTitles.push(
columnTitleObj.columnTitle || columnTitleObj
);
if (multiLevelHeaders) {
topLevelColumnTitles.push(
columnTitleObj.topLevelColumnTitle || columnTitleObj
);
}
j++;
}
mockSeries = {
chart: series.chart,
autoIncrement: series.autoIncrement,
options: series.options,
pointArrayMap: series.pointArrayMap
};
// Export directly from options.data because we need the uncropped
// data (#7913), and we need to support Boost (#7026).
each(series.options.data, function eachData(options, pIdx) {
var key,
prop,
val,
name,
point;
point = { series: mockSeries };
series.pointClass.prototype.applyOptions.apply(
point,
[options]
);
if (point.x >= series.xAxis.min && point.x <= series.xAxis.max) {
key = point.x;
name = series.data[pIdx] && series.data[pIdx].name;
if (xTaken) {
if (xTaken[key]) {
key += '|' + pIdx;
}
xTaken[key] = true;
}
j = 0;
// Pies, funnels, geo maps etc. use point name in X row
if (!series.xAxis || series.exportKey === 'name') {
key = name;
}
//console.log(point)
if (!rows[key]) {
// Generate the row
rows[key] = [];
// Contain the X values from one or more X axes
rows[key].xValues = [];
}
rows[key].x = point.x;
rows[key].name = name;
rows[key].xValues[xAxisIndex] = point.x;
while (j < valueCount) {
prop = pointArrayMap[j]; // y, z etc
val = point[prop];
rows[key][i + j] = pick(
categoryMap[prop][val], // Y axis category if present
datetimeValueAxisMap[prop] ?
time.dateFormat(csvOptions.dateFormat, val) :
null,
val
);
j++;
}
}
});
i = i + j;
}
});
// Make a sortable array
for (x in rows) {
if (rows.hasOwnProperty(x)) {
rowArr.push(rows[x]);
}
}
var xAxisIndex, column;
// Add computed column headers and top level headers to final row set
dataRows = multiLevelHeaders ? [topLevelColumnTitles, columnTitles] :
[columnTitles];
i = xAxisIndices.length;
while (i--) { // Start from end to splice in
xAxisIndex = xAxisIndices[i][0];
column = xAxisIndices[i][1];
xAxis = xAxes[xAxisIndex];
// Sort it by X values
rowArr.sort(function (a, b) { // eslint-disable-line no-loop-func
return a.xValues[xAxisIndex] - b.xValues[xAxisIndex];
});
// Add header row
xTitle = columnHeaderFormatter(xAxis);
dataRows[0].splice(column, 0, xTitle);
if (multiLevelHeaders && dataRows[1]) {
// If using multi level headers, we just added top level header.
// Also add for sub level
dataRows[1].splice(column, 0, xTitle);
}
// Add the category column
each(rowArr, function (row) { // eslint-disable-line no-loop-func
var category = row.name;
if (xAxis && !defined(category)) {
if (xAxis.isDatetimeAxis) {
if (row.x instanceof Date) {
row.x = row.x.getTime();
}
category = time.dateFormat(
csvOptions.dateFormat,
row.x
);
} else if (xAxis.categories) {
category = pick(
xAxis.names[row.x],
xAxis.categories[row.x],
row.x
);
} else {
category = row.x;
}
}
// Add the X/date/category
row.splice(column, 0, category);
});
}
dataRows = dataRows.concat(rowArr);
Highcharts.fireEvent(this, 'exportData', { dataRows: dataRows });
return dataRows;
};
var chart = new Highcharts.StockChart({
chart: {
renderTo: 'container'
},
navigator: {
series: {
includeInCSVExport: false
}
},
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, 32, 221, 123, 321, 322, 29, 177, 76, 46, 245, 122, 67],
pointStart: Date.UTC(2013, 0, 1),
pointInterval: 24 * 36e5
}],
exporting: {
csv: {
dateFormat: '%Y-%m-%d'
}
}
});
document.getElementById('getcsv').addEventListener('click', function () {
alert(chart.getCSV());
});
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/modules/export-data.js"></script>
<div id="container" style="height: 300px; margin-top: 2em"></div>
<button id="getcsv">Alert CSV</button>
I got the answer.
Just include this condition inside of export-data in funciton Highcharts.Chart.prototype.getDataRows:
(...)
point = { series: mockSeries };
series.pointClass.prototype.applyOptions.apply(point, [options]);
key = point.x;
name = series.data[pIdx] && series.data[pIdx].name;
j = 0;
if (point.x >= series.xAxis.min && point.x <= series.xAxis.max) {// ***this condition
if (!xAxis || series.exportKey === "name" || (!hasParallelCoords && xAxis && xAxis.hasNames && name)) {
key = name;
}
(...)
}
If don't want change the lib following this example in fiddleJs
example Highstock period time export

allowOverlap property for Highcharts JS v3.0.10

In my project, we are using the highchart version: Highcharts JS v3.0.10.
What should be equivalent code or property which is suitable for allowOverlap = false according to the newer version?
How to achieve alloOverlap= false in the older version?
Link: https://api.highcharts.com/highcharts/series.timeline.dataLabels.allowOverlap
The code that is responsible for hiding the overlapping data labels is currently in Highcharts core: https://code.highcharts.com/highcharts.src.js, but previously it was a separate module: https://code.highcharts.com/5/modules/overlapping-datalabels.src.js
However, to make it work you would have to make many changes in your code. I think using the plugin below will be the best solution for you:
(function(H) {
var each = H.each,
extend = H.extend;
/**
* Hide overlapping labels. Labels are moved and faded in and out on zoom to provide a smooth
* visual imression.
*/
H.Series.prototype.hideOverlappingDataLabels = function() {
var points = this.points,
len = points.length,
i,
j,
label1,
label2,
intersectRect = function(pos1, pos2, size1, size2) {
return !(
pos2.x > pos1.x + size1.width ||
pos2.x + size2.width < pos1.x ||
pos2.y > pos1.y + size1.height ||
pos2.y + size2.height < pos1.y
);
};
// Mark with initial opacity
each(points, function(point, label) {
label = point.dataLabel;
if (label) {
label.oldOpacity = label.opacity;
label.newOpacity = 1;
}
});
// Detect overlapping labels
for (i = 0; i < len - 1; ++i) {
label1 = points[i].dataLabel;
for (j = i + 1; j < len; ++j) {
label2 = points[j].dataLabel;
if (label1 && label2 && label1.newOpacity !== 0 && label2.newOpacity !== 0 &&
intersectRect(label1.alignAttr, label2.alignAttr, label1, label2)) {
(points[i].labelrank < points[j].labelrank ? label1 : label2).newOpacity = 0;
}
}
}
// Hide or show
each(points, function(point, label) {
label = point.dataLabel;
if (label) {
if (label.oldOpacity !== label.newOpacity) {
label[label.isOld ? 'animate' : 'attr'](extend({
opacity: label.newOpacity
}, label.alignAttr));
}
label.isOld = true;
}
});
};
H.wrap(H.Series.prototype, 'drawDataLabels', function(proceed) {
proceed.call(this);
this.hideOverlappingDataLabels();
});
}(Highcharts));
Live demo: http://jsfiddle.net/BlackLabel/6s5ycrwa/

Synchronized charts example with Highstock crashes with "Cannot read property 'category' of undefined"

I want the functionality of the "Synchronized charts" example, but with Highstock. But when trying to accomplish this, I get "highstock.src.js:9991 Uncaught TypeError: Cannot read property 'category' of undefined"
This also holds directly for the example: http://www.highcharts.com/demo/synchronized-charts doesn't work when converted to Highstock: http://jsfiddle.net/9gq47g0w/
(Since StackOverflow demands me to post some code along with the fiddle, here's from Highstock, noting the point where it crashes with **):
/**
* Refresh the tooltip's text and position.
* #param {Object} point
*/
refresh: function (point, mouseEvent) {
...
// shared tooltip, array is sent over
if (shared && !(point.series && point.series.noSharedTooltip)) {
...
textConfig = {
x: ** point[0].category, ** <- here!
y: point[0].y
};
...
}
...
},
Here you can find an example of synchronized highstock charts:
http://jsfiddle.net/vw77cooj/20/
This example is using custom functions for synchronizing extremes and tooltips on charts:
function syncExtremes(e) {
var thisChart = this.chart;
if (e.trigger !== 'syncExtremes') { // Prevent feedback loop
Highcharts.each(Highcharts.charts, function(chart) {
if (chart !== thisChart) {
if (chart.xAxis[0].setExtremes) { // It is null while updating
chart.xAxis[0].setExtremes(e.min, e.max, undefined, false, {
trigger: 'syncExtremes'
});
}
}
});
}
}
$('#container').bind('mousemove touchmove touchstart', function(e) {
Highcharts.each(Highcharts.charts, function(chart) {
event = chart.pointer.normalize(e.originalEvent);
point = chart.series[0].searchPoint(event, true);
if (point) {
point.onMouseOver(); // Show the hover marker
chart.tooltip.refresh(point); // Show the tooltip
chart.xAxis[0].drawCrosshair(event, point); // Show the crosshair
}
});
});
In case of having multiple series on your chart you may change function responsible for synchronizing your tooltip:
function syncTooltip(container, p) {
var i = 0,
j = 0,
data,
series,
points = [];
for (; i < chartSummary.length; i++) {
if (container.id != chartSummary[i].container.id) {
series = chartSummary[i].series
Highcharts.each(series, function(s) {
Highcharts.each(s.data, function(point) {
if (point.x === p && point.series.yAxis.options.index !== 1) {
points.push(point)
}
})
});
chartSummary[i].tooltip.refresh(points);
}
};
}
http://jsfiddle.net/ZArZM/316/
For a nice working example on Highstock, please follow this example with MouseOver and MouseOut:
var onMouseOver = function onMouseOver() {
var x = this.x,
interactedChart = this.series.chart,
points = [],
charts = Highcharts.charts,
each = Highcharts.each;
each(charts, function(chart) {
if (chart !== interactedChart) {
each(chart.series, function(series) {
each(series.data, function(point) {
if (point.x === x && point.series.yAxis.options.index !== 1) {
points.push(point)
}
})
});
each(points, function(p) {
p.setState('hover');
});
chart.tooltip.refresh(points);
}
});
}
http://jsfiddle.net/ska1r5wq/
You can avoid many tooltip issues from examples provided on the web.
Source : https://github.com/highcharts/highcharts/issues/6595

Customizing default y-axis label in Highcharts

I want to prefix a $ to the default y-axis label. My bar chart is using values in the millions so the chart is returning value-MM (80MM, 30MM). What I would like to do is format the y-axis like $-value-MM ($80MMm $30MM). I have tried the code below and can't get it to work?
yAxis: [{ // Primary yAxis
labels: {
formatter: function () {
return '$' + this.value;
}
},
title: {
text: 'Revenue',
If I understand the question correctly, your data already has 'MM' suffix and you want to add the prefix '$'.
Try,
yAxis: {
labels: {
format: '${value}'
}
}
One rather elaborate way to achieve this is to re-use the code Highcharts uses in their internal defaultLabelFormatter for axis that are numeric, and use it in the axis formatter.
An example of this, with your added prefix (JSFiddle):
yAxis: {
labels: {
formatter: function() {
var numericSymbols = Highcharts.getOptions().lang.numericSymbols;
var i = numericSymbols && numericSymbols.length;
var numericSymbolDetector = this.axis.isLog ? this.value : this.axis.tickInterval;
var UNDEFINED, ret, multi;
while (i-- && ret === UNDEFINED) {
multi = Math.pow(1000, i + 1);
if (numericSymbolDetector >= multi && (this.value * 10) % multi === 0 && numericSymbols[i] !== null) {
ret = Highcharts.numberFormat(this.value / multi, -1) + numericSymbols[i];
}
}
if (ret === UNDEFINED) {
if (Math.abs(this.value) >= 10000) {
ret = Highcharts.numberFormat(this.value, -1);
} else {
ret = Highcharts.numberFormat(this.value, -1, UNDEFINED, '');
}
}
return "$"+ret; // Adding the prefix
}
},
}
A experimental short form of this would be to call the defaultLabelFormatter with the essential parts of the context it requires. An example of this (JSFiddle):
yAxis: {
labels: {
formatter: function() {
return "$" + this.axis.defaultLabelFormatter.call({
axis: this.axis,
value: this.value
});
}
},
}
As the context is incomplete it wouldn't work as expected if your axis was datetime or categories or perhaps logarithmical, but should work for numeric axis. For the full picture I suggest looking at the full defaultLabelFormatter implementation.

Resources