Chart.js: different dataset size - line

I need to display what I think is a pretty common use case: A line chart showing e.g. daily and hourly averages covering one week.
One dataset has obviously 7 data points, the other has 168.
I have not been able to find an example or a documented option that would allow for this. Sure - I can configure almost anything regarding the look&feel, but when it comes to the data size, both datasets are expected to have as many data points as there are labels.
I would like to have labels only for the dataset with the less datapoints (in this example: daily avg), and the other (168 data points) would simply draw a thin line and should not add aynthing else to the chart (no vertical bars etc.)
Is this possible with Chart.js?

What you are describing is actually possible with chart.js as long as at least 1 of your datasets can be expressed using the alternate scatter chart dataset. Let me walk you through an example where I show the daily avg as bars and the hourly avg as a line (aka, a mixed chart type).
First, configure a bar chart with labels for each day of the week and provide the daily average values for the data as your first dataset.
Next, create another dataset, but set it's type property to 'line'. Since we are wanting to use a different X axis and a different set of labels, you must express your data using the {x: 0, y: 2} notation. This will allow you to circumvent the scale from using the labels defined above.
Finally, in your options.scales config, define 2 xAxes scales and associate your 2nd dataset to the 2nd X axis scale (also set it's display property to false so you don't see any of the 2nd scale).
You end up with a chart configuration that looks like this.
var ctx = document.getElementById("canvas").getContext("2d");
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
datasets: [{
type: 'bar',
label: 'Daily Avg',
backgroundColor: chartColors.red,
data: dailyAvgData,
borderColor: 'white',
borderWidth: 2
}, {
type: 'line',
label: 'Hourly Avg',
borderColor: chartColors.green,
backgroundColor: chartColors.green,
borderWidth: 1,
fill: false,
pointRadius: 0,
xAxisID: 'x-axis-2',
data: hourlyAvgData
}]
},
options: {
responsive: true,
title: {
display: true,
text: 'Chart.js - Combo Chart With Multiple Scales (X Axis)'
},
tooltips: {
mode: 'nearest',
intersect: true
},
scales: {
xAxes: [{}, {
id: 'x-axis-2',
type: 'linear',
position: 'bottom',
display: false,
}],
yAxes: [{
ticks: {
min: 0,
max: 50
}
}]
}
}
});
You can see it in action at this codepen.
Here is another example but instead of using a combo chart (daily is bars, hourly is line), both plots are lines. The approach is exactly the same except the chart type is changed from bar to line.
Note, in this version I have adjusted the daily avg line over by one so that the points are plotted at the "end of the day". The previous example was showing the daily avg at the start of each day (which is technically not correct).
Here is an example codepen that demonstrates the line version of the chart.

Landing here after searching how to implement a moving average in Chart.js, I've struggled to fully understand the answer of #jordanwillis, so maybe this can be useful to someone else too.
The central idea is that you can assign a format of data (for each dataset in datasets) such that the y component represents the value of the series and the x - this is the crucial point IMHO - matches the label (in the unique, common labels array, that will be the shared x axis for all the series, i.e. the datasets) where you want to plot that specific point.
So my example for a moving average of N (say 7 day moving average) and a generic asymmetric shift of S between 0 and N-1 (e.g. 3) would be
var valuesMean7 = movingAverage(7, 3, ydata, xlabels);
where I have defined:
function movingAverage(N, shift, values, labels) {
var NN = values.length;
var valuesMean = [];
rightShift = N - shift - 1;
for (let i = shift; i < NN - rightShift; i++) {
var mean = 0;
for (let j = i - shift; j < i - shift + N; j++) {
mean += values[j];
}
mean = mean / N;
valuesMean.push({
x: labels[i],
y: mean
});
}
return valuesMean;
}

Related

how do i update a Google Charts pie chart to reflect filtered data but retain filtered out segments as a diff colour

