I'm building irregular time graphs with HighCharts that at the moment look like so:
And I'm wondering if it's possible to create an 'average' line for the three (or possibly more in future) lines.
It would start following the blue line, then go closer to the green line mid-January, etc.
At the moment the code I'm working with looks like:
$('#chart').highcharts({
chart: { type: 'spline' },
title: { text: '' },
xAxis: { type: 'datetime' },
yAxis: {
title: { text: '' }
}
series: [{
name: 'Line 1',
data: [
[Date.UTC(2014,0,16), 173.33],
[Date.UTC(2014,0,23), 163.33],
[Date.UTC(2014,0,30), 137.67],
[Date.UTC(2014,1,6), 176.33],
[Date.UTC(2014,1,13), 178.67],
[Date.UTC(2014,1,27), 167.33],
],
color: 'purple'
},
{
name: 'Line 2',
data: [
[Date.UTC(2014,0,11), 156.33],
[Date.UTC(2014,1,15), 167.67],
],
color: 'green'
},
{
name: 'Line 3',
data: [
[Date.UTC(2014,0,1), 135],
[Date.UTC(2014,0,5), 146.33],
[Date.UTC(2014,0,27), 146.75],
],
color: 'blue'
}]
});
What you are describing is called a trend or regression line. Highcharts doesn't have a built in ability to add these lines, but the math isn't too difficult (and besides, it's more fun to do it yourself). I've coded up the simplest example I can using least squared linear regression.
/////////////////////
//utility functions//
////////////////////
// linear regression
// given array of x values and array of y values
// returns rV object with slope/intercept
lineFit = function(xs, ys, rV){
rV.slope = 0.0;
rV.intercept = 0.0;
rV.rSquared = 1.0; // assume perfection
if (xs.length < 2)
{
return false;
}
if (xs.Count != ys.Count)
{
return false;
}
var N = xs.length;
var sumX = sumFunc(xs,null);
var sumY = sumFunc(ys,null);
var funcSq = function(i){return (i*i);}
var funcSq2 = function(i,j){return (i*j);}
var sumXx = sumFunc(xs, funcSq);
var sumYy = sumFunc(ys, funcSq);
var sumXy = sumFunc(zip(xs,ys),funcSq2);
rV.slope = ((N * sumXy) - (sumX * sumY)) / (N * sumXx - (sumX*sumX));
rV.intercept = (sumY - rV.slope * sumX) / N;
rV.rSquared = Math.abs((rV.slope * (sumXy - (sumX * sumY) / N)) / (sumYy - ((sumY * sumY) / N)));
return true;
}
// sums arrays with optional function transformation
sumFunc = function(arr, func){
var total = 0;
$.each(arr, function(i,k){
if ($.isArray(k)){
if (func == null){
k = k[0] + k[1];
}else{
k = func(k[0],k[1]);
}
} else {
if (func != null){
k = func(k);
}
}
total += k;
});
return total;
}
// python style zip function
// to pair to array together
zip = function(arr1,arr2) {
var rV = [];
for(var i=0; i<arr1.length; i++){
rV.push([arr1[i],arr2[i]]);
}
return rV;
}
The lineFit function will return the rV object (by reference) with attributes of slope and intercept. After that you can add a line to Highcharts with good old fashioned y = slope * x + intercept and minX is the starting value for the regression line and maxX is the ending value.
{
name: 'Regression Line',
data: [[minX, reg.slope * minX + reg.intercept],
[maxX, reg.slope * maxX + reg.intercept]],
color: 'red',
marker:{enabled:false},
lineWidth: 5
}
Working fiddle here.
Based on ideas provided by the answer from Mark, I wrote some code to generate a custom fourth line, using the data from all three lines, and calculating the required value for each point.
My new code is as follows:
line1 = [
[Date.UTC(2014,0,16), 173.33],
[Date.UTC(2014,0,23), 163.33],
[Date.UTC(2014,0,30), 137.67],
[Date.UTC(2014,1,6), 176.33],
[Date.UTC(2014,1,13), 178.67],
[Date.UTC(2014,1,27), 167.33],
];
line2 = [
[Date.UTC(2014,0,11), 156.33],
[Date.UTC(2014,1,15), 167.67],
];
line3 = [
[Date.UTC(2014,0,1), 135],
[Date.UTC(2014,0,5), 146.33],
[Date.UTC(2014,0,27), 146.75],
[Date.UTC(2014,2,2), 168.75]
];
function average(array, index) {
sum = array[0][1];
for(var i = 1; i <= index; i++) {
sum += array[i][1];
}
value = sum / (index + 1);
return parseFloat(value.toFixed(2));
}
// Make a fourth line with all of the data points for the other
// three lines, sorted by date
all_lines = line1.concat(line2).concat(line3);
all_lines.sort(function(a, b) { return a[0] - b[0]});
// Calculate the value for each data point in the fourth line -
// the average of all the values before it
average_line = [];
for(var i = 0; i < all_lines.length; i++) {
average_line.push([all_lines[i][0], average(all_lines, i)])
}
$('#chart').highcharts({
chart: { type: 'spline' },
title: {
text: '',
},
xAxis: {
type: 'datetime'
},
yAxis: {
title: {
text: ''
}
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle',
borderWidth: 0
},
series: [{
name: 'Line 1',
data: line1,
color: 'purple'
},
{
name: 'Line 2',
data: line2,
color: 'green'
},
{
name: 'Line 3',
data: line3,
color: 'blue'
},
{
name: 'Average',
data: average_line,
color: 'red'
}]
});
The graph as it looks now (with one extra data point on the blue line) is:
Related
I want to count the number of items in an arraylist to display it in a chart using chart.js
This is how I am doing it currently, but I am unable to get a count of it.
const dataArray = [];
let labelsArray = ['1 - Very Jialat', '2 - Jialat', '3 - Normal lor', '4 - Shiok a bit', '5 - Shiok ah '];
db.collection('Feedback Ratings').get().then((snapshot => {
snapshot.docs.forEach(doc => {
FeebackRatings = doc.data().response;
let response = FeebackRatings.response;
dataArray.push(FeebackRatings);
// loop through the data array
dataArray.forEach(response => {
// -1 because if value is 1, i want to refer to index 0 of the array
if (dataArray[response - 1]) {
dataArray[response - 1] = dataArray[response - 1] += 1
}
if (!dataArray[response - 1]) {
dataArray[response - 1] = 1
}
})
})
}));
// expected outcome = [2, 1, 1] --- where first item in index represents the count of the item
console.log(dataArray);
let myChart = document.getElementById('myChart').getContext('2d');
let BarChart = new Chart(myChart, {
type: 'pie', //can create diff types using this; bar, horizontal, pie, line, donut, radar
data: {
labels: labelsArray,
datasets: [{
label: 'Total number',
data: dataArray,
backgroundColor: 'pink'
}]
},
options: {
title: {
display: true,
text: 'Feedback Statistics',
fontSize: 25
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.css" rel="stylesheet"/>
<canvas id="myChart"></canvas>
If it is a JS array, just use
dataArray.length
You are writing .then(), So I guess you might be dealing with a promise.
If you are dealing with a promise, code after the promise will execute before the code present in .then() will execute. So might need to do this.
1) Convert the code which you need to execute into a function
function displayChart(dataArray){
console.log(dataArray);
let myChart = document.getElementById('myChart').getContext('2d');
let BarChart = new Chart(myChart, {
type: 'pie', //can create diff types using this; bar, horizontal, pie, line, donut, radar
data: {
labels: labelsArray,
datasets: [{
label: 'Total number',
data: dataArray,
backgroundColor: 'pink'
}]
},
options: {
title: {
display: true,
text: 'Feedback Statistics',
fontSize: 25
}
}
});
}
2) And call this function at then end of your then () method.
const dataArray = [0, 0, 0, 0, 0];
let labelsArray = ['1 - Very Jialat', '2 - Jialat', '3 - Normal lor', '4 - Shiok a bit', '5 - Shiok ah '];
db.collection('Feedback Ratings').get().then(snapshot => {
snapshot.docs.forEach(doc => {
FeebackRatings = doc.data().response;
let response = FeebackRatings.response;
// loop through the data array
response.forEach(response, => {
labelsArray.forEach((value, index) => {
if (value == response) {
dataArray[index]++;
}
});
})
})
displayChart(dataArray);
});
I am creating a chart with Chartist.js. I'm getting json data with the Google embed API. I have a problem with this one. The array works with the values I give. But it does not work for data from json.
my code :
var TotalBrowser = [];
var BrowserSeries = [];
var oxyn = {
queryAnalytics: function() {
var id = '164690638';
var expressions = [{
expression: 'ga:hits'
}];
var dimension = [{
name: 'ga:browser'
}];
oxyn.getReportQuery(id, '7daysago', 'today', expressions, dimension).then(function(response) {
var formattedJson = JSON.stringify(response.result, null, 2);
var data = JSON.parse(formattedJson);
var i = 0;
BrowserTotal = data.reports[0].data.totals[0].values[0];
jQuery(data.reports[0].data.rows).each(function() {
if (i <= 3) {
jQuery('#Browsers').append(browsericon[i] + this.dimensions[0]);
var percent = (parseInt(this.metrics[0].values[0]) / parseInt(BrowserTotal)) * 100;
BrowserSeries.push(Math.round(percent));
TotalBrowser.push(Math.round(percent) + '%');
i++;
}
});
demo.initChartist();
});
}
}
var demo = {
initChartist: function() {
var dataPreferences = {
series: [
[BrowserSeries.join()]
]
};
var optionsPreferences = {
donut: true,
donutWidth: 40,
startAngle: 0,
total: 100,
showLabel: false,
axisX: {
showGrid: false
}
};
Chartist.Pie('#chartPreferences', dataPreferences, optionsPreferences);
Chartist.Pie('#chartPreferences', {
labels: [TotalBrowser.join()],
series: [BrowserSeries.join()]
});
console.log(BrowserSeries.join());
}
};
it does not work that way. But if I write the code like this, it works.
Chartist.Pie('#chartPreferences', {
labels: [TotalBrowser.join()],
series: [30, 70]
});
and this is working.
Chartist.Pie('#chartPreferences', {
labels: [TotalBrowser[0], TotalBrowser[1]],
series: [BrowserSeries[0], BrowserSeries[1]]
});
console output
console.log(BrowserSeries.join());
30,70
JSON Source
It's a very silly problem.
yes I finally solved it. I write for those who have the same problem.
Chartist.Pie('#chartPreferences', {
labels: TotalBrowser,
series: BrowserSeries
});
We need to remove [ ] characters. We must also send the data directly to the array.
Also : https://github.com/gionkunz/chartist-js/issues/738
Using chart.js 2.6 Is there a way to dynamically change the bars in my chart for values above zero and below zero? The graph series data is being generated via a call to a method. Right now its just a random number generator but will be a DB call.
function changeWOWData(chart) {
var datasets = chart.data.datasets;
var labelLen = chart.data.labels.length;
if (datasets[0]) {
for (i = 0, len = datasets.length; i < len; i++) {
try {
for (j = 0, len = labelLen; j < len; j++) {
datasets[i].data[j] = getRandomInt(-100, 100);
}
} catch (e) {
console.log(e.message);
}
}
}
}
Chart looks like this:
I want the chart bars above zero to be blue, the bars below zero to be red.
Any/all replies appreciated. Thanks in advance!
Griff
** Edit ** Added the code from the answer below as such:
var myBarChart = new Chart(wowChart, {
type: 'bar',
data: wowData,
plugins: [{
beforeDraw: function (c) {
var data = c.data.datasets[0].data;
for (var i in data) {
try {
var bar = c.data.datasets[0]._meta[0].data[i]._model;
if (data[i] > 0) {
bar.backgroundColor = '#07C';
} else bar.backgroundColor = '#E82020';
} catch (ex) {
console.log(ex.message);
}
console.log(data[i]);
}
}
}],
options: wowOptions
});
Every other line of the console I see the data element along with the exception
You could accomplish that using the following chart plugin :
plugins: [{
beforeDraw: function(c) {
var data = c.data.datasets[0].data;
for (let i in data) {
let bar = c.data.datasets[0]._meta['0'].data[i]._model;
if (data[i] > 0) {
bar.backgroundColor = '#07C';
} else bar.backgroundColor = '#E82020';
}
}
}]
add this followed by your chart options
ᴅᴇᴍᴏ
var ctx = document.getElementById("canvas").getContext('2d');
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'LEGEND',
data: [9, 14, -4, 15, -8, 10]
}]
},
options: {},
plugins: [{
beforeDraw: function(c) {
var data = c.data.datasets[0].data;
for (let i in data) {
let bar = c.data.datasets[0]._meta['0'].data[i]._model;
if (data[i] > 0) {
bar.backgroundColor = '#07C';
} else bar.backgroundColor = '#E82020';
}
}
}]
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="canvas" height="180"></canvas>
in chartjs v3 you can use Simply scriptable option
example:
datasets: [
{
data: this.chartData,
backgroundColor(context) {
const index = context.dataIndex
const value = context.dataset.data[index]
return value < 0 ? 'red' : 'blue'
}
}
]
visit https://www.chartjs.org/docs/latest/general/options.html#scriptable-options
Issue with C3.js plotting xy chart. Any help will be appreciated.
var T = [];
var F = [];
var n = [];
T.push('T');
F.push('F');
d3.csv("/abc.csv", function(d) {
{
True_SJs.push(d[" T"]);
False_SJs.push(d[" F"]);
},
function(error, rows) {
var chart = c3.generate({
bindto: '#roc',
data: {
x: 'F',
columns: [
F,
T
],
order: 'asc',
selection: {
grouped: true
}
},
zoom: {
enabled: true,
rescale: true,
extent: [1, 10]
},
tooltip: {
format: {
title: function(x) {
return 'F' + x;
}
}
},
grid: {
x: {
show: true
},
y: {
show: true
}
}
});
});
Expected output should be line starting from top of point at 4 and connect next point.
Here is the link of example http://c3js.org/samples/simple_xy.html
This is because your data has repeating x values and corresponding y values are go up and then down. Something like
F => ['F', .... 4, 4, 4, ...];
T => ['T', .... 21, 23, 22, ...];
If you want the line to be continuous you can sort the T values when the F (X) values are equal. Something like
// construct an array of arrays - each element contains the T values for an F value
var groupedT = [];
T.forEach(function (e, i) {
// skip element 0 (the label)
if (i) {
groupedT[T[i]] = groupedT[T[i]] || [];
groupedT[T[i]].push(e);
}
})
// flattening array of arrays
T = groupedT.reduce(function (a, b) {
return a.concat(b.sort());
}, ['T']);
before you generate your chart will get you what you need.
Here I have google visualisation datatable:
How I can add here new Row and on postion [0] to put string "Sum" in position 1 will be 10, in position [2] will be 17, as sum of columns...
I start to write code but I'n not going anywhere ...
I try:
data.addRow();
for (var x=1, maxcol=data.getNumberOfColumns(); x < maxcol; x++) {
for y=1, maxrow=data.getNumberOfRows(); y < maxrow; y++) {
//WHAT NEXT ???
Try this:
var group = google.visualization.data.group(data, [{
type: 'number',
column: 0,
modifier: function () {return 0;}
}], [{
type: 'number',
column: 1,
aggregation: google.visualization.data.sum
}, {
type: 'number',
column: 2,
aggregation: google.visualization.data.sum
}]);
data.addRow(['Sum', group.getValue(0, 1), group.getValue(0, 2)]);
[Edit - added code to handle an arbitrary number of columns]
var groupColumns = [];
for (var i = 1; i < data.getNumberOfColumns(); i++) {
groupColumns.push({
type: 'number',
column: i,
aggregation: google.visualization.data.sum
});
}
var group = google.visualization.data.group(data, [{
type: 'number',
column: 0,
modifier: function () {return 0;}
}], groupColumns);
var row = ['Sum'];
for (var i = 1; i < group.getNumberOfColumns(); i++) {
row.push(group.getValue(0, i));
}
data.addRow(row);