After much searching and wanting to bang my head against my desk, I'm posting my first question on stackoverflow.
I've got an ASP.NET web application that is generating some data on the server side and creating a CSV file on the server.
I'm using HighCharts to produce a graph. Everything is working great, however, depending on what kind of date range a user runs there can be a few data points or many data points.
The problem comes in when there are many data points. Look at the X-Axis, and you'll see what I mean. Is there anyway to "group" these where it doesn't show every single point on the X-Axis?
The dates are at random intervals.
I've created a JSFiddle with my client side code and the contents of my CSV file in a JavaScript variable.
Here is my code:
function parseDate(dateStr) {
var matches = dateStr.match(/([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{4})/)
return Date.UTC(matches[3], matches[1]-1, matches[2]);
}
var csv = 'Chart,3/4/2007,3/24/2007,4/13/2007,4/25/2007,9/19/2007,9/28/2007,11/5/2007,1/7/2008,1/14/2008,1/21/2008,1/27/2008,2/3/2008,2/10/2008,2/17/2008,2/24/2008,3/2/2008,3/23/2008,3/30/2008,4/5/2008,4/21/2008,5/3/2008,5/10/2008,5/17/2008,5/24/2008,5/31/2008,6/8/2008,6/15/2008,6/29/2008,7/4/2008,7/18/2008,7/25/2008,8/1/2008,8/8/2008,9/17/2010,11/25/2010,8/16/2012,1/17/2013,1/27/2013\nDates,180.00,175.50,167.00,166.50,170.00,171.50,174.00,163.00,162.50,164.00,166.50,166.50,167.50,170.00,170.00,171.00,169.00,166.50,166.00,166.50,162.00,160.00,160.50,162.50,164.00,164.00,165.00,165.50,166.00,169.00,171.00,170.00,171.00,165.00,165.00,189.00,177.00,175.50';
var options = {
chart: {
renderTo: 'chart',
defaultSeriesType: 'line'
},
title: {
text: 'Test Chart'
},
xAxis: {
type: 'datetime',
categories: []
},
yAxis: {
title: {
text: 'Pounds'
}
},
series: []
};
// Split the lines
var lines = csv.split('\n');
$.each(lines, function(lineNo, line) {
var items = line.split(',');
if (lineNo == 0) {
$.each(items, function(itemNo, item) {
if (itemNo > 0) options.xAxis.categories.push(parseDate(item));
});
}
else {
var series = {
data: []
};
$.each(items, function(itemNo, item) {
if (itemNo == 0) {
series.name = item;
} else {
series.data.push(parseFloat(item));
}
});
options.series.push(series);
}
});
var chart = new Highcharts.Chart(options);
Here is the link to JSFiddle:
http://jsfiddle.net/Q2hyF/6/
Thanks in Advance,
Robert
Check out HighStocks and its DataGrouping feature:
http://www.highcharts.com/stock/demo/data-grouping
It can handle much larger datasets than HighCharts can. However, there are drawbacks as the newest HighCharts features are not always immediately in HighStocks. There are generally only minor changes needed to your syntax to use HighStocks, if you want to test it.
I ended up getting this working and never posted the answer... Here is the answer.
Take a close look at:
series.data.push([parseDate(points[0]), parseFloat(points[1])]);
in the code below...
function parseDate(dateStr) {
var matches = dateStr.match(/([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{4})/)
return Date.UTC(matches[3], matches[1] - 1, matches[2]);
}
var csv = 'Chart,11/1/2013|6,11/2/2013|4,11/3/2013|6,11/4/2013|3,11/5/2013|5,11/6/2013|5,11/7/2013|5,11/8/2013|6,11/9/2013|4,11/10/2013|13,11/11/2013|12,11/12/2013|3,11/13/2013|5,11/14/2013|7,11/15/2013|9,11/16/2013|0,11/17/2013|2,11/18/2013|3,11/19/2013|2,11/20/2013|16,11/21/2013|6,11/22/2013|9,11/23/2013|9,11/24/2013|20,11/25/2013|10,11/26/2013|10,11/27/2013|4,11/28/2013|9,11/29/2013|7,11/30/2013|7';
var options = {
chart: {
renderTo: 'chart',
type: 'line'
},
title: {
text: 'Sales'
},
xAxis: {
type: 'datetime'
},
series: []
};
var lines = csv.split('\n');
$.each(lines, function (lineNo, line) {
var items = line.split(',');
var series = {
data: []
};
$.each(items, function (itemNo, item) {
if (itemNo == 0) {
series.name = item;
} else {
var points = item.split('|');
series.data.push([parseDate(points[0]), parseFloat(points[1])]);
}
});
options.series.push(series);
});
var chart = new Highcharts.Chart(options);
http://jsfiddle.net/rswilley601/gtsLatyr/
Related
I am trying to display Highchart having same data in custom no. of times, Say I want to display the chart 2 times in same pagedynamically.
What I have done is that I set the chart in for loop in which I can repeat the chart multiple times dynamically.
Here is the Script That I have tried.
var len = 2 ;
var chartArea = document.getElementById("content");
for(var i=0;i<len;i++)
{
console.log("I", i);
chartArea.innerHTML +=
'<div id="container'+i+'"></div>';
var categories = ["1","2","3","4","5","6","7","8","9","10"];
Highcharts.stockChart('container'+i, {
rangeSelector: {
enabled: false
},
xAxis: {
labels: {
formatter: function() {
return categories[this.value];
}
}
},
navigator: {
xAxis: {
labels: {
formatter: function() {
return categories[this.value];
}
}
}
},
plotOptions: {
series: {
animation: {
duration: 2000
},
marker:{
enabled: false
}
}
},
series: [{
data: [3,5,3,6,2,6,4,9,4,6]
}]
});
But the problem is that only last graph shows the line chart. the other first chart have the x-axis labels bu the line graph is not showing.
Here is the Fiddle That I have tried.
http://jsfiddle.net/abnitchauhan/cenmohbw/
You forgot to append the child to the DOM tree.
When you create a new HTML element dynamically, it needs to be attached to an existing node in the DOM tree.
In Javascript you can do:
var existingNode = document.getElementById("content");
var newElement = document.createElement("div");
newElement.id = "someID";
existingNode.appendChild(newElement);
In jQuery, its more easy:
$("#content").append(`<div id="someID"></div>`);
In your case, the change would look like (only for loop changes) as:
for (var i = 0; i < len; i++) {
console.log("I", i);
$("#content").append(`<div id="container${i}"></div>`);
// rest of your code
I am using the following code to render an OHLC chart in CanvasJS:
<script>
var candleData = [];
var chart = new CanvasJS.Chart("chartContainer", {
title: {
text: 'Demo Stacker Candlestick Chart (Realtime)'
},
zoomEnabled: true,
axisY: {
includeZero: false,
title: 'Price',
prefix: '$'
},
axisX: {
interval: 1,
},
data: [{
type: 'ohlc',
dataPoints: candleData
}
]
});
function mapDataToPointObject(data) {
var dataPoints = [];
for(var i = 0; i < data.length; i++) {
var obj = data[i];
var newObj = {
x: new Date(obj.time),
y: [
obj.open,
obj.high,
obj.low,
obj.close
]
}
dataPoints.push(newObj);
}
return dataPoints;
}
function updateChart() {
$.ajax({
url: 'http://localhost:8080',
success: function(data) {
candleData = JSON.parse(data);
candleData = mapDataToPointObject(candleData);
chart.render();
}
});
}
$(function(){
setInterval(() => {
updateChart();
}, 500);
});
The data properly loads, parses into the correct format, and render() is called on the interval like it should. The problem is, while the chart axes and titles all render properly, no data shows up. The chart is empty.
What DOES work is setting the data directly to the chart using
chart.options.data[0].dataPoints = candleData;
Why does my above solution not work then? Is there a way I can update the chart's dataPoints without having to hardcode a direct accessor to the chart's dataPoints?
It's related to JavaScript pass by value and pass by reference.
After execution of the following line.
dataPoints: candleData
dataPoints will refer to the current value of candleData. ie. dataPoints = [];
Now if you redefine candleData to any other value.
candleData = JSON.parse(data);
candleData = mapDataToPointObject(candleData);
Then dataPoints won't be aware of this update and will still refer to the empty array (that you pointed earlier).
The following snippet will make it easy to understand
//pass by value
var a = "string1";
var b = a;
a = "string2";
alert("b is: " + b); //this will alert "string1"
//pass by reference
var c = { s: "string1" };
var d = c;
c.s = "string2";
alert("d.s is: " + d.s); //this will alert "string2"
For more, you can read about pass by value and pass by reference.
Javascript by reference vs. by value
Explaining Value vs. Reference in Javascript
I want to display a chart (that takes the data from a PHP file with JSON) with two dimensions of series : the first one is the technology used (5 in total), and the other one is the export or import.
So when the user is on the page, he can choose to diplay the technology, as export, import or both.
In first, to join one technology import with the same in export, I have used a "linkedto=previous", the result is a single item in the legend per technology.
But I would like to add two items in the legend : "Import" and "Export", with 0 data, that would permit to display or not the import or the export.
I have used this code, but I can't find how to display the choice of import, export, the both, or nothing.
Thank you very much if you take a bit of time to read my post. BR
$(function () {
var chart;
$(document).ready(function() {
var options = {
chart: {
renderTo: 'euro',
type: 'column'
},
title: {
text: 'Vision en euro'
},
xAxis: {
categories: []
},
yAxis: {
title: {
text: 'k€'
},
stackLabels: {
enabled: true,
rotation: 30,
}
},
tooltip: {
headerFormat: '{point.x}<b></b><br/>',
},
plotOptions: {
column: {
stacking: 'normal',
dataLabels: {
enabled: false,
},
}
},
series: []
};
Highcharts.setOptions(Highcharts.theme);
$.getJSON('SOURCE.php', function(json) {
options.xAxis.categories = json[0]['month'];
options.series[0] = {};
options.series[0].name = 'TECHNO 1';
options.series[0].data = json[1]['data'];
options.series[0].stack ='EXPORT';
options.series[0].color= '#808080';
options.series[1] = {};
options.series[1].name = 'TECHNO 1';
options.series[1].data = json[0]['data'];
options.series[1].stack = 'IMPORT';
options.series[1].linkedTo = ':previous';
options.series[1].color= 'url(#highcharts-default-pattern-0)';
options.series[2] = {};
options.series[2].name = 'TECHNO 2';
options.series[2].data = json[3]['data'];
options.series[2].stack = 'EXPORT';
options.series[2].color= '#FFC125';
options.series[3] = {};
options.series[3].name = 'TECHNO 2';
options.series[3].data = json[2]['data'];
options.series[3].stack = 'IMPORT';
options.series[3].linkedTo = ':previous';
options.series[3].color= 'url(#highcharts-default-pattern-1)';
options.series[4] = {};
options.series[4].name = 'TECHNO 3';
options.series[4].data = json[5]['data'];
options.series[4].stack = 'EXPORT';
options.series[4].color= '#2B99FF';
options.series[5] = {};
options.series[5].name = 'TECHNO 3';
options.series[5].data = json[4]['data'];
options.series[5].stack = 'IMPORT';
options.series[5].linkedTo = ':previous';
options.series[5].color= 'url(#highcharts-default-pattern-2)';
options.series[6] = {};
options.series[6].name = 'TECHNO 4';
options.series[6].data = json[7]['data'];
options.series[6].stack = 'EXPORT';
options.series[6].color= '#C72828';
options.series[7] = {};
options.series[7].name = 'TECHNO 4';
options.series[7].data = json[6]['data'];
options.series[7].stack = 'IMPORT';
options.series[7].linkedTo = ':previous';
options.series[7].color= 'url(#highcharts-default-pattern-3)';
options.series[8] = {};
options.series[8].name = 'TECHNO 5';
options.series[8].data = json[9]['data'];
options.series[8].stack = 'Sortie';
options.series[8].color= '#1CA154';
options.series[9] = {};
options.series[9].name = 'TECHNO 5';
options.series[9].data = json[8]['data'];
options.series[9].stack = 'EXPORT';
options.series[9].linkedTo = ':previous';
options.series[9].color= 'url(#highcharts-default-pattern-4)';
options.series[10] = {};
options.series[10].name = 'IMPORT';
options.series[10].data = json[10]['data'];
options.series[10].stack = 'IMPORT';
options.series[10].color= 'url(#highcharts-default-pattern-5)';
options.series[11] = {};
options.series[11].name = 'EXPORT';
options.series[11].data = json[11]['data'];
options.series[11].stack = 'IMPORT';
options.series[11].color= 'url(#highcharts-default-pattern-5)';
//options.series[1].color= '#C89B9B';
chart = new Highcharts.Chart(options);
});
});
});
First of all, you can hide or show a series in a chart by modifying the "visible" property of a series object to false or true respectively. For example:
options.series[10].visible = true; // or false
Secondly, you can achieve that in an event listener (the push of a button for example), using chart.update() method, and passing the changes as an argument. Have a look in here: Dynamic charts -> update options after render.
But the simplest solution is to just repeat the
chart = new Highcharts.Chart(options);
statement, after you have put in the series object the "visible" property with the value you like, for each series (import/export) you want to show or hide.
Finally, having 2 kinds of clickable labels in the legend can only be done with some custom jquery programming of your own. I think a couple of small buttons next to the chart would be much easier and faster to implement.
I am working with jquery HighCharts. I want to create a common function to develop multiple charts of same type.
To achieve this goad I created a function as follow -
function generateGraph(data) {
var dates = new Array();
var startDate = "";
for (var i = 0; i < data.length; i++) {
dates[i] = data[i].date;
}
var productInsight = _comm.getProductInsightArray();
var productInsight_Array = new Array();
$.each(productInsight, function(key, value) {
productInsight_Array.push(key);
});
if(dates.length > 0) {
startDate = dates[0].split("/");
}
intervals = 24 *3600 * 1000; //one day
var title = $("#DateSearch_analytics_Similar_Stores1").val();
var color = ['#000000', '#FFFFFF', '#000080', '#0000FF', '#808080'];
var containers =['container1', 'container2','container3','container4', 'container5', 'container6', 'container7', 'container8', 'container9'];
for(var j=0; j<containers.length; j++)
{
var chart = new Highcharts.Chart({
chart: {
zoomType: 'x',
renderTo: containers[j],
type: 'line',
},
title: {
text: title
},
subtitle: {
text: document.ontouchstart === undefined ?
'Click and drag in the plot area to zoom in' :
'Pinch the chart to zoom in'
},
xAxis: {
type: 'datetime',
tickInterval: intervals,
},
series: []
});
for(var i=0; i < productInsight_Array.length; i++) {
var fillData = new Array();
var counter = 0;
var productValue = document.getElementById('product-nm').value;
$.each(dates, function() {
fillData[counter] = _comm.randomNumberFromRange(_randomNumberStartRange, _randomNumberEndRange);
counter++;
});
chart.addSeries({
name: productInsight_Array[i],
type: 'line',
color: color[i],
pointStart: Date.UTC(startDate[2], startDate[0] - 1, startDate[1]),
pointInterval: intervals,
data: fillData,
});
}
$("text:contains('Highcharts.com')").css("display", "none");
}
}
Its working fine and developing charts on window load and button click. I am saving these graph in 9 container but this script is not loading graph in 3rd container and when I remove 3rd container than its not loading in fourth and so on. But it works fine when I call this function on button click in place of document load.
I am little surprised why its happing, As a common function creting all graphs.
I need your help! :(
Thanks
The problem is when load the function your markup doesnt exists. For this reason when you click the button works ok. When the button is visible, all markup is already loaded.
Review your load order.
Also aside comment: put this in your chart declaration:
credits: {
enabled: false
}
To not show the credits message: "Highcharts.com".
An example
I need to know how I can easily add another series to an existing plot using Flot.
Here is how I currently plot a single series:
function sendQuery() {
var host_name = $('#hostNameInput').val();
var objectName = $('#objectNameSelect option:selected').text();
var instanceName = $('#instanceNameSelect option:selected').text();
var counterName = $('#counterNameSelect option:selected').text();
$.ajax({
beforeSend: function () {
$('#loading').show();
},
type: "GET",
url: "http://okcmondev102/cgi-bin/itor_PerfQuery.pl?machine=" + host_name + "&objectName=" + objectName + "&instanceName=" + instanceName + "&counterName=" + counterName,
dataType: "XML",
success: function (xml) {
var results = new Array();
var counter = 0;
var $xml = $.xmlDOM(xml);
$xml.find('DATA').each(function () {
results[counter] = new Array(2);
results[counter][0] = $(this).find('TIMESTAMP').text();
results[counter][1] = $(this).find('VALUE').text();
counter++;
});
plot = $.plot($("#resultsArea"), [{
data: results,
label: host_name
}], {
series: {
lines: {
show: true
}
},
xaxis: {
mode: "time",
timeformat: "%m/%d/%y %h:%S%P"
},
colors: ["#000099"],
crosshair: {
mode: "x"
},
grid: {
hoverable: true,
clickable: true
}
});
You can just add another results set:
// build two data sets
var dataset1 = new Array();
var dataset2 = new Array();
var $xml = $.xmlDOM(xml);
$xml.find('DATA').each(function(){
// use the time stamp for the x axis of both data sets
dataset1[counter][0] = $(this).find('TIMESTAMP').text();
dataset2[counter][0] = $(this).find('TIMESTAMP').text();
// use the different data values for the y axis
dataset1[counter][1] = $(this).find('VALUE1').text();
dataset2[counter][2] = $(this).find('VALUE2').text();
counter++;
});
// build the result array and push the two data sets in it
var results = new Array();
results.push({label: "label1", data: dataset1});
results.push({label: "label2", data: dataset2});
// display the results as before
plot = $.plot($("#resultsArea"), results, {
// your display options
});
At a high-level, the result of your call into itor_PerfQuery.pl will need to be extended to include the additional series data. You'll then want to make your "results" variable a multi-dimensional array to support the additional data and you'll need to update the current xml "find" loop which populates results accordingly. The remainder of the code should stay the same as flot should be able to plot the extended dataset. I think a review of the flot example will help you out. Best of luck.