I'm new to Google Charts and I'm struggling to solve this.
I have a datatable (called "result" in the code)
Name Liquidity percent
a 1.3 20%
b 2.0 20%
c 3.4 20%
d 4 20%
e 5 20%
My pie chart is set to show 5 segments of equal size - 20% - and each segment is blue
I have set a 'Number Range Filter' control wrapper to filter the liquidity - when i set the control to the range 1 to 4 the pie moves to 4 equal sized segments.
BUT... I don't want it to do this. Instead of 1 segment disappearing I want the 5 segments to remain visible and the colour of the filtered segment to change to be a different colour.
The aim being that I can see visually a total percentage that falls within the number filter.
EDIT:
So I've had a mess about and this is as far as I've got incorporating dlaliberte's comment below.
function drawChart3(chartData3) {
var result = google.visualization.arrayToDataTable(chartData3,false); // 'false' means that the first row contains labels, not data.
var chart3 = new google.visualization.ChartWrapper({
'chartType': 'PieChart',
'containerId': 'chart3_div',
'dataTable': result,
'options': {
'width': 500,
'height': 500,
'legend': {position: 'none'},
'pieSliceText': 'none',
'colors': ['blue']
},
'view': {'columns': [0 , 1]}
});
var liquidityDT = new google.visualization.DataTable();
// Declare columns
liquidityDT.addColumn('number', 'Liquidity');
// Add data.
liquidityDT.addRows([
[1],
[2],
[3],
[4],
[5],
]);
// Create a range slider, passing some options
var liquidityRangeSlider = new google.visualization.ControlWrapper({
'controlType': 'NumberRangeFilter',
'containerId': 'filter3_div',
'dataTable': liquidityDT,
'options': {
'filterColumnLabel': 'Liquidity',
'minValue': 0,
'maxValue': 5
}
});
liquidityRangeSlider.draw();
chart3.draw();
google.visualization.events.addListener(liquidityRangeSlider, 'statechange', setChartColor);
function setChartColor(){
var state = liquidityRangeSlider.getState();
var stateLowValue = state.lowValue;
var stateHighValue = state.highValue;
for (var i = 0; i < result.getNumberOfRows(); i++) {
var testValue = result.getValue(i,2);
if (testValue < stateLowValue || testValue > stateHighValue){
alert("attempting to set colors")
//this bit I have no clue how to change the color of the table row currently being iterated on
chart3.setOption({'colors' : ['red']});
}
}
chart3.draw();
}
}
so it produces the pie chart with 5 blue segments. I can move the number filter and it fires the listener event but I can't get it to affect anything on the piechart (Chart3) - The code currently attempts just to change the whole chart to RED but that isn't even working never mind the just colouring the filtered segments
So how do I effect the changes into Chart3 and how do I only effect the filtered segments?.
any clues welcome?
thanks
You can (and must, for your application) use a NumberRangeFilter outside of a Dashboard because it will otherwise always do the data filtering. Instead, just listen for the change events on the NumberRangeFilter, get its current state, and then iterate through the colors array for your chart to set the appropriate colors. You'll have to draw the chart again with the updated colors. Here is the loop to set the colors and redraw.
var colors = [];
for (var i = 0; i < result.getNumberOfRows(); i++) {
var color = (testValue < stateLowValue || testValue > stateHighValue) ? 'red' : 'blue';
colors.push(color);
}
chart3.setOption('colors', colors);
chart3.draw();

issue with different type of axes for storyboard example of dimplejs

