In c3.js I want to change the title of the tooltip in the timeseries chart, By default, the title of the tooltip is the time of the X-series.
I have a unique text label for each time point which I store as a data series in rows: [ ], but I can't retrieve this unique text label to show in the tooltip.
My data is:
rows: [
['Date', 'Type', 'Pages', 'Words'],
['2013-01-01', 'A', 120, 300],
['2013-01-02', 'B', 160, 240],
['2013-01-03', 'C', 200, 290],
['2013-01-04', 'D', 160, 230],
['2013-01-05', 'E', 130, 300],
['2013-01-06', 'F', 220, 320]
],
Currently the title of the tooltip is showing the X-series date, which is basically:
title = d[0].x;
I modified tooltip: {} like this
title = d[0].value;
but the title of the tooltip shows null, instead of showing the text corresponding to the 2nd column.
Then when I do this:
title = d[1].value;
the screen can show the number corresponding to the 3rd column.
Is there a problem showing a text field as the title of the tooltip? I wouldn't think so if it can show the date by default.
Please let me know how to show the 2nd column data as the title of the tooltip. The 2nd column data basically describes the event occurring at that date.
Full code for tooltip: {} is below:
tooltip: {
contents: function (d, defaultTitleFormat, defaultValueFormat, color) {
var $$ = this, config = $$.config,
titleFormat = config.tooltip_format_title || defaultTitleFormat,
nameFormat = config.tooltip_format_name || function (name) { return name; },
valueFormat = config.tooltip_format_value || defaultValueFormat, text, i, title, value, name, bgcolor;
title = d[0].x ; //title of tooltip is X-axis label
//title = d[0].name; //shows Type as the tooltip title
//title = d[0].value; //shows null as the tooltip title
//title = d[1].value; //shows Page number as the tooltip title
for (i = 0; i < d.length; i++) {
if (! (d[i] && (d[i].value || d[i].value === 0))) { continue; }
if (! text) {
text = "<table class='" + $$.CLASS.tooltip + "'>" + (title || title === 0 ? "<tr><th colspan='2'>" + title + "</th></tr>" : ""); //for the tooltip title
}
name = nameFormat(d[i].name); //for the columns data
value = valueFormat(d[i].value, d[i].ratio, d[i].id, d[i].index);
bgcolor = $$.levelColor ? $$.levelColor(d[i].value) : color(d[i].id);
text += "<tr class='" + $$.CLASS.tooltipName + "-" + d[i].id + "'>";
text += "<td class='name'><span style='background-color:" + bgcolor + "'></span>" + name + "</td>";
text += "<td class='value'>" + value + "</td>";
text += "</tr>";
}
return text + "</table>";
}
}, //tooltip
Related
I have Highcharts chart rendered on yAxis[0] and whenever the user selects an indicator like Aroon Oscillator, MACD etc. it is added as a new yAxis in the rendering container. Example:
The above snapshot is with the Aroon Oscillator. All good.
When i select Slow Stochastic though i get the following:
First of all both SS$K and SS$D have the same value as you can see in the tooltip which is clearly wrong and both lines have the same color for a reason when they should not because:
I tried to log the p.series.yData[p.point.index] (the currently hovered point yData value) in my console and for my surprise I get the following:
It returns an array (but why) of 2 values, and both plots access the first value, instead of SS%D accessing the second value. This is a Slow Stochastic problem from Highcharts since the Aroon Oscillator which has the same philosophy of many plots on the same yAxis, still works fine as you can see in the first picture (where the lines have the correct color and correct values assigned).
Lastly as you can see with another indicators MACD (which has the same issue as mentioned before with Slow Stochastic) - and VPT which works as intended, when i go full range, those have a gap at start. Why is that happening?
Has anyone come across a similar indicator problem from Highcharts and if so, could anyone give me his/her lights on how to solve the problem?
Thanks in advance, Christopher.
P.S. Those are my tooltip options for highcharts:
tooltip: {
formatter: function() {
let finalDate = '';
let tooltipString = '';
$.each(this.points, function(i, p) {
// console.log(p.series.name, p.series.yData[p.point.index]);
//Starting date
let dateTo = new Date(p.x),
month = '' + (dateTo.getMonth() + 1),
day = '' + dateTo.getDate(),
year = dateTo.getFullYear();
if (month.length < 2) month = '0' + month;
if (day.length < 2) day = '0' + day;
let intradayTime = '';
if (p.point.isIntradayData) {
let hours = dateTo.getHours();
let minutes = ('0' + dateTo.getMinutes()).slice(-2);
intradayTime += ' ' + hours + ':' + minutes;
}
dateTo = [day, month, year].join('.');
//If we wanna display previous date too:
let dateFrom = '';
if (p.point.previousDate) {
dateFrom = new Date(p.point.previousDate);
month = '' + (dateFrom.getMonth() + 1);
day = '' + dateFrom.getDate();
year = dateFrom.getFullYear();
if (month.length < 2) month = '0' + month;
if (day.length < 2) day = '0' + day;
dateFrom = [day, month, year].join('.');
dateFrom += ' - ';
}
finalDate = dateFrom + dateTo + intradayTime;
if (p.series.userOptions.compare == 'percent') {
tooltipString +=
'<br>' +
`<span style="color:${p.color}; font-size:10px; font-family:\'Arial\'">` +
p.series.name +
': </span>' +
'<span style="font-size:10px; font-family:\'Arial\'">' +
' ' +
p.point.percentageDifference +
`%(${p.y})`;
('</span>');
} else if (
p.series.type == 'candlestick' ||
p.series.type == 'ohlc'
) {
tooltipString +=
'<br>' +
`<span style="color:${p.color}; font-size:10px; font-family:\'Arial\'">` +
p.series.name +
': </span>' +
'<br>' +
'<span style="font-size:10px; font-family:\'Arial\'">' +
'Open: ' +
p.point.open +
'<br>' +
'<span style="font-size:10px; font-family:\'Arial\'">' +
'High: ' +
p.point.high +
'<br>' +
'<span style="font-size:10px; font-family:\'Arial\'">' +
'Low: ' +
p.point.low +
'<br>' +
'<span style="font-size:10px; font-family:\'Arial\'">' +
'Close: ' +
p.point.close +
'</span> <br>';
} else {
let pointValueFormatted = p.y.toFixed(2);
tooltipString +=
'<br>' +
`<span style="color:${p.color}; font-size:10px; font-family:\'Arial\'">` +
p.series.name +
': </span>' +
'<span style="font-size:10px; font-family:\'Arial\'">' +
' ' +
pointValueFormatted +
'</span>';
}
});
return (
'<span style=" font-size:10px; font-family:\'Arial\'">' +
finalDate +
'</span>' +
tooltipString
);
},
borderColor: '#7fb7f0',
shared: true,
},
INDICATORS LOGIC
It's impossible to reproduce it in jsfiddle due to the size, the complexity and for security reasons due to policies. So a user can select indicators from a tabmenu like:
and they way the indicators are sorted:
//Sorting indicators.
sortIndicators() {
console.log(`Function Call: sortIndicators()`);
this.cleanIndicators(); // Just clearing the chart yaxises and the indicators array list to iterate over indicators again.
let counter = 0;
//For every indicator
for (let indicator of this.indicators) {
//If it is chart indicator we render it on yAxis[0] with our prices
if (toolsManager.isChartIndicator(indicator)) {
//some indicators have multiple arrays of data instead of one saved in an array called arrayOfObj
if (indicator.arrayOfObj) {
for (let arg of indicator.arrayOfObj) {
this.highchartOptions.series.push(arg);
}
} else {
this.highchartOptions.series.push(indicator);
}
} else {
//If it not chart indicator
this.highchartOptions.chart.height +=
2 * this.yAxisMargin + this.yAxisHeight;
let topCalculation =
456 + (counter + 1) * this.yAxisMargin + counter * this.yAxisHeight;
//yAxis to add
let addedAxis = {
title: {
enabled: false,
},
opposite: true,
min: null,
height: this.yAxisHeight,
top: topCalculation + this.yAxisMargin,
showLastLabel: false,
labels: {
enabled: true,
x: -30,
y: 0,
style: {
fontSize: '10px',
fontFamily: 'Arial',
color: '#999999',
},
},
};
this.highchartOptions.yAxis.push(addedAxis);
if (indicator.arrayOfObj) {
//some indicators have multiple arrays of data instead of one saved in an array called arrayOfObj
for (let arg of indicator.arrayOfObj) {
//for every array of that arrayOfObj
arg.yAxis = counter + 1;
this.highchartOptions.series.push(arg); //push it to series
}
} else {
indicator.yAxis = counter + 1;
if (indicator.id === 'volume') {
indicator.data = this.volumeSeries;
}
this.highchartOptions.series.push(indicator);
}
counter++;
}
}
},
the part where the magic happens is here:
if (indicator.arrayOfObj) {
//some indicators have multiple arrays of data instead of one saved in an array called arrayOfObj
for (let arg of indicator.arrayOfObj) {
//for every array of that arrayOfObj
arg.yAxis = counter + 1;
this.highchartOptions.series.push(arg); //push it to series
}
} else {
indicator.yAxis = counter + 1;
if (indicator.id === 'volume') {
indicator.data = this.volumeSeries;
}
the reason it is implemented like this is because when an indicator is selected an object is returned from the server which may have one of the two following structures:
data structure 1:
data structure 2:
And after sorting out what type of structure I am dealing with, I am pushing it into the series to display. counter variable is just used for the y-axis's. Arron Oscillator and Slow Stochastic both fulfill the first part of the conditional, because both return an object which contains and arrayOfObj (for the line colors etc as you can see in the photo uploaded).
I have the following highcharts graph
https://jsfiddle.net/deemgfay/
and I am trying to display the "Consum Test" values in the tooltip but without adding them to the series. I just want to add Consum (l/100km)
Total Consum (l) to the series. Is that possible with hightcharts? Please see the screenshot below.
You can set the extra series to be hidden and ignored in legend:
visible: false,
showInLegend: false
Then use tooltip formatter function (useHTML must be enabled) to display points from all series in the shared tooltip regardless of their visibility:
formatter: function() {
var html,
originalPoint = this.points[0];
// header
html = "<span style='font-size: 10px'>" + originalPoint.x + "</span><br/>";
// points
originalPoint.series.chart.series.forEach(function(series) {
var point = series.points.find((p) => p.x === originalPoint.point.x);
html += "<span style='color: " + series.color + "'>\u25CF</span> " + series.name + ": <b>" + point.y + "</b><br/>"
});
return html;
}
Live demo: https://jsfiddle.net/kkulig/1oggzsx0/
API references:
http://api.highcharts.com/highcharts/tooltip.formatter
http://api.highcharts.com/highcharts/tooltip.useHTML
This can be done by using tooltip.formatter. Here I append to tooltip info based on index of current series from index of required extra array.
formatter: function() {
var s = '<b>' + this.x + '</b>';
var reqpoint = 0;
$.each(this.points, function() {
var reqpoint = this.point.index
s += '<br/>' + this.series.name + ': ' +
this.y.toFixed(2) + 'm';
if (this.series.index == 1) {
s += '<br/>Test Consum (l): ' + extraData[reqpoint] + 'm';
}
});
return s;
},
Fiddle demo
Link to JSFiddle with question content marked by comments: https://jsfiddle.net/z1q7aqo3/
Specifically, the part which needs filling in is
'tooltip': {
'formatter': function(){
var output = '<strong>' + this.y + '</strong>';
var sorted = this.points.sort(function(a, b){
if (a.y == b.y){
return 0;
}
return a.y < b.y ? 1 : -1;
});
sorted.forEach(function(point, index){
var marker = '';
/*
TODO: How do I determine what symbol is used for the marker?
*/
output += '<br /><span style="color: ' + point.series.color + '">' + marker + '</span> ' + point.series.name + ': ' + point.y;
});
return output;
},
'shared': true
}
I have a line chart with a number of series. On mouseover, the tooltip displays all the values of all of them, in sorted order. This part is done and can be seen in the JSFiddle. My question is, I want each the text for each series in the tooltip to include the marker symbol used for that series, styled in the color of the series. The color styling is also complete and can be seen in the JSFiddle, but how do I get the marker symbol?
Modifying form earlier Answer it is for individual series tooltip. Here modified to shared tooltip
Fiddle demo
Highcharts.chart('container', {
'title': {
'text': 'Random Chart'
},
'tooltip': {
'formatter': function(){
var output = '<strong>' + this.y + '</strong>';
var sorted = this.points.sort(function(a, b){
if (a.y == b.y){
return 0;
}
return a.y < b.y ? 1 : -1;
});
sorted.forEach(function(point, index){
var marker = '';
if ( point.point.graphic && point.point.graphic.symbolName ) {
switch ( point.point.graphic.symbolName ) {
case 'circle':
marker = '●';
break;
case 'diamond':
marker = '♦';
break;
case 'square':
marker = '■';
break;
case 'triangle':
marker = '▲';
break;
case 'triangle-down':
marker = '▼';
break;
}
}
/*
TODO: How do I determine what symbol is used for the marker?
*/
output += '<br /><span style="color: ' + point.series.color + '">' + marker + '</span> ' + point.series.name + ': ' + point.y;
});
return output;
},
'shared': true
},
'series': [{
'name': 'Series 1',
'data': [1, 3, 7, 19, 11, 27, 8, 15]
}, {
'name': 'Series 2',
'data': [2, 1, 8, 12, 14, 20, 9, 10]
}]
});
I am using a stock chart to show trend data. On the backend I am getting what the valueSuffix should be (or valuePrefix as the case may be). I am also formatting the date display in the tooltip. Here is the important part of the series declaration:
...
name: 'Wages',
tooltip: {
valuePrefix: '$',
valueDecimals: 0
},
...
Here is the tooltip formatter:
...
tooltip: {
formatter: function () {
var s = '<b>';
if (Highcharts.dateFormat('%b', this.x) == 'Jan') {
s = s + 'Q1';
}
if (Highcharts.dateFormat('%b', this.x) == 'Apr') {
s = s + 'Q2';
}
if (Highcharts.dateFormat('%b', this.x) == 'Jul') {
s = s + 'Q3';
}
if (Highcharts.dateFormat('%b', this.x) == 'Oct') {
s = s + 'Q4';
}
s = s + ' ' + Highcharts.dateFormat('%Y', this.x) + '</b>';
$.each(this.points, function (i, point) {
s += '<br/><span style="color: ' + point.series.color + '">' + point.series.name + ':</span>' + point.y;
});
return s;
}
}
...
Example jsFiddle.
If you notice the prefix for the dollar sign is not showing on the Wage series. I am not really sure what I am missing here.
The fix is to break up the label formatting and the value formatting into distinct sections. See example jsFiddle.
Set the chart.tooltip like:
...
tooltip: {
headerFormat: '<b>{point.key}</b><br>',
xDateFormat: '%Q'
},
...
On the xAxis I replaced the label formatter with:
...
format: '{value: %Q}'
...
Inside the series I kept my suffix/prefix/decimals the same:
...
tooltip: {
valuePrefix: '$',
valueDecimals: 0
},
...
The big change came when I found that you can set your own date format label. I created one for Quarters (which is what I did the original cumbersome code for):
Highcharts.dateFormats = {
Q: function (timestamp) {
var date = new Date(timestamp);
var y = date.getFullYear();
var m = date.getMonth() + 1;
if (m <= 3) str = "Q1 " + y;
else if (m <= 6) str = "Q2 " + y;
else if (m <= 9) str = "Q3 " + y;
else str = "Q4 " + y;
return str;
}
};
I now get the tooltip to show the correct labels.
Unless something has changed in a recent version, you can't set your tooltip options within the series object. You can set anything that you would set in the plotOptions on the series level, but not the tooltip.
You can set a check within your main tooltip formatter to check for the series name, and apply the prefix accordingly.
{{edit::
This appears to be an issue of the formatter negating the valuePrefix setting.
Old question... but I spent hours looking for an acceptable way to do this.
/!\ : Before OP's answer...
If someone is looking for a way to use the formatter provided by highcharts (pointFormat attribute) (Doc here: LABELS AND STRING FORMATTING), you will find a (bad) example here :
http://jsfiddle.net/gh/get/jquery/1.7.2/highslide-software/highcharts.com/tree/master/samples/highcharts/tooltip/pointformat/
Why "bad" ? because if you change :
pointFormat: '{series.name}: <b>{point.y}</b><br/>',
with :
pointFormat: '{series.name}: <b>{point.y:,.1f}</b><br/>',
you add thousand separators (useless here) and force number format to 1 decimal ... and loose the suffix.
The answer is : series.options.tooltip.valueSuffix (or series.options.tooltip.valuePrefix)
Example :
pointFormat: '<span style="color:{series.color}">\u25CF</span> {series.name}: <b>{point.y:,.0f}{series.options.tooltip.valueSuffix}</b><br/>',
Hope this will save you some time.
Now OP's Answer :
Based on what I said, you can change :
$.each(this.points, function (i, point) {
s += '<br/><span style="color: ' + point.series.color + '">' + point.series.name + ':</span>' + point.y;
});
For this :
$.each(this.points, function (i, point) {
s += '<br/><span style="color: ' + point.series.color + '">'
+ point.series.name + ':</span>'
+ (typeof point.series.options.tooltip.valuePrefix !== "undefined" ?
point.series.options.tooltip.valuePrefix : '')
+ point.y
+ (typeof point.series.options.tooltip.valueSuffix !== "undefined" ?
point.series.options.tooltip.valueSuffix : '');
});
This will add prefix and/or suffix if they exists
The basic concept of my app is to map a group of pallets onto a truck. I have a page that allows the user to drag a draggable div "pallet" and place it in a droppable div "slot" on the truck. The weights for each row, left and right columns and the truck total weight are calculated on the fly. I then pass all of the information to a page to print a table of pallets and slots so an operator can then load the truck. The problem is restoring the load if the user only loads half the truck and then wants to come back at a latter time to finish. I know how to save any number of data points, but i cannot find a way to put the "pallets" back in their "slots" when i rebuild the page.
Here are the div's for the pallets, some of the slots and the drop procedure (i'm not sure what else is needed)
//load the playpen with the pallets that belong on this truck
for (var i = 0; i < noPallets; i++) {
$('<div id="' + PalletIDs[i] + '">' + PalletIDs[i] + '<br>' + Storage[i] + ':' + Weights[i] + '</div>').data('pallet',
{ ID: PalletIDs[i], Weight: Weights[i], columnNo: 0, rowNo: 0, tierNo:0 }).appendTo('#playpen').draggable({
containment: '#content',
stack: '#playpen div',
cursor: 'move',
start: handlePalletStart,
revert: true
});
}
// Create the pallet slots for truck left
var words = ['C<br>one', 'C<br>two', 'C<br>three', 'C<br>four', 'C<br>five', 'C<br>six', 'C<br>seven', 'C<br>eight', 'C<br>nine', 'C<br>ten', 'C<br>eleven'];
for (var i = 1; i <= 11; i++) {
position = 1000 + (i * 10) + 3
$('<div id="' + position + '">' + words[i - 1] + '</div>').data('position', { columnNo: 1, rowNo: i, tierNo: 3 }).appendTo('#truckleftC').droppable({
accept: '#playpen div',
hoverClass: 'hovered',
drop: handlePalletDrop
});
}
function handlePalletDrop(event, ui) {
// this is the truck location we are about to drop into
var columnNo = $(this).data('position').columnNo;
var rowNo = $(this).data('position').rowNo;
var tierNo = $(this).data('position').tierNo;
// this is pallet information
var palletID = ui.draggable.data('pallet').ID;
var weight = ui.draggable.data('pallet').Weight;
// if the pallet was already in a positio, we need to zero that position
if (ui.draggable.data('pallet').columnNo != 0) {
oldposition = ui.draggable.data('pallet').columnNo * 1000 + ui.draggable.data('pallet').rowNo * 10 + ui.draggable.data('pallet').tierNo;
// alert(oldposition);
for (var h = 0; h <= 65; h++) {
if (aryTWeight[h][0] == oldposition) {
aryTWeight[h][1] = 0;
}
}
}
// set the position in the truck for this pallet
ui.draggable.data('pallet').columnNo = columnNo;
ui.draggable.data('pallet').rowNo = rowNo;
ui.draggable.data('pallet').tierNo = tierNo;
// set the weight for this position in the truck
position = columnNo * 1000 + rowNo * 10 + tierNo
// alert(position);
for (var h = 0; h <= 65; h++) {
if (aryTWeight[h][0] == position) {
aryTWeight[h][1] = weight;
}
}
// alert(columnNo + ' : ' + rowNo + ' : ' + tierNo);
// set the hidden text box with the palletid so data can be posted to next page
for (var i = 0; i < document.forms[0].elements.length; i++) {
element = document.forms[0].elements[i];
if (element.name == oldposition) {
element.value = 0;
};
if (element.name == position) {
element.value = palletID;
};
}
// set all of the weights for rows, columns and totals
calcWeights();
ui.draggable.addClass('correct');
ui.draggable.position({ of: $(this), my: 'left top', at: 'left top' });
ui.draggable.draggable('option', 'revert', false);
}
You could store x and y coordinates of the loaded pallets and then when you're rebuilding the page set the palettes' css position: absolute; and set top and left to the x and y stored. And append them to the droppable div programmatically.
It all depends on how your slots work, if you have lots of droppables and each droppable can only contain one draggable then just store the pairs and append the draggable to droppable with jquery when you're rebuilding the page.
var html = '<div id="myDraggableDiv"></div>'
$('#' + droppableId).append(html);
If you need any special css like exact top and left add it in style attribute or with jquery after the element is created.