Highcharts sparklines from ajax-inserted data - javascript

You have a html table and you want to show sparkline charts from your data, exactly as in this example (from highcharts demos):
https://codepen.io/_dario/pen/rNBOGVR
Highcharts suggested code follows:
/**
* Create a constructor for sparklines that takes some sensible defaults and merges in the individual
* chart options. This function is also available from the jQuery plugin as $(element).highcharts('SparkLine').
*/
Highcharts.SparkLine = function(a, b, c) {
var hasRenderToArg = typeof a === 'string' || a.nodeName,
options = arguments[hasRenderToArg ? 1 : 0],
defaultOptions = {
chart: {
renderTo: (options.chart && options.chart.renderTo) || this,
backgroundColor: null,
borderWidth: 0,
type: 'area',
margin: [2, 0, 2, 0],
width: 120,
height: 20,
style: {
overflow: 'visible'
},
// small optimalization, saves 1-2 ms each sparkline
skipClone: true
},
title: {
text: ''
},
credits: {
enabled: false
},
xAxis: {
labels: {
enabled: false
},
title: {
text: null
},
startOnTick: false,
endOnTick: false,
tickPositions: []
},
yAxis: {
endOnTick: false,
startOnTick: false,
labels: {
enabled: false
},
title: {
text: null
},
tickPositions: [0]
},
legend: {
enabled: false
},
tooltip: {
hideDelay: 0,
outside: true,
shared: true
},
plotOptions: {
series: {
animation: false,
lineWidth: 1,
shadow: false,
states: {
hover: {
lineWidth: 1
}
},
marker: {
radius: 1,
states: {
hover: {
radius: 2
}
}
},
fillOpacity: 0.25
},
column: {
negativeColor: '#910000',
borderColor: 'silver'
}
}
};
options = Highcharts.merge(defaultOptions, options);
return hasRenderToArg ?
new Highcharts.Chart(a, options, c) :
new Highcharts.Chart(options, b);
};
var start = +new Date(),
$tds = $('td[data-sparkline]'),
fullLen = $tds.length,
n = 0;
// Creating 153 sparkline charts is quite fast in modern browsers, but IE8 and mobile
// can take some seconds, so we split the input into chunks and apply them in timeouts
// in order avoid locking up the browser process and allow interaction.
function doChunk() {
var time = +new Date(),
i,
len = $tds.length,
$td,
stringdata,
arr,
data,
chart;
for (i = 0; i < len; i += 1) {
$td = $($tds[i]);
stringdata = $td.data('sparkline');
arr = stringdata.split('; ');
data = $.map(arr[0].split(', '), parseFloat);
chart = {};
if (arr[1]) {
chart.type = arr[1];
}
$td.highcharts('SparkLine', {
series: [{
data: data,
pointStart: 1
}],
tooltip: {
headerFormat: '<span style="font-size: 10px">' + $td.parent().find('th').html() + ', Q{point.x}:</span><br/>',
pointFormat: '<b>{point.y}.000</b> USD'
},
chart: chart
});
n += 1;
// If the process takes too much time, run a timeout to allow interaction with the browser
if (new Date() - time > 500) {
$tds.splice(0, i + 1);
setTimeout(doChunk, 0);
break;
}
// Print a feedback on the performance
if (n === fullLen) {
$('#result').html('Generated ' + fullLen + ' sparklines in ' + (new Date() - start) + ' ms');
}
}
}
doChunk();
However, in my use case, the data in the table (and the data-sparkline attribute) are not hard-coded like in the example, but loaded and displayed via an AJAX call, similar to below.
//here a table row gets compiled
var tableRow = '<tr id="row_' + word.id + '">';
//this is where the sparkline data go
tableRow += '<td class="has-sparkline"></td></tr>';
//the row gets appended to tbody
$('#wordstable tbody').append(tableRow);
//finally the sparkline data are attached
//data are a simple string such as "1,2,3,4,5"
var rowId = '#row_'+word.id;
var rowIdTd = rowId + ' td.has-sparkline';
$(rowIdTd).data('sparkline',word.sparkline);
This breaks the example logic and I can't have Highcharts "see" the data.
No particular error is returned (as the data, as far as Highcharts is concerned, just isn't there, so there's nothing to do).

