Setting formatter callback using highcharts-convert.js - highcharts

I am using highcharts-convert.js to create PDF charts on the serverside. I want to set the dataLabel formatter for a column chart (http://api.highcharts.com/highcharts#plotOptions.column.dataLabels). As this is a function, I can't write it in my output json file that is the input for highcharts-convert.js. So I assume I need to put it in my callback.js and call it like:
highcharts-convert.js -infile infile.js -outfile outfile.pdf -width 1000 -callback callback.js
But I can't set the formatter function in callback.js. I tried this:
function( chart ) {
var labelFormatter = function(p) {
if (this.y == 0) {
return '';
}
else {
return this.y
}
};
chart.series.dataLabels.formatter = labelFormatter;
}
But that gives me this error:
TypeError: 'undefined' is not an object (evaluating 'chart.series.dataLabels.formatter = labelFormatter')
I tried putting the formatter function outside the definition of the callback, but highcharts-convert.js won't accept that. How do I set a dataLabel formatter callback for highcharts-convert.js?

Well, input file for Highcharts exporting server doesn't have to be true JSON. In general, function in that JSON are supported. Just use that as example:
{"series": [{"data": [29.9, 71.5, 106.4], "dataLabels": { "enabled": true, "formatter":function(p) {if (this.y == 0) {return '';} else {return this.y+'a';}}}}]}

if you use callback function at serverside, Use this.
function (chart) {
options = chart.options;
var labelFormatter = function() {
if (this.y == 0) return '';
else return this.y;
}
options.yAxis[0].labels = new Object();
options.yAxis[0].labels.formatter = labelFormatter;
chart = new Highcharts.Chart(options);
chart.redraw();
}

Related

Highcharts line chart with a connector between series and label

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.

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.

Showing Tool-tip & Cross-hair on two independent graphs from a single hover event?

Good day,
We are trying to develop a report which contains two charts. Each chart shows different metrics on the same timescale (unix timestamp). The aim is to show the tooltip & cross hair on both charts regardless of which you hover over.
In my head I would like to get the xAxis timestamp (time) from the tooltip hover event on chart A. I would then fetch the associated series on chart B and trigger the tooltip refresh event.
As it stands, it looks like highcharts only accepts points.
I've added a mouseOver/Out event to both charts. This obtains the hoverPoints
plotOptions: {
series: {
point: {
events: {
mouseOver: function () {
$el.trigger('tooltip-open', {"$sourceEl": $el[0], "point": this});
},
mouseOut: function () {
$el.trigger('tooltip-close', {"$sourceEl": $el[0], "point": this});
}
}
}
}
}
// Where $el is the highcharts container
// ..and tool-tip-XXXXX is a custom jQuery event.
My temporary work around is:
$el.bind('tooltip-open', function (e, data) {
var $el = $(this);
if (data.$sourceEl.id == $el.id) {
return;
}
var chart = $el.highcharts();
var points = $(chart.series).map(function () {
return this.points
}).get();
var pointsForTimestamp = $.grep(points, function (p) {
return p.x == data.point.x;
});
$(pointsForTimestamp).each(function () {
this.setState('hover');
});
chart.tooltip.refresh(pointsForTimestamp);
});
$el.bind('tooltip-close', function (e, data) {
var $el = $(this);
if (data.$sourceEl.id == this.id) {
return;
}
var chart = $el.highcharts();
chart.tooltip.hide();
});
var linkGraphs = function ($elA, $elB) {
function isEventHandled(data){
data.count = data.count || 0;
if(data.count > 0){
return true
}else{
data.count++;
return false;
}
}
function bindToOpen($elA, $elB) {
$elA.bind('tooltip-open', function (e, data) {
if(!isEventHandled(data)){
$elB.trigger('tooltip-open', data);
}
});
}
function bindToClose($elA, $elB) {
$elA.bind('tooltip-close', function (e, data) {
if(!isEventHandled(data)){
$elB.trigger('tooltip-close', data);
}
});
}
bindToOpen($elA, $elB);
bindToOpen($elB, $elA);
bindToClose($elA, $elB);
bindToClose($elB, $elA);
};

Highcharts: How to access range values in formatter for "columnrange" type?

I want to customize the tooltips of my Highcharts graph. The y-axis is of type "columnrange", i.e. it has an interval for the y-value:
series: [{
data: [
[-0.547571175, 0.401498266],
[-0.960011899, 0.444655955],
[-0.660727717, 0.862639688],
[-0.446911722, 0.660380453],
[-0.863925256, 0.619544707],
.......
]
}]
The tooltip formatter should look something like this:
tooltip: {
formatter: function() {
var point = this.points[0];
return '<b>'+ point.x +'</b><br />Interval:'+ point.low +' - '+ point.high;
},
shared: true
}
But point.low and point.high are not defined... How to get these low/high values?
Here you find a sample graph: http://jsfiddle.net/dmN3N/21/
In case when you use shared option in tooltip then you need to use this.points[0] etc, if you have shared option disabled you should use this.point
Take look at example shared
http://jsfiddle.net/dmN3N/24/
tooltip: {
shared:true,
valueSuffix: '',
formatter:function(){
return 'LOW: '+this.points[0].point.low+' HIGH: '+this.points[0].point.high ;
}
},
and not shared
http://jsfiddle.net/dmN3N/22/
tooltip: {
valueSuffix: '',
formatter:function(){
return 'LOW: '+this.point.low+' HIGH: '+this.point.high;
}
},
This code works fine for the formatter of tooltips:
tooltip: {
formatter: function() {
var point = this.points[0];
return '<b>'+ point.x +'</b><br />Interval:'+ point.series.data[0].low +' - '+ point.series.data[0].high;
},
shared: true
}
See here for a sample: http://jsfiddle.net/c2gVe/1/
To use the basic pointFormat property you can do:
tooltip: { pointFormat: '{point.low}' }
Aren't they just point[0] and point[1] for low and high respectively?
Try this, for your formatter function:
formatter: function() {
var lowPoint = getLow(this.series.data, 'x');
var highPoint = getHigh(this.series.data, 'x');
return '<b>'+ this.x +'</b><br />Interval:'+ lowPoint +' - '+ highPoint;
}
The getLow() and getHigh() functions simply take the list of points and returns the low/high points respectively.
Here is one way of implementing it, but you're free to use whatever algorithm you like.
I'm assuming you wanted the highest/lowest x value (you can easily get y by passing in 'y' as axis argument):
var getLow = function(list, axis) {
var low = null;
for (var i = 0; i < list.length; i++){
if (low !== null) {
low = list[i][axis] < low ? list[i][axis] : low;
} else {
low = list[i][axis];
}
}
return low;
};
var getHigh = function(list, axis) {
var high = null;
for (var i = 0; i < list.length; i++){
if (high !== null) {
high = list[i][axis] > high ? list[i][axis] : high;
} else {
high = list[i][axis];
}
}
return high;
};
Here is the fiddle to demonstrate: http://jsfiddle.net/tKB7y/

Resources