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.
I have two files:
1 geojson file with the country shapes I want for a specific group of countries.
1 csv file with the data I want to map using mapbox.
I'm trying to figure out how can I join these two files so that the geojson country info name matches the countries in my CSV file. I'd like to use omnivore to convert my csv to json and then figure out how to pull that data out to bind in a popup.
There is an example of joining two geojson files (one for shapes and another for data) here: https://www.mapbox.com/mapbox.js/example/v1.0.0/choropleth-joined-data-multiple-variables/
But I'd like to use Omnivore to parse my csv file so that I can convert CSV first.
I've managed to separately load my geojson country file and load my CSV file making it ready for my highcharts pop-up, but I can't figure out how to join the two by name.
Here's how I've separately called my geojson layer:
function popup(feature, layer) {
if (feature.properties && feature.properties.name) {
}
}
$.ajax({
dataType: "json",
url: "countries.geojson",
success: function(data) {
$(data.features).each(function(key, data) {
//transitpipes.addData(data);
var countries = new L.geoJson(data, {
onEachFeature: popup,
style: countriesStyle,
}).addTo(map);
});
}
}).error(function() {});
});
And here's what I'm trying to accomplish with my CSV data:
var ckeyOrder = []
var csvGrab2 = $.get('countries.csv',function (response) {
Papa.parse(response, {
complete: function(results) {
var cHeaderRow = results.data[0];
for (var i = 7; i < cHeaderRow.length; i++) {
ckeyOrder.push(cHeaderRow[i])
}
}
});
})
csvGrab2.done(function (csvString) {
var countriesLayer = omnivore.csv.parse(csvString)
countriesLayer.eachLayer(function(marker) {
var pieChartOptions = {
title: {
text: null
},
legend: {
enabled: false
},
credits: {
enabled: false
},
exporting: {
enabled: false
},
tooltip: {
pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>',
backgroundColor: {
linearGradient: [0, 0, 0, 60],
stops: [
[0, '#FFFFFF'],
[1, '#E0E0E0']
]
},
borderWidth: 1,
useHTML: true,
borderColor: '#AAA'
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
connectNulls: false,
dataLabels: {
enabled: true,
format: '<b>{point.name}</b>: {point.percentage:.1f} %',
style: {
color: (Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black'
}
}
}
}
};
pieChartOptions.tooltip.formatter = function() {
var y = "" + this.y;
return '<center>' + y + '</center>';
};
var cData = [];
for (var i = 0; i < ckeyOrder.length; i++) {
cData.push(parseInt(marker.feature.properties[ckeyOrder[i]]))
}
var lastColumn = ckeyOrder.length;
pieChartOptions.series = [{
data: cData
}];
// HTML content for country pop-up
var countryContent = '<div id="popup_template">' +
'<div>' +marker.toGeoJSON().properties.Name +'</div>' +
'<div><p>'+marker.toGeoJSON().properties.Production+'</p></div>'+
'<div id="piechart"></div>';
var ccontainer = $('<div id="popup_template"/>');
ccontainer.html( '<div>' +marker.toGeoJSON().properties.Name +'</div>' +
'<div><p>'+marker.toGeoJSON().properties.Production +'</p></div>' +
'<div id="piechart"></div>');
// Delegate all event handling for the container itself and its contents to the container
ccontainer.find('#piechart').highcharts(lineChartOptions);
marker.bindPopup(ccontainer[0]);
Is there a way to join my geojson countries with my omnivore parsed CSV code so that I can map my CSV data this way?
My incredible bad attempt at this mix of code is here: https://jsfiddle.net/t8qsbzs0/
Here is my CSV structure (one row):
Country,Production,Gas demand,Total imports,of which LNG,Total exports,Total storage capacity,Share of gas in TPES (%),Self sufficiency (%),Electricity and heat,Industry,Residential,Services,Other
France,0.3,44,47.9,7.8,5,12.1,15.1,0.7,16,26,33,18,7
and my countries.geojson file is structured like this:
{"type":"Feature","properties":{"name":"France","iso_a2":"FR","iso_a3":"FRA","iso_n3":"250"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-52.6,2.5],[-52.9,2.1],[-53.4,2.1],[-53.6,2.3],[-53.8,2.4],[-54.1,2.1],[-54.5,2.3],[-54.3,2.7],[-54.2,3.2],[-54,3.6],[-54.4,4.2],[-54.5,4.9],[-54,5.8],[-53.6,5.6],[-52.9,5.4],[-51.8,4.6],[-51.7,4.2],[-52.2,3.2],[-52.6,2.5]]],[[[9.6,42.2],[9.2,41.4],[8.8,41.6],[8.5,42.3],[8.7,42.6],[9.4,43],[9.6,42.2]]],[[[3.6,50.4],[4.3,49.9],[4.8,50],[5.7,49.5],[5.9,49.4],[6.2,49.5],[6.7,49.2],[8.1,49],[7.6,48.3],[7.5,47.6],[7.2,47.4],[6.7,47.5],[6.8,47.3],[6,46.7],[6,46.3],[6.5,46.4],[6.8,46],[6.8,45.7],[7.1,45.3],[6.7,45],[7,44.3],[7.5,44.1],[7.4,43.7],[6.5,43.1],[4.6,43.4],[3.1,43.1],[3,42.5],[1.8,42.3],[0.7,42.8],[0.3,42.6],[-1.5,43],[-1.9,43.4],[-1.4,44],[-1.2,46],[-2.2,47.1],[-3,47.6],[-4.5,48],[-4.6,48.7],[-3.3,48.9],[-1.6,48.6],[-1.9,49.8],[-1,49.3],[1.3,50.1],[1.6,50.9],[2.5,51.1],[2.7,50.8],[3.1,50.8],[3.6,50.4]]]]}},
Okay, so yes, once the content of pie chart is decided..this "joining" is pretty easy.
Entire code block is here in a JSFiddle - https://jsfiddle.net/j4fLj5gm/1/
Won't work because of the CORS issue mentioned in comments, but posting it here for anyway.
The join happens with a loop through actual data, and then a search through the Leaflet feature layer to find the match for the country name.
for (var countryIndex = 0; countryIndex < countryData.length; countryIndex++) {
var marker;
var thisDataRow = countryData[countryIndex];
var thisCountry = thisDataRow[0];
countries.eachLayer(function(country) {
if (country.feature.properties.name === thisCountry) {
marker = country;
}
})
if (typeof marker == 'undefined') {
continue;
}
Data prep for the pie chart is quite easy inside each country's loop.
var cData = [];
var innerIndex = 0;
for (var i = 9; i < 14; i++) {
cData.push({name: ckeyOrder[innerIndex],y: parseInt(thisDataRow[i])})
innerIndex++;
}
pieChartOptions.series = [{
Name: "Production Types",
data: cData
}];
Results in below screenshot..You will probably want to control a dimensions a bit.
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.
I'm looking for help with the highlightSeries plugin made by Brian Peiris (http://brian.peiris.name/highlightSeries/). It doesn't appear to be working; I'm positive that the event is firing, because an alert test I performed earlier worked just fine, printing out $(this).text(). I'm trying to get the series on the chart to be highlighted when the user mouses over the series name in the legend (something which works perfectly fine on Mr. Peiris's website).
$('.server-chart').each(function() {
var serv = $(this).attr('id').substr(6);
var plotData = [];
//alert(serv + ": " + $.toJSON(serverStats[serv]));
for (var k in serverStats[serv].StatsByCollection) {
var outlabel = k;
var outdata = serverStats[serv].StatsByCollection[k];
plotData.push({ label: outlabel, data: outdata});
}
plotOptions = {
legend: {
container: $('#legend-' + serv),
labelFormatter: function(label, series) {
return '' + label + '';
},
noColumns: 2
},
series: {
lines: {
show: true,
fill: false
},
points: {
show: false,
fill: false
}
},
colors: colors,
grid: {
hoverable: false
},
highlightSeries: {
color: "#FF00FF"
}
}
$_plot = $.plot(this, plotData, plotOptions);
$plots.push($_plot);
$('#legend-' + serv + ' .legendLabel, #legend-' + serv + ' .legendColorBox').on('mouseenter', function () {
$_plot.highlightSeries($(this).text());
});
$('#legend-' + serv + ' .legendLabel, #legend-' + serv + ' .legendColorBox').on('mouseleave', function () {
$_plot.unHighlightSeries($(this).text());
});
});
I'm not sure what other code to put on here, so tell me if you need more; the charts are all working fine, this is just the part of the ready function setting up all of the plots and their options inside the placeholders.
Also, there's a couple of classes attached to the labels that are extraneous; I was trying different things to get this stuff to work.
The plugin requires a patched version of flot to work (it introduces a public drawSeries method). The last patched version is for an older version of flot (0.7).
With that said, I wouldn't use this plugin. If you just want to highlight a series on legend mouseover it's pretty simple.
$('#legend .legendLabel, #legend .legendColorBox').on('mouseenter', function() {
var label = $(this).text();
var allSeries = $_plot.getData();
// find your series by label
for (var i = 0; i < allSeries.length; i++){
if (allSeries[i].label == label){
allSeries[i].oldColor = allSeries[i].color;
allSeries[i].color = 'black'; // highlight it in some color
break;
}
}
$_plot.draw(); // draw it
});
$('#legend .legendLabel, #legend .legendColorBox').on('mouseleave', function() {
var label = $(this).text();
var allSeries = $_plot.getData();
for (var i = 0; i < allSeries.length; i++){
if (allSeries[i].label == label){
allSeries[i].color = allSeries[i].oldColor;
break;
}
}
$_plot.draw();
});
See example here.