The doChunk bit just does all the processing in advance, and when you add your row it is no longer processing. One way of dealing with this is pulling out the part that creates a single chart into a separate function (makeChart) and when you are doing your processing you use that part directly to create your sparkline.
For example, doChunk with split out makeChart:
function makeChart(td) {
$td = td;
stringdata = $td.data('sparkline');
arr = stringdata.split('; ');
data = $.map(arr[0].split(', '), parseFloat);
chart = {};
if (arr[1]) {
chart.type = arr[1];
}
$td.highcharts('SparkLine', {
series: [{
data: data,
pointStart: 1
}],
tooltip: {
headerFormat: '<span style="font-size: 10px">' + $td.parent().find('th').html() + ', Q{point.x}:</span><br/>',
pointFormat: '<b>{point.y}.000</b> USD'
},
chart: chart
});
}
// Creating 153 sparkline charts is quite fast in modern browsers, but IE8 and mobile
// can take some seconds, so we split the input into chunks and apply them in timeouts
// in order avoid locking up the browser process and allow interaction.
function doChunk() {
var time = +new Date(),
i,
len = $tds.length,
$td,
stringdata,
arr,
data,
chart;
for (i = 0; i < len; i += 1) {
makeChart($($tds[i]));
n += 1;
// If the process takes too much time, run a timeout to allow interaction with the browser
if (new Date() - time > 500) {
$tds.splice(0, i + 1);
setTimeout(doChunk, 0);
break;
}
// Print a feedback on the performance
if (n === fullLen) {
$('#result').html('Generated ' + fullLen + ' sparklines in ' + (new Date() - start) + ' ms');
}
}
}
And then a basic example of your ajax-code:
function ajaxIsh() {
var word = {
name: 'Bird', // is the word
id: 'bird',
sparkline: '1, 2, 3, 4, 5'
};
//here a table row gets compiled
var tableRow = '<tr id="row_' + word.id + '">';
//this is where the sparkline data go
tableRow += '<th>'+word.name+'</th><td class="has-sparkline"></td></tr>';
//the row gets appended to tbody
$('#table-sparkline tbody').append(tableRow);
//finally the sparkline data are attached
//data are a simple string such as "1,2,3,4,5"
var rowId = '#row_'+word.id;
var rowIdTd = rowId + ' td.has-sparkline';
$(rowIdTd).data('sparkline',word.sparkline);
makeChart($(rowIdTd));
}
See this JSFiddle demonstration of it in action.

Related

Can i use crosshairs with OHLC/candlestick charts using ChartJS

I have installed the crosshair plugin and am using chartJS 3.0 to take advantage of the candlestick charts but the crosshair does not appear. are these things compatible together? my data appear no problem but the crosshair never appears. how do i use these two things together? are there any working examples?
the tags i am using
<script src="https://cdn.jsdelivr.net/npm/luxon#1.24.1"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.0.0-beta.9/dist/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon#0.2.1"></script>
<script src="https://dd7tel2830j4w.cloudfront.net/f1614793727236x392906938665549250/chartjs-chart-financial.js"></script>
<script scr="https://cdn.jsdelivr.net/npm/chartjs-plugin-crosshair"></script>
and the chart code which also works
var divID = "chartContainer" + properties.chartid
var chartID = "myChart" + properties.chartid
instance.canvas.append('<div id="' + divID + '"></div>')
document.getElementById(divID).innerHTML = ' ';
document.getElementById(divID).innerHTML = '<canvas id=' + chartID + ' width="' + properties.bubble.width() + '" height="' + properties.bubble.height() + '"></canvas>';
var ctx = document.getElementById(chartID).getContext('2d');
var chart = new Chart(ctx, {
type: 'candlestick',
data: {
datasets: [{
label: 'CHRT - Chart.js Corporation',
data: getData()
}]
},
options: {
scales: {
y: {
min: 0,
max: 500
}
},
tooltips: {
mode: "interpolate",
intersect: false
},
plugins: {
crosshair: {
line: {
color: '#F66', // crosshair line color
width: 3, // crosshair line width
dashPattern: [5, 5] // crosshair line dash pattern
},
sync: {
enabled: true, // enable trace line syncing with other charts
group: 1, // chart group
suppressTooltips: false // suppress tooltips when showing a synced tracer
},
zoom: {
enabled: true, // enable zooming
zoomboxBackgroundColor: 'rgba(66,133,244,0.2)', // background color of zoom box
zoomboxBorderColor: '#48F', // border color of zoom box
zoomButtonText: 'Reset Zoom', // reset zoom button text
zoomButtonClass: 'reset-zoom', // reset zoom button class
},
callbacks: {
beforeZoom: function(start, end) { // called before zoom, return false to prevent zoom
return true;
},
afterZoom: function(start, end) { // called after zoom
}
}
}
}
}
});
function getData() {
var dates = properties.time.get(0, properties.time.length())
var opens = properties.open.get(0, properties.open.length())
var highs = properties.high.get(0, properties.high.length())
var lows = properties.low.get(0, properties.low.length())
var closes = properties.close.get(0, properties.close.length())
let data = []
for (i = 0; i < dates.length; i++) {
data.push({
t: dates[i].valueOf(),
o: opens[i],
h: highs[i],
l: lows[i],
c: closes[i]
})
}
console.log(data)
return data
}
chart.update()
The crosair plugin is not yet compatible with the new beta of version 3. They have a pr to be up to date with beta 11 but after that there have been breaking changes again. So you will have to update the plugin yourself or wait till it has been updated to support v3