I'm trying to create a storyboard very similar to the one in dimpleJS examples. My data looks like below.
[{"Date":"2014-05-05T19:03:23Z","Liters":0.23,"Soda":"Coke","Day":"2014-05-05","Time of Day":"15:03","Day-Time":"2014-05-05 15:03"},{"Date":"2014-05-05T19:37:27Z","Liters":0.35,"Soda":"Coke","Day":"2014-05-05","Time of Day":"15:37","Day-Time":"2014-05-05 15:37"},{"Date":"2014-05-05T20:33:33Z","Liters":0.21,"Soda":"Coke","Day":"2014-05-05","Time of Day":"16:33","Day-Time":"2014-05-05 16:33"},{"Date":"2014-05-05T21:11:37Z","Liters":0.21,"Soda":"Coke","Day":"2014-05-05","Time of Day":"17:11","Day-Time":"2014-05-05 17:11"},{"Date":"2014-05-06T13:55:09Z","Liters":0.22,"Soda":"Coke","Day":"2014-05-06","Time of Day":"9:55","Day-Time":"2014-05-06 9:55"},{"Date":"2014-05-06T14:27:13Z","Liters":0.27,"Soda":"Coke","Day":"2014-05-06","Time of Day":"10:27","Day-Time":"2014-05-06 10:27"},{"Date":"2014-05-06T15:42:21Z","Liters":0.14,"Soda":"Coke","Day":"2014-05-06","Time of Day":"11:42","Day-Time":"2014-05-06 11:42"},{"Date":"2014-05-06T16:16:24Z","Liters":0.24,"Soda":"Coke","Day":"2014-05-06","Time of Day":"12:16","Day-Time":"2014-05-06 12:16"},{"Date":"2014-05-05T19:13:24Z","Liters":0.59,"Soda":"Diet Coke","Day":"2014-05-05","Time of Day":"15:13","Day-Time":"2014-05-05 15:13"},{"Date":"2014-05-05T20:33:33Z","Liters":0.01,"Soda":"Diet Coke","Day":"2014-05-05","Time of Day":"16:33","Day-Time":"2014-05-05 16:33"},{"Date":"2014-05-05T21:04:36Z","Liters":0.39,"Soda":"Diet Coke","Day":"2014-05-05","Time of Day":"17:04","Day-Time":"2014-05-05 17:04"},{"Date":"2014-05-05T21:11:37Z","Liters":0.21,"Soda":"Diet Coke","Day":"2014-05-05","Time of Day":"17:11","Day-Time":"2014-05-05 17:11"},{"Date":"2014-05-05T21:57:42Z","Liters":0.17,"Soda":"Diet Coke","Day":"2014-05-05","Time of Day":"17:57"},{"Date":"2014-05-06T11:11:53Z","Liters":0.42,"Soda":"Diet Coke"},{"Date":"2014-05-06T12:54:03Z","Liters":0.49,"Soda":"Diet Coke"},{"Date":"2014-05-06T12:55:03Z","Liters":0.48,"Soda":"Diet Coke"},{"Date":"2014-05-06T13:07:04Z","Liters":0.27,"Soda":"Diet Coke"},{"Date":"2014-05-06T13:34:07Z","Liters":0.41,"Soda":"Diet Coke"},{"Date":"2014-05-06T13:55:09Z","Liters":0.19,"Soda":"Diet Coke"},{"Date":"2014-05-06T14:27:13Z","Liters":0.01,"Soda":"Diet Coke"},{"Date":"2014-05-06T15:42:21Z","Liters":0.02,"Soda":"Diet Coke"},{"Date":"2014-05-06T16:01:23Z","Liters":0.45,"Soda":"Diet Coke"},{"Date":"2014-05-06T16:05:23Z","Liters":0.52,"Soda":"Diet Coke"},{"Date":"2014-05-06T16:35:27Z","Liters":0.65,"Soda":"Diet Coke"},{"Date":"2014-05-06T16:49:28Z","Liters":0.4,"Soda":"Diet Coke"},{"Date":"2014-05-06T16:50:29Z","Liters":0.14,"Soda":"Diet Coke"},{"Date":"2014-05-05T18:10:18Z","Liters":0.24,"Soda":"Powerade"},{"Date":"2014-05-05T19:03:23Z","Liters":0.01,"Soda":"Powerade"},{"Date":"2014-05-05T19:37:27Z","Liters":0.01,"Soda":"Powerade"},{"Date":"2014-05-05T20:39:34Z","Liters":0.39,"Soda":"Powerade"},{"Date":"2014-05-05T21:04:36Z","Liters":0.01,"Soda":"Powerade"},{"Date":"2014-05-06T10:32:49Z","Liters":0.18,"Soda":"Powerade"},{"Date":"2014-05-06T11:11:53Z","Liters":0.01,"Soda":"Powerade"},{"Date":"2014-05-06T12:54:03Z","Liters":0.01,"Soda":"Powerade"},{"Date":"2014-05-06T14:27:13Z","Liters":0.02,"Soda":"Powerade"},{"Date":"2014-05-06T15:42:21Z","Liters":0.02,"Soda":"Powerade"},{"Date":"2014-05-06T16:01:23Z","Liters":0.03,"Soda":"Powerade"},{"Date":"2014-05-06T16:05:23Z","Liters":0.03,"Soda":"Powerade"},{"Date":"2014-05-06T16:23:25Z","Liters":0.12,"Soda":"Powerade"},{"Date":"2014-05-06T16:50:29Z","Liters":0.01,"Soda":"Powerade"}]
I've category and time axes. The problem is that the bubbles do not show up at the correct y(time) axis. My code looks like below and it also shows too many 0 values although there is no 0 value in dataset.
var series, charts, lastDate = null, sodas = dimple.getUniqueValues(data, "Soda");
charts = [new dimple.chart(svg, null)],
charts.push(myChart);
charts.forEach(function (chart, i) {
var x, y, z;
chart.setBounds(this.attributes.left, this.attributes.top, this.attributes.width - 225, this.attributes.height - 225);
x = chart.addCategoryAxis("x", "Soda");
x.overrideMax = 3;
x.hidden = (i === 0);
y = chart.addTimeAxis("y", "Day-Time", "%Y-%m-%d %H:%M", "%d-%m %H:%M");
// y.overrideMax = "17:00";
y.hidden = (i === 0);
z = chart.addMeasureAxis("z", "Liters");
z.overrideMax = 1;
// Ensure the same colors for every owner in both charts
// differing by opacity
sodas.forEach(function (soda, k) {
chart.assignColor(
soda,
charts[0].defaultColors[k].fill,
"white",
(i === 0 ? 0.3 : 1));
}, this);
}, this);
charts[1].addLegend(850, 100, 60, 300, "Right");
charts[1].setStoryboard("Time of Day", function (d) {
// Use the last date variable to manage the previous tick's data
if (lastDate !== null) {
// Pull the previous data
var lastData = dimple.filterData(data, "Time of Day", lastDate);
// Add a series to the background chart to display old position
var lastSeries = charts[0].addSeries("Soda", dimple.plot.bubble);
// Average suits these measures better
lastSeries.aggregate = dimple.aggregateMethod.avg;
// Give each series its own data at different periods
lastSeries.data = lastData;
// Draw the background chart
charts[0].draw();
// Class all shapes as .historic
lastSeries.shapes.attr("class", "historic");
// Reduce all opacity and remove once opacity drops below 5%
d3.selectAll(".historic").each(function () {
var shape = d3.select(this),
opacity = shape.style("opacity") - 0.02;
// shape.style("opacity", opacity);
if (opacity < 0.1) {
shape.remove();
} else {
shape.style("opacity", opacity);
}
});
}
lastDate = d;
});
series = charts[1].addSeries("Soda", dimple.plot.bubble)
series.aggregate = dimple.aggregateMethod.avg;
// Draw the main chart
charts[1].draw();
Here is the screenshot.
Thanks for adding the fiddles. If you remove the x.hidden = (i === 0); and y.hidden = (i === 0); lines (so that the back chart's axes are displayed) you can see the issue. Unlike measure axes where you can fix the max and min to make sure that the charts are identically proportioned, category axes will only draw for the elements in the data, so a frame with a single category will only draw a single point in the centre of the axis. Also the ordering will change by default.
To get your case working you need to fix ordering for the x axis.
x.addOrderRule(["Coke", "Diet Coke", "Powerade"]);
Define a max and min value for the y axis:
y.overrideMin = d3.time.format("%Y-%m-%d").parse("2014-05-05");
y.overrideMax = d3.time.format("%Y-%m-%d").parse("2014-05-07");
And stick some dummy rows in for any missing categories.
lastData = lastData.concat([
{
"Date": "2014-05-06T00:00:00Z",
"Liters": 0,
"Soda": "Coke"
},
{
"Date": "2014-05-06T00:00:00Z",
"Liters": 0,
"Soda": "Diet Coke"
},
{
"Date": "2014-05-06T00:00:00Z",
"Liters": 0,
"Soda": "Powerade"
}
]);
This results in this output: http://jsfiddle.net/4qBqJ/9/

Struggling with add.Series (CSV dataset)

I’ll try to keep this short. I have a CSV with traffic counts for specific streets. So far I have plotted the street names on the (x) axis, and the total traffic count on the (y) axis. The CSV also contains counts for vehicles that travel for (< 15 min, 15-30 min, 30-45 min, 60 min, etc).
What I am trying to do is “split” the total count for each street in accordance with the (< 15, 15-30, etc) minute counts, kind of like categories. Essentially, I am trying to replicate this example:
http://dimplejs.org/examples_viewer.html?id=bars_vertical_grouped_stacked where the “Owner” category is instead the “Arterial” category from my dataset.
In short:
1. I can semi-successfully split some of the street names, however, some don’t seem to be split at all even though counts exist for the categories.
The tooltip is not showing category-specific counts. It seems to be shoving all of the counts into one tooltip regardless of hovering over a category.
For the legend, is there a way to ensure that it uses the street names? If I remove the “Commute” values and leave “Arterial” it uses the names correctly, but then I lose the ability to show the categories.
I hope this isn’t too confusing. I’d sincerely appreciate any help.
CODE:
var svg = dimple.newSvg("#chartContainer", 1280, 720);
d3.csv("../HTML/strippedData_v2.csv", function (data) {
var myChart = new dimple.chart(svg, data);
myChart.setBounds(60, 45, 510, 315)
myChart.addCategoryAxis("x", ["Arterial"]);
myChart.addMeasureAxis("y", "Total");
myChart.addSeries(["Arterial", "Commute15", "Commute1530", "Commute3045", "Commute4560", "Commute60"], dimple.plot.bar);
myChart.addLegend(200, 10, 380, 20, "right");
myChart.draw();
});
IMAGES: (Don't have enough rep :/)
(Only the first 3 images of the gallery apply.)
http://imgur.com/a/8P2tN#0
I'm struggling to work out exactly how you would like the chart to look. I suspect the problem may be the CommuteXX fields. It sounds like you are trying to treat them as dimension values, whereas dimple treats columns as dimensions (and their row values as dimension values). Therefore you need to reorganise your data something like this:
Arterial |Commute |Population
Colfax Avenue |Commute15 |1380
Colfax Avenue |Commute1530 |1641
Colfax Avenue |Commute3045 |855
Etc...
This can be done in Javascript once the CSV is loaded. Here is a function to do that:
function unPivot(sourceData, valueFields, newCategoryField, newValueField) {
var returnData = [],
newRow,
key,
i,
j;
for (i = 0; i < sourceData.length; i += 1) {
for (j = 0; j < valueFields.length; j += 1) {
newRow = {}
for (key in sourceData[i]) {
if (sourceData[i].hasOwnProperty(key) && valueFields.indexOf(key === -1)) {
newRow[key] = sourceData[i][key];
}
}
newRow[newCategoryField] = valueFields[j];
newRow[newValueField] = sourceData[i][valueFields[j]];
returnData.push(newRow);
}
}
return returnData;
};
And here it is in a fiddle: http://jsfiddle.net/GeLng/15/
I'm not sure if this is the chart you are looking for, you mention a grouped bar but I'm not sure what you want to group by. Hopefully this will give you enough to create the chart the way you want.

Adding x axis labels when using dojox.charting.DataSeries

I'm creating a Dojo line chart from a dojo.data.ItemFileReadStore using a dojox.charting.DataSeries. I'm using the third parameter (value) of the constructor of DataSeries to specify a method which will generate the points on the chart. e.g.
function formatLineGraphItem(store,item)
{
var o = {
x: graphIndex++,
y: store.getValue(item, "fileSize"),
};
return o;
}
The graphIndex is an integer which is incremented for every fileSize value. This gives me a line chart with the fileSize shown against a numeric count. This works fine.
What I'd like is to be able to specify the x axis label to use instead of the value of graphIndex i.e. the under lying data will still be 1,2,3,4 but the label will show text (in this case the time at which the file size was captured).
I can do this by passing in an array of labels into the x asis when I call chart.addAxis() but this requires me to know the the values before I iterate through the data. e.g.
var dataSeriesConfig = {query: {id: "*"}};
var xAxisLabels = [{text:"2011-11-20",value:1},{text:"2011-11-21",value:2},{text:"2011-11-22",value:3}];
var chart1 = new dojox.charting.Chart("chart1");
chart1.addPlot("default", {type: "Lines", tension: "4"});
chart1.addAxis("x", {labels: xAxisLabels});
chart1.addAxis("y", {vertical: true});
chart1.addSeries("Values", new dojox.charting.DataSeries(dataStore, dataSeriesConfig, formatLineGraphItem));
chart1.render();
The xAxisLabels array can be created by preparsing the dataSeries but it's not a very nice work around.
Does anyone have any ideas how the formatLineGraphItem method could be extended to provide the x axis labels. Or does anyone have any documentation on what values the object o can contain?
Thanks in advance!
This will take a unix timestamp, multiply the value by 1000 (so that it has microseconds for JavaScript, and then pass the value to dojo date to format it).
You shouldn't have any problems editing this to the format you need.
You provided examples that your dates are like "1", "2", "3", which is clearly wrong. Those aren't dates.. so this is the best you can do unless you edit your question.
chart1.addAxis("x",{
labelFunc: function(n){
if(isNaN(dojo.number.parse(n)) || dojo.number.parse(n) % 1 != 0){
return " ";
}
else {
// I am assuming that your timestamp needs to be multiplied by 1000.
var date = new Date(dojo.number.parse(n) * 1000);
return dojo.date.locale.format(date, {
selector: "date",
datePattern: "dd MMMM",
locale: "en"
});
}
},
maxLabelSize: 100
}

What to change or add to slider so I have step motion in 5

Can someone help me with slider. What to change or add so I have step in 5 and to allow only integer numbers ? Currently, when I slide up or down step i about 3.6 . How to set value in slider from some function ?
<script type="text/javascript">
dojo.require("dijit.form.Slider");
dojo.require("dijit.form.TextBox"); // this we only include to make the textinput look prettier
dojo.addOnLoad(function() {
var vertical = dojo.byId("vertical");
var rulesNode = document.createElement('div');
vertical.appendChild(rulesNode);
var sliderRules = new dijit.form.VerticalRule({
count: 24,
style: "width:5px;"
},
rulesNode);
var slider = new dijit.form.VerticalSlider({
name: "vertical",
value: 0,
minimum: 1440,
maximum: 0,
pageIncrement:100,
showButtons:true,
slideDuration:288,
intermediateChanges:false,
style: "height:450px;",
onChange: function(value) {
dojo.byId("sliderValue").value = value;
}
},
vertical);
});
</script>
You need to define how many discrete values you want to allow in the slider. This is done with the discreteValues parameter. In your case you have a range of 0 - 1440. If you want every number that is a multiple of 5 to be a valid value, this is 1440 / 5 = 288 discrete values.
But wait! That's not including the 0! You actually want the 288 steps + the first step which is 0. That makes 289 discrete values, and so your widget should be instantiated like this:
var slider = new dijit.form.VerticalSlider({
/* .. your other properties.. */
discreteValues: 289
}, vertical);