C3.js plotting xy chart, line not getting connected properly between points - javascript

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.

Related

How to group javascript array based on some property?

I have a JS array of objects like this:
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
lines are unique in entire myArray , each line has some texts (which are not unique).
How to match each text to its corresponding lines?
The final result should be like this:
var myNewArray = [
{ text: 31, line: [20] },
{ text: 80, line: [20, 10] },
{ text: 22, line: [10] }
]
Some approaches with Map.
As result you get a temporary map which collects all text, grouped by line. To get an array of objects, map the key/values pairs as eanted properties.
Because of having nested array of the data, you need eiter to normalize the data to get single line/text values and then add a grouping by text,
const
data = [{ line: 20, text: [31, 80] }, { line: 10, text: [80, 22] }],
result = Array.from(
data
.flatMap(({ line, text }) => text.map(text => ({ text, line })))
.reduce((m, { text, line }) => m.set(text, [...(m.get(text) || []), line]), new Map),
([text, line]) => ({ text, line })
);
console.log(result);
Or do it in a single step but with a nested approach of reducing the outer (line) and inner arrays (text arrays).
const
data = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
],
result = Array.from(
data.reduce(
(m, { line, text }) =>
text.reduce(
(n, text) => n.set(text, [...(n.get(text) || []), line]),
m
),
new Map
),
([text, line]) => ({ text, line })
);
console.log(result);
Here's how:
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
var newArray = myArray.reduce((acc, {line, text}) => {
for( let t of text ){
const match = acc.find(({text}) => text == t) // check if the text already exists in newArray
if( match ) match.lines.push(line) // if exists, add the line to that text
else acc.push({text:t, lines:[line]}) // it not, create a new object with that line
}
return acc
}, [])
console.log( newArray )
Or by first generating an Object instead of an Array, which is faster if your dataset is huge, and then convert that to an Array at the end:
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
// generate a key/value pairs for text/lines
var newArray = myArray.reduce((acc, {line, text}) => {
for( let t of text )
acc[t] = [...(acc[t] || []), line]
return acc
}, {})
// convert the above Object to an Array of Objects (AKA Collection)
newArray = Object.entries(newArray).map(([text,lines]) => ({text, lines}))
console.log( newArray )
Probably easiest by first building an intermediate Map that indexes lines by text:
const data = [
{line: 20, text: [31,80]},
{line: 10, text: [80,22]}
];
const result = [...data.reduce((map, {line, text}) => {
text.forEach(t => {
map.has(t) || map.set(t, []);
map.get(t).push(line);
});
return map;
}, new Map()).entries()].map(([text, line]) => ({text, line}));
console.log(result);
Here is a simple solution to your problem.
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
var myNewArray = []
myArray.forEach((item) => {
item.text.forEach((t) => {
const index = myNewArray.findIndex((e => e.text === t))
index === -1
? myNewArray.push({text: t, line: [item.line]})
: myNewArray[index].line.push(item.line)
})
})
console.log(myNewArray)

How to count the number of items in the arraylist

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);
});

Chartist.js Array with data

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

Chart.js : Is there a way to name each bubble in chart?

I have created a bubble chart using chart.js,which looks like the below
Is there a way to name each and every bubble in the chart? I am planning to put a data box below this chart. On clicking each bubble data box should display info associated with each bubble. Each bubble will have its own data like maturity_date,bond_type,credit_rating,symbol,etc... How can I name each bubble? These bubbles are created dynamically. This is the code I use to create the chart
$(document).ready(function(){
$.ajax({url: "xxxxxxxx.x.xx", success: function(result){
var dataObj = {};
dataObj.datasets = [];
var object = {};
object.label = 'First Dataset';
object.backgroundColor = [];
object.hoverBackgroundColor = [];
object.data = [];
var resultData = result.data;
var currentYear = new Date().getFullYear();
for (var i=0; i<resultData.length; i++) {
if(resultData[i].hasOwnProperty("maturity_date") && resultData[i].hasOwnProperty("ask_ytm")) {
var maturity_date = resultData[i].maturity_date.split("-");
var matYear = new Date(maturity_date[1]+"-"+maturity_date[0]+"-"+maturity_date[2]).getFullYear();
if (resultData[i].bond_type == "Tax Free" )
{
object.backgroundColor.push("#34A10C");
object.hoverBackgroundColor.push("#34A10C");
}
else
{
object.backgroundColor.push("#1130E8");
object.hoverBackgroundColor.push("#1130E8");
}
object.data.push({x: (matYear - currentYear), y: resultData[i].ask_ytm, r: 4});
}
}
dataObj.datasets.push(object);
var ctx = document.getElementById("myChart");
var myBubbleChart = new Chart(ctx,{
type: 'bubble',
data : dataObj,
legend: {
display: false
},
responsive: true,
maintainAspectRatio: true,
}
});
}});
});
In your data declaration, you can add custom properties if you need to :
data: [{
x: 20,
y: 30,
r: 15,
symbol: "£",
bond_type: "corporate"
}, {
x: 40,
y: 10,
r: 10,
symbol: "$",
bond_type: "james"
} /* ... */]
Since this data is dynamic, you need to do it from your back-end of course.
Afterwards, you can access these new properties in your callback (onClick event for instance) :
options: {
onClick: function(e) {
var element = this.getElementAtEvent(e);
if (element.length > 0) {
var data = this.config.data.datasets[element[0]._datasetIndex].data[element[0]._index];
console.log(data);
// You can have the following for instance :
// data -> { x:40, y:10, r:10, symbol:"$", bond_type:"james" }
}
}
}

How to create an average line for an irregular time graph?

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:

Categories