Highstock/Highchart substep maximum?

example
I want my maximum step count to cap at a dynamic integer. In this case it's 4. I went through the documentation but can't find anything that suits my needs. Does someone have an idea about this?
Ideally; I want the white space shown in red gone.
xAxis: {
min: 1.1,
ordinal: false,
max: 2,
labels: {
formatter: function () {
return 'Lap ' + this.value;
},
},
},
http://jsfiddle.net/d54uae3s/4/
You can use breaks from the broken-axis Highcharts module:
function createBreaks() {
var breaks = [];
for (var i = 1; i < 8; i++) {
breaks.push({
from: i + 0.4,
to: i + 1.1,
breakSize: 0.1
})
}
return breaks;
}
var chart = Highcharts.chart({
...,
xAxis: {
...
breaks: createBreaks()
}
});
Live demo: http://jsfiddle.net/BlackLabel/sjLdfegp/
API Reference: https://api.highcharts.com/highstock/xAxis.breaks

Changing Tables in Highcharts Missing Row

I have an HTML table with some data, from that table I'm creating a chart:
var chart = Highcharts.chart('graph_user_1', {
data: {
table: 'table_user_1',
startColumn: 0,
endColumn: 1
},
chart: {
type: 'column'
},
title: {
text: 'Coverage'
},
yAxis: {
allowDecimals: true,
title: {
text: 'Hours'
}
},
tooltip: {
formatter: function() {
return '<b>' + this.series.name + '</b><br/>' +
this.point.y + ' Points - ' + this.point.name.toLowerCase();
}
},
plotOptions: {
column: {
colorByPoint: true
}
},
series: [{
id: 'series1'
}],
colors: [
'#ff0000',
'#00ff00',
'#0000ff',
'#3A8BEE',
'#F9B342',
'#DC4008'
]
});
I also have a filter, it lets me select some dates and filter the table, these rows of data that are not hidden, are appended to a hidden table on the page:
$("#to_date").on("change", function() {
var from = parseDate($('#from_date').val());
var to = parseDate($('#to_date').val());
var loop_count = 0;
var hidden_count = 0;
$("#body tr").each(function() {
var row = $(this);
var date_from = parseDate(row.find("td").eq(7).text());
var date_to = parseDate(row.find("td").eq(8).text());
var show = true;
show = isContained(date_from, date_to, from, to);
if (show) {
row.show();
legendArray.push(row); //pushes row to array
} else {
row.hide();
hidden_count++;
}
loop_count++;
});
if (hidden_count === loop_count) {
warning.prop("hidden", false);
warning.append("<strong>No results.</strong>");
}
restore(legendArray, chart);
});
The restore function at the end gets the array with rows and a chart reference, it appends the rows from the array to the new table, then changes the source of the data from the original to the hidden table (id="aux") and redraws it.
function restore(array) {
var table = document.getElementById('auxBody');
for (var i = 0; i <= array.length - 1; i++) {
table.appendChild(array[i][0].cloneNode(true));
}
var complete = function(options) {
var chart = $('#graph_user_1').highcharts();
var series1 = chart.get('series1');
series1.setData(options.series[0].data, false);
chart.redraw();
};
Highcharts.data({
table: 'aux',
complete: complete
});
}
After this, the graph IS updated, but it's always missing one row, the last one. I've printed the array, the new table, the nodes that are being appended, inspected, etc. All of this show the table with all the rows, but the graph is always missing the last one, I thought it was an index problem in the loop that adds the nodes but that doesn't seems to be the case.
As I posted earlier, it was a header issue with the table.
At creation I was appending the outerHTML of the copied table head:
newTable.append(newTHead[0].outerHTML);
Of course this caused an issue and the table had no headers, (even though it still showed on inspect) solution was removing the outerHTML and just appending the node, just a dumb conceptual mistake.
newTable.append(newTHead[0]);

