Highcharts - issue with tooltip formatting in synchronized charts - javascript

I am using synchronized charts using Highcharts, but I am having an issue with using the formatter function within the tooltip options. Each chart has different formatting requirements (% vs integer vs. float etc), but it seems to just take the last charts's formatting for each of the charts tooltips.
This was unexpected as I didn't have such issues for chart titles as well as y-axis formatting, which it picks up correctly. This is the code I currently have (abbreviated as it is quite lengthy otherwise):
for (var j = 0; j < json.data.length; j++) {
$('#highchart_metric' + i + '_').highcharts({
chart: {
type: 'area',
},
title: {
text: json.fmt[i].displayName,
},
xAxis: { // left out for brevity},
yAxis: {
labels: {
formatter: function () {
var label = this.axis.defaultLabelFormatter.call(this);
return numeral(label).format(json.fmt[i].format);
}
}
},
tooltip: {
formatter: function () {
return moment(this.x).format("MMM Do[,] YYYY") + ': <b>' + numeral(this.y).format(json.fmt[i].format) + '</b>';
}
}
series: [{ // left out for brevity }]
});
}
The json would be structured something along the lines of:
var json = {
data: [[/* data */][/* data */]];
fmt: [
{
col: "tfr",
displayName: "TFR",
fmt: "0,0.00"
},
{
col: "volume",
displayName: "Ticket Volume",
fmt: "0,0"
}
]
};

It looks like it is the issue with incorrect use of a callback and a for loop. Each formatter callback creates a closure which has access to the same variable i - which, when the loop ends, is equaled to json.data.length.
Use a forEach kind of loop which will create a seperate i variable for each chart.
Compare forEach http://jsfiddle.net/9eezsx7v/ with for loop http://jsfiddle.net/9eezsx7v/1/

Related

dynamically adding series to highcharts

I'm relative new to highcharts so I'm not that knowledgeable.
But what I am trying to do is that I am trying to dynamically create series with a name and some x, y values. To get the x and y values I use two dictionaries looking like this:
The X values. They are dates
The Y values. They are seconds
What I want to do is create a series that have the names of the keys and then they can have multiple points for x, y values.
So for example at "Fiber_Mätning" I have two values in the key in both dictionaries. What I want to do is that I create a series with the name "Fiber_Mätning", and then it should have two points where I give both x and y value.
So the 1st point in "Fiber_Mätning" would be x:"2020-10-28" y:"28800"
and the 2nd point in "Fiber_Mätning" would be x:"2020-10-29" y:"18000"
After that I would move on onto the next key in both dictionaries and do the same. So the way I create the series need to be a function that is dynamically creating series and the points depending on the amount of keys and values in the dictionaries.
Currently my highchart code looks like this:
$('#container').highcharts({
chart:{
type: 'column',
events:{
load: function(){
RedrawColumns(this)
},
redraw: function(){
RedrawColumns(this)
}
}
},
title:{
text: 'Time worked by {{user_to_show}}'
},
subtitle:{
text:'{{user_to_show}}´s flex time: '
},
tooltip:{
formatter: function (){
var text = 'Date: ' + this.x + '<br>' + 'Time: ' + secondsTimeSpanToHMS(this.y/1000) +
'<br>' +'Extra info:' + {{extra_info|safe}}[this.x];
return text;
}
},
xAxis:
{
categories: {{all_dates|safe}}
},
yAxis:
[{
title:
{
text: '' //If it isnt given '' it will display "value" so using '' to hide it
},
gridLineWidth: 1,
type: 'datetime', //y-axis will be in milliseconds
dateTimeLabelFormats:
{ //force all formats to be hour:minute:second
second: '%H:%M:%S',
minute: '%H:%M:%S',
hour: '%H:%M:%S',
day: '%H:%M:%S',
week: '%H:%M:%S',
month: '%H:%M:%S',
year: '%H:%M:%S'
},
opposite: true
}],
plotOptions:
{
series:
{
dataLabels:
{
enabled: true,
formatter: function()
{
if( this.series.index == 0 )
{
return secondsTimeSpanToHMS(this.y/1000) ;
}
else
{
return this.y;
}
}
}
}
},
series:
[{
}]
});
});
(I am doing this with django, html and js)
If anything is unclear please say so and I will try to explain it in further detail.
Thanks in advance for any replies.
Here is my proposal for the solution. I think that everything is explained in the comments. If something is unclear, feel free to ask.
let data1 = {
Jarnvag_asdasd: ['2020-10-22'],
Fiber_Matning: ['2020-10-28', '2020-10-29'],
Fiber_Forarbete: ['2020-10-28', '2020-10-29'],
};
let data2 = {
Jarnvag_asdasd: [28800],
Fiber_Matning: [28800, 18000],
Fiber_Forarbete: [28800, 14400],
};
// The constructor to create the series structure
function CreateSeries(name, data) {
this.name = name;
this.data = data;
}
// series array
let series = [];
// Iterate through the data to concat them
for (let i in data1) {
let data = [];
data1[i].forEach((d, j) => {
data.push([d, data2[i][j]])
})
series.push(new CreateSeries(i, data))
}
Highcharts.chart('container', {
series: series
});
Demo: https://jsfiddle.net/BlackLabel/r7ame5gw/

Add dynamic series

I'm using Highcharts plugin and I have two array, here is an example of my arrays:
array1: Serie1, Serie2, Serie3
array2: 20, 30, 40
What I want to do is use this array in series options, array 1 as serie name and array 2 as serie value, I could do this manually but in the future maybe array1 is going to have more values and also array2.
$('#Chart').highcharts({
title: {
text: 'Example'
},
xAxis: {
labels:
{
enabled: false
}
},
yAxis: {
allowDecimals: true,
min: 0,
title: {
text: 'Results'
}
},
credits: {
enabled: false
},
legend: {
reversed: true
},
series: [{
name: array1[x],
data: array2[x].map(function (value) {
return Number(value);
})
}]
});
I tried using a for loop but I think that is not possible use this inside series option. Also I'm trying using a for loop outside the highcharts code and using a string var concatenating the above code but I'm not pretty sure about this, here is my code:
var constructor = "";
for (var i = 0; i < array1.length; i++) {
constructor += "name: '"+ array1[i] + "',";
constructor += "data: " + array2[i] + ".map(function (value) {"
constructor += "return Number(value);";
})
You could create the graph without series and then in a separate function you can do iterations on the array to get the data correctly and dynamically add the series in this way:
for (var i = 0; i < array1.length; i++) {
chart.addSeries({
name: array1[i],
data: [array2[i]]
}, false);
}
chart.redraw();
Not tested but the idea is that