When adding point on dynamically created Multiple Highchart Graphs on a single page, the plot line draws to the start point instead of last point?

var charts = [];
// change to an empty string to use the global namespace
var getChartConfig = function(renderId, title, data) {
var config = {};
config.chart = {
renderTo: renderId,
type: 'spline',
width: 450,
height: 300
};
config.title = {
text: title
};
config.legend = {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle',
borderWidth: 0
};
config.yAxis = {
min: 0,
title: {
text: 'watts'
}
};
config.xAxis = {
type: 'datetime'
};
config.tooltip = {
headerFormat: '<span style="font-size:10px">{point.key}</span><table>',
pointFormat: '<tr><td style="color:{series.color};padding:0">{series.name}: </td>' +
'<td style="padding:0"><b>{point.y:.1f} watts</b></td></tr>',
footerFormat: '</table>',
shared: true,
useHTML: true
};
config.series = [{
name: title,
data: data
}];
return config;
};
$(document).ready(function() {
$.ajax({
url: '/node/watts/data',
success: function(msg) {
var list = $.parseJSON(msg);
$.each(list, function(i, item) {
if (($('#node-' + item.nodeId + '-value').length) == 1)
series = item.data
charts.push(new Highcharts.Chart(
getChartConfig('node-' + item.nodeId + '-chart', item.name, series)
))
});
}
})
var i = 0
function socketloop() {
setTimeout(function() {
i = i + 1
requestData();
}, 5000);
}
socketloop();
setInterval(socketloop, 5000)
});
function requestData() {
$.ajax({
url: '/node/watts/test/2',
success: function(point) {
var myObject = JSON.parse(point);
console.log("============= " + myObject)
$.each(myObject, function(i, item) {
for (x = 0; x < charts.length; x++) {
if (charts[x].series[0].name == item.name) {
console.log("============= " + item.data)
shift = charts[x].series[0].data.length > 20; // shift if the series is
charts[x].series[0].addPoint(item.data, true, shift);
}
}
});
cache: false
}
});
}
I have two ajax calls to request the past data and the second ajax that retrives the newest point.
The Json consists of datatime and a watt value. With muiltple json objects by consisting of each node-ID, name, and data (timedate and value).
When it retrieves the past data it creates a chart with the data in the series and assigns the element id, title and data through the helper "getChartConfig" and pushs that chart to the charts array.
This renders fine but for the time doesn't display in the tooltip.
But it calls the function requestdata() which gets the JSON of the latest DateTime and watt value. I parse the json and to the series i call the function addpoint and the data i send in this case it is item.data,(as im iterating over the parsed Json Array.
This is when the point is added and when the line draws from the start point of the history data instead of the end point of the history data.
Itseems to be as if it doesn't see the previuos data.
Any advice would be much appricated, as how to make the addpoint draw from the last point of the existing data.
Ensure that your data is sorted by x ascending.

Flot Stacked Bar Chart and displaying bar values on mouse over

I'm trying to understand the tooltip functionality of Flot but not really getting my head around it!
I am trying to achieve a tooltip that displays the label and value of each section of a stacked bar chart
Would someone be able to point my towards an example of this or provide code for doing so?
The following code works for my Flot stacked bar chart, based on the Flot example that shows data point hover. The trick is that the 'item' values in the stacked chart are cumulative, so the 'y' value displayed in the tool tip has to first subtract the datapoint for the bars underneath.
var previousPoint = null;
$("#chart").bind("plothover", function (event, pos, item) {
if (item) {
if (previousPoint != item.datapoint) {
previousPoint = item.datapoint;
$("#tooltip").remove();
var x = item.datapoint[0],
y = item.datapoint[1] - item.datapoint[2];
showTooltip(item.pageX, item.pageY, y + " " + item.series.label);
}
}
else {
$("#tooltip").remove();
previousPoint = null;
}
});
I did not find this in the Flot documentation, but the item.datapoint array seemed to contain what I needed in practice.
The code above caused redraw-issues for me.
Here is an improved code:
var previousPoint = [0,0,0];
$("#regionsChart").bind("plothover", function (event, pos, item) {
if (item) {
if (previousPoint[0] != item.datapoint[0]
|| previousPoint[1] != item.datapoint[1]
|| previousPoint[2] != item.datapoint[2]
) {
previousPoint = item.datapoint;
$("#tooltip").remove();
var x = item.datapoint[0],
y = item.datapoint[1] - item.datapoint[2];
showTooltip(item.pageX, item.pageY, item.series.label + " " + y.toFixed(0) );
}
}
else {
$("#tooltip").remove();
previousPoint = [0,0,0];
}
});
This is the same as Thomas above, except that I shifted the tooltip up to prevent it blocking the hover action.
var previousPoint = [0,0,0];
$("#regionsChart").bind("plothover", function (event, pos, item) {
if (item) {
if (previousPoint[0] != item.datapoint[0]
|| previousPoint[1] != item.datapoint[1]
|| previousPoint[2] != item.datapoint[2]
) {
previousPoint = item.datapoint;
$("#tooltip").remove();
var x = item.datapoint[0],
y = item.datapoint[1] - item.datapoint[2];
showTooltip(item.pageX, item.pageY - 35, item.series.label + " " + y.toFixed(0) );
}
}
else {
$("#tooltip").remove();
previousPoint = [0,0,0];
}
});
The solution is using tooltipOpts -> content method with a callback function to properly return dynamic data to the label.
I figured out that passing a 4th argument to the callback function of the "tooltipOpts" actually gives you the whole data object from which the chart/graph is constructed from.
From here, you can easily extract the X axis labels, using the second argument of this same function as the index of the label to extract.
EXAMPLE:
Data object I'm passing to the plot function:
[
{ data: [[1,137],[2,194],[3,376],[4,145],[5,145],[6,145],[7,146]] }
],
{
bars: { show: true, fill: true, barWidth: 0.3, lineWidth: 1, fillColor: { colors: [{ opacity: 0.8 }, { opacity: 1}] }, align: 'center' },
colors: ['#fcc100'],
series: { shadowSize: 3 },
xaxis: {
show: true,
font: { color: '#ccc' },
position: 'bottom',
ticks: [[1,'Thursday'],[2,'Friday'],[3,'Saturday'],[4,'Sunday'],[5,'Monday'],[6,'Tuesday'],[7,'Wednesday']]
},
yaxis:{ show: true, font: { color: '#ccc' }},
grid: { hoverable: true, clickable: true, borderWidth: 0, color: 'rgba(120,120,120,0.5)' },
tooltip: true,
tooltipOpts: {
content: function(data, x, y, dataObject) {
var XdataIndex = dataObject.dataIndex;
var XdataLabel = dataObject.series.xaxis.ticks[XdataIndex].label;
return y + ' stories created about your page on ' + XdataLabel
},
defaultTheme: false,
shifts: { x: 0, y: -40 }
}
}
Bar chart rendered from the above data object:
As you can see on the image preview, the logic used to render the label's content dynamically form the actual data is this:
tooltipOpts: {
content: function(data, x, y, dataObject) {
var XdataIndex = dataObject.dataIndex;
var XdataLabel = dataObject.series.xaxis.ticks[XdataIndex].label;
return y + ' stories created about your page on ' + XdataLabel;
},
defaultTheme: false,
shifts: { x: 0, y: -40 }
}

Categories

Resources