How to sort XY line chart tooltip items based on value and add thousands separators?

I'm using Chart.js 2.1.4 and I would like to sort tooltip items based on the tooltip item value (yAxis value). I would also like to add thousands separator to values.
The lines in following picture should be sorted based on the value:
This adds separator for Scales: Chart.js 2.0 Formatting Y Axis with Currency and Thousands Separator
Using Documentation: http://www.chartjs.org/docs/#chart-configuration-tooltip-configuration and the Question above I have tried modifying the code for Tooltips value:
var chart_config = {
type: 'line',
data: {datasets: mydatasets},
options: {
tooltips: {
mode: 'label',
bodySpacing: 10,
titleMarginBottom: 15,
callbacks: {
beforeLabel: function(tooltipItem, data) {
var tooltipValue = tooltipItem.xLabel + ': ' + tooltipItem.yLabel.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
console.log(tooltipValue);
return tooltipValue;
},
},
},
}
};
var chartHolder = document.getElementById("chartHolder");
var chart = new Chart(chartHolder, chart_config);
The problem is it now returns only part of the tooltip items and there is error in console: TypeError: vm.labelColors[i] is undefined.
How should I format the function's return value to not have any errors and present the data as specified?
You can use the itemSort callback option for tooltips, e.g.
tooltips: {
itemSort: (a, b, data) => b.yLabel - a.yLabel
}
You have this information in the Docs: ChartJS Tooltip Configuration Callbacks
You could do this:
var myChart;
myChart = {
type: 'line',
data: {
datasets: mydatasets
},
options: {
tooltips: {
mode: 'label',
callbacks: {
title: function(tooltipItem) {
return moment(tooltipItem[0].xLabel).format('ll');
},
label: function(tooltipItem, data) {
return data.datasets[tooltipItem.datasetIndex].label + ": " + tooltipItem.yLabel;
}
}
}
}
};

Show only the MIN/MAX value on Y-AXIS with C3JS

I would like to have the y-axis only with the min/max values of my data.
I tried to use the d3 directive but without results.
I had a look at google but I didn't find an answer to achieve this behaviour.
Below the code:
$.getJSON('assets/json/chartc3.json', function(data)
{
scene=data;
var chart = c3.generate({
bindto: '#chartc3',
data:
{
json: scene,
keys:
{
x: 'round',
value: ['Marketable', 'Total Requested Capacity', 'Your Bid'],
},
types: {
Marketable: 'area'
},
colors: {
Marketable: '#A09FA2',
'Total Requested Capacity': '#272E80',
'Your Bid': '#8EBF60'
}
},
axis:
{
x: {
tick:
{
culling:
{
max: 10
}
},
type: 'category'
},
y:
{
min: 0,
padding : {
bottom : 0
},
tick:
{
values: [[0], [***d3.max(scene)]***],
format: function (d) { return d3.format(',f')(d) +' kWh/h' }
//or format: function (d) { return '$' + d; }
}
}
}.........
How could I achieve the result described above ? d3.max(scene) returns NaN.
Well the problem is scene is not an array its a json object.
var k = d3.max([1,5,2])
k will be 5
so you will need to pass an array of elements which constitute your y ordinals.
you need to use
d3.max(arr, function(d){return numberic value});
or
var arr = scene.map(function(d){return ...the number value..})
y:{
min:d3.min(arr),
max:d3.max(arr)
},
the function depends on the array element of your data.
I used a little function to calculate the max by myself.
var maxs=0;
for (var j=0; j<scene.length; j++) {
var maxtemp=Math.max(scene[j].Marketable, scene[j]['Your Bid'], scene[j]['Total Requested Capacity']);
maxs=Math.max(maxs, maxtemp);
}

HighCharts: How to read the JSON data

This is my JSON:
[{"name":"AniSig01",
"data":[["2013,06,11,16,06,55",14],
["2013,06,11,16,07,00",14],
["2013,06,11,16,07,06",15],
["2013,06,11,16,07,11",14]]},
{"name":"AniSig02",
"data":[["2013,06,11,16,06,55",0],
["2013,06,11,16,07,00",0],
["2013,06,11,16,07,06",0],
["2013,06,11,16,07,11",0]]},
{"name":"AniSig03",
"data":[["2013,06,11,16,06,55",6],
["2013,06,11,16,07,00",6],
["2013,06,11,16,07,06",7],
["2013,06,11,16,07,11",6]]},
{"name":"AniSig04",
"data":[["2013,06,11,16,06,55",13],
["2013,06,11,16,07,00",13],
["2013,06,11,16,07,06",14],
["2013,06,11,16,07,11",13]]}]
And my HighCharts code:
$(document).ready(function()
{
var options =
{
chart:
{
renderTo: 'container',
type: 'spline',
},
title:
{
text: 'Statistik',
},
xAxis:
{
type:'datetime',
},
yAxis:
{
title: { text: ''},
},
tooltip:
{
shared: true,
crosshairs: true,
},
plotOptions:
{
series:
{
marker: {enabled: false},
animation: false,
threshold: null
}
},
series:[]
};
$.getJSON('datgen/DatReq.txt',function(data)
{
$.each(data, function(key,value)
{
var series = { data:[]};
$.each(value, function(key,val)
{
if (key == 'name')
{
series.name = val;
}
else
{
$.each(val, function(key,val)
{
val[0] = 'Date.UTC(' + val[0] + ')';
series.data.push(val[0], val[1]);
});
}
});
options.series.push(value);
});
var linechart = new Highcharts.Chart(options);
});
});
But I see these 4 signals only as a point (vertical line) on the chart.
and the Tooltip didn't show the correct time (the x-value)!
Does anyone know what the problem is?
As you've discovered, your X values are at fault. Not only are they wrong (per the tooltip), but they seem to be the same wrong for each point (giving you a vertical line).
So, let's concentrate on the X values. What are they? Okay, times. At some point, you had to parse the times from the data source into Javascript. Where did you do that?
Here:
val[0] = 'Date.UTC(' + val[0] + ')';
There we go — Javascript functions simply don't work that way. You can't just shove code into a string and expect it to be executed.
Split val[0] on the comma character, and feed each part of the result into a real call to Date.UTC.

Categories

Resources