I am trying to use Chart.js Bar Chart to display a set of data.
My data is weekly based so to my method I send the year and week and get the data back in 3 columns; Product, Area and Amount.
What I want is to have to Products horizontaly and in each Product I want different bars for each Area and offcourse the Amount verticaly. (Bonus: If an Area nothing in that product it should not be shown in that particular Product)
The problem is that the number of Products and the number of Areas can vary from each week. And I can't seem to find a good way to loop through the data and create the datasets the way chart.js wants.
Also tried using Underscore.js to group it but the fact that the each Area doesn't always have an amount for a spesific product seems to be causing some issues.
So I guess you have to loop through the data and map that data to another predefined array for each Area so it can match this structure somehow??
Also open for other Chart plugins, but really liked how Chart.js animates the data. And if I get this working I can probably figgure out an update method for when you change week.
To get the labels i can f.ex do this:
$.ajax({
....
success: function (d) {
var a = _.groupBy(d.data, function (d) { return d.Product });
var labels = [];
$.each(a, function (i, value) {
labels.push(i);
});
}
});
With data in this format
var myJSONData = [
{
Product: 'P1',
Area: 'A1',
Value: 12
},
...
]
You can use this function to convert it into the format Chart.js requires
var data = {
labels: [],
datasets: []
}
var colors = ['Red','Blue','Green', ...] // add as many colors as there will be areas (maximum)
myJSONData.forEach(function (e) {
// create labels
var labelIndex = data.labels.indexOf(e.Product)
if (labelIndex === -1) {
labelIndex = data.labels.length;
data.labels.push(e.Product);
// dummy entries for each dataset for the label
data.datasets.forEach(function (dataset) {
dataset.data.push(0)
})
}
// get the area dataset
var area = data.datasets.filter(function(area){
return (area.label === e.Area);
})[0]
// otherwise create it
if (area === undefined) {
area = {
label: e.Area,
// create a dummy array with an entry for each of the existing labels
data: data.labels.map(function () {
return 0;
}),
fillColor: colors[data.datasets.length]
};
data.datasets.push(area)
}
// set the value
area.data[labelIndex] = e.Value;
})
and use that to display the chart.
Fiddle - http://jsfiddle.net/jt4Lqkn3/
(Bonus: If an Area nothing in that product it should not be shown in
that particular Product)
You can't change any configuration to do this - there will be a space left for each series.
However you might want to set the strokeColor to a transparent value (e.g. strokeColor: "rgba(0, 0, 0, 0)", just below the fillColor line) and set the barStrokeWidth option to 0, so that 0 values don't show up at all on the chart (otherwise there will be thin line shown)
new Chart(ctx).Bar(data, {
barStrokeWidth: 0,
});
Related
I'm currently trying to write a short program that captures data on mouse movements, scrolling and keyboard clicks from my laptop. This data is then saved in a firebase real time database and deployed to a simple webpage.
I'm using chart.js to plot this data on the page, but when I plot the data, the first adn last points get connected together (see image). I'm having to use the scatter plot as opposed to the line plot as I want the values to be distributed relatively along the x axis. The line chart.js plot places them with equal spacing (which looks weird for a time series bit of data!).
As this is a time series data set this is clearly quite annoying! Anyone have any ideas how I can stop this from happening??
Any help much appreciated!
//This code is taken from a larger script, I'm fairly sure these are all the relevant parts
//This is a js file which manages 3 chart.js charts on a webpage.
//This function generates the chart template
function chartTemplateCreation(InputLabel)
{
const chartTemplate = {
type: 'scatter',
data: {
datasets: [{
label: InputLabel,
data: [],
//backgroundColor: 'rgba(255, 205, 210, 0.5)'
}]
},
options: {
legend: {
display: false
},
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [{
ticks: {
suggestedMin: 7,
suggestedMax: 22
}
}]
}
}
}
return chartTemplate
}
//These values create links to the html positioning of the charts
const blankMouseChart = document.getElementById('mouse-chart').getContext('2d')
const blankKeysChart = document.getElementById('keys-chart').getContext('2d')
const blankScrollChart = document.getElementById('scroll-chart').getContext('2d')
//Charts are created
const mouseChart = new Chart(blankMouseChart,chartTemplateCreation('mouse'))
const keysChart = new Chart(blankKeysChart, chartTemplateCreation('keys'))
const scrollChart = new Chart(blankScrollChart, chartTemplateCreation('scroll'))
//This function is used to add to new data to the chart
function addDataScatter(chart, time, value)
{
//chart.data.labels.push(label);
chart.data.datasets.forEach((dataset) =>
{
dataset.data.push({x:time,y:value})
})
chart.update()
}
//collect the firebase reference
const dataRefTenMin = firebase.database().ref(date + "/10min_culmalative/")
//initially add all the historic values from today
dataRefTenMin.once("value").then(function(snapshot)
{
snapshot.forEach(function(childSnapshot)
{
var childData = childSnapshot.val()
var decTime = childData.decTime
addDataScatter(mouseChart, decTime, childData.mouse)
addDataScatter(scrollChart, decTime, childData.scroll)
addDataScatter(keysChart, decTime, childData.keys)
})
})
//then live update the graph by adding data points every time a child is added to the firebase realtime database
dataRefTenMin.on("child_added", function(data)
{
var newData = data.val()
var decTime = newData.decTime
addDataScatter(mouseChart,newData.decTime, newData.mouse)
addDataScatter(scrollChart,newData.decTime, newData.scroll)
addDataScatter(keysChart,newData.decTime, newData.keys)
})
Ah! Figured out the answer by mistake! Accidently commented out a section of code and it now works. All you have to do is remove the section which was meant to add the historic data. As it turns out, both:
dataRefTenMin.once("value").then(function(snapshot)
{
snapshot.forEach(function(childSnapshot)
{
var childData = childSnapshot.val()
var decTime = childData.decTime
addDataScatter(mouseChart, decTime, childData.mouse)
addDataScatter(scrollChart, decTime, childData.scroll)
addDataScatter(keysChart, decTime, childData.keys)
})
})
and
dataRefTenMin.on("child_added", function(data)
{
var newData = data.val()
var decTime = newData.decTime
addDataScatter(mouseChart,newData.decTime, newData.mouse)
addDataScatter(scrollChart,newData.decTime, newData.scroll)
addDataScatter(keysChart,newData.decTime, newData.keys)
})
add historic data to the chart. Hence the error comes as they plot the data over each other (I think).
If any one else has a similar problem, remove the first one of the two portions of code above! Then the chart will both plot the historic data and update live.
I am trying to create a bubble chart using the JS HighChart in Angular2+. Whenever there are more than 50 data points (bubbles), the graph breaks. There are the correct number of bubbles in the correct positions (x,y plots) with all different colors but the sizes are all the same even though the z-values are all different. (I am outputing the z-values in a tooltip and the z-values are accurate)
This function is how I am passing in data to the high-chart configuration.
setSeries() {
this.objData = []
this.Data.forEach(element => {
var x= element['xVal'];
var y = element['yVal'];
var z = element['zVal'].toFixed(0);
var name = element['seriesName'].trim();
var newData =[{
x:x,
y:y,
z:+z,
}]
// SetSeriesData is how i am creating the obj to pass into series=[] in highchart configuration
if(i<50) //If I comment this condition, the graph breaks. Right now, the graph is working properly
this.setSeriesData(sumData, name, this.objData)
i++
})
this.options.series = this.objData;
this.generateChart();
}
This is my setSeriesData function.
setSeriesData(graphData: any, dataName: any, objData: any){
var obj = {};
obj['name'] = dataName;
obj['data'] = graphData;
obj['events'] = {click: function(e) {
//takes me to another link
}};
objData.push(obj)
}
In the above function, I configured the chart so that when you click the bubble, it takes you to another page. When the data points >50, this click functionality is not working either. In addition, the fillOpacity is not correct.
Just a few things to point out
1. I am using Angular 2+
2. The discovered issues are, fillOpacity, click, and size based on z-value.
3. It works perfectly when the data points are less than 50
How can I fix this?
We have scatter plots working great in our dashboard, but we have been thrown a curve ball. We have a new dataset that provides multiple y values for a single key. We have other datasets were this occurs but we had flatten the data first, but we do not want to flatten this dataset.
The scatter plot should us the uid for the x-axis and each value in the inj field for the y-axis values. The inj field will always be an array of numbers, but each row could have 1 .. n values in the array.
var data = [
{"uid":1, "actions": {"inj":[2,4,10], "img":[10,15,25], "res":[15,19,37]},
{"uid":2, "actions": {"inj":[5,8,15], "img":[5,8,12], "res":[33, 45,57]}
{"uid":3, "actions": {"inj":[9], "img":[2], "res":[29]}
];
We can define the dimension and group to plot the first value from the inj field.
var ndx = crossfilter(data);
var spDim = ndx.dimension(function(d){ return [d.uid, d.actions.inj[0]];});
var spGrp = spDim.group();
But are there any suggestions on how to define the scatter plot to handle multiple y values for each x value?
Here is a jsfiddle example showing how I can display the first element or the last element. But how can I show all elements of the array?
--- Additional Information ---
Above is just a simple example to demonstrate a requirement. We have developed a dynamic data explorer that is fully data driven. Currently the datasets being used are protected. We will be adding a public dataset soon to show off the various features. Below are a couple of images.
I have hidden some legends. For the Scatter Plot we added a vertical only brush that is enabled when pressing the "Selection" button. The notes section is populated on scatter plot chart initialization with the overall dataset statistics. Then when any filter is performed the notes section is updated with statistics of just the filtered data.
The field selection tree displays the metadata for the selected dataset. The user can decide which fields to show as charts and in datatables (not shown). Currently for the dataset shown we only have 89 available fields, but for another dataset there are 530 fields the user can mix and match.
I have not shown the various tabs below the charts DIV that hold several datatables with the actual data.
The metadata has several fields that are defined to help use dynamically build the explorer dashboard.
I warned you the code would not be pretty! You will probably be happier if you can flatten your data, but it's possible to make this work.
We can first aggregate all the injs within each uid, by filtering by the rows in the data and aggregating by uid. In the reduction we count the instances of each inj value:
uidDimension = ndx.dimension(function (d) {
return +d.uid;
}),
uidGroup = uidDimension.group().reduce(
function(p, v) { // add
v.actions.inj.forEach(function(i) {
p.inj[i] = (p.inj[i] || 0) + 1;
});
return p;
},
function(p, v) { // remove
v.actions.inj.forEach(function(i) {
p.inj[i] = p.inj[i] - 1;
if(!p.inj[i])
delete p.inj[i];
});
return p;
},
function() { // init
return {inj: {}};
}
);
uidDimension = ndx.dimension(function (d) {
return +d.uid;
}),
uidGroup = uidDimension.group().reduce(
function(p, v) { // add
v.actions.inj.forEach(function(i) {
p.inj[i] = (p.inj[i] || 0) + 1;
});
return p;
},
function(p, v) { // remove
v.actions.inj.forEach(function(i) {
p.inj[i] = p.inj[i] - 1;
if(!p.inj[i])
delete p.inj[i];
});
return p;
},
function() { // init
return {inj: {}};
}
);
Here we assume that there might be rows of data with the same uid and different inj arrays. This is more general than needed for your sample data: you could probably do something simpler if there is indeed only one row of data for each uid.
To flatten out the resulting group, with we can use a "fake group" to create one group-like {key, value} data item for each [uid, inj] pair:
function flatten_group(group, field) {
return {
all: function() {
var ret = [];
group.all().forEach(function(kv) {
Object.keys(kv.value[field]).forEach(function(i) {
ret.push({
key: [kv.key, +i],
value: kv.value[field][i]
});
})
});
return ret;
}
}
}
var uidinjGroup = flatten_group(uidGroup, 'inj');
Fork of your fiddle
In the fiddle, I've added a bar chart to demonstrate filtering by UID. Filtering on the bar chart works, but filtering on the scatter plot does not. If you need to filter on the scatter plot, that could probably be fixed, but it could only filter on the uid dimension because your data is too course to allow filtering by inj.
Dynamically updating a chartjs chart and creating the labels in an array format (["A","B","C"]). However chartjs doesn't accept a push of the label array unless it is in the format "A","B","C" (without brackets). Anyone else experience this or have I misunderstood? Se code below. Produces this
Instead of this (ok when adding labels as chart.data.labels.push("A","B","C","D")
var chart = new Chart(document.getElementById("element"), {
type: 'bar',
options: {
legend: {
display: false
}
}
});
//PUSH DATA TO GRAPH.
var verserier = [];
var veromslperserie = [];
var stat = seriestat(); //function to retrieve data for labels
$.each(stat, function(i, item) {
verserier.push(i);
veromslperserie.push(item["omsl"]);
});
chart.data.labels.push(verserier); //error occurs here
chart.data.datasets.push({
label: "Omsl",
data: veromslperserie,
backgroundColor: colorarray,
});
chart.update();
When you push outside of the loop you are actually pushing verserier into position [n], which in this case is 0.
If you do not add values again you can do
chart.data.labels = verserier
I don't have the explanation of the "why", but here's a workaround :
var data_array = new Array();
// This doesn't work (but with no error on my side, appart from visually wrong labels)
myChart.data.labels.push(data_array);
// But this works
for(i=0;i<data_array.length;i++)
{
myChart.data.labels.push(data_array[i]);
}
The data series in my HighCharts chart only includes dates from the past few days, but the chart's x-axis and zoom bar show a date range all the way back to 1970.
How can I limit the presented date range so it only goes back as far as the first date present in the series data?
Example
HTML
<div id="chart_container" style="height: 500px></div>
JavaScript
$(function () {
var timestamps = [1481000484000,1481108510000,1481215541000,1481316568000,1481417583000];
var series_raw_data = [100,300,750,400,200];
// prepare data
var points = [[],[]];
for (var i = 0; i < timestamps.length; i++) {
points.push([timestamps[i], series_raw_data[i]]);
}
// create chart
$('#chart_container').highcharts('StockChart', {
series: [{
data: points
}]
});
});
Here's Fiddle1 which shows the behavior.
I also tried setting the xAxis 'min' option to the first timestamp, and setting the axis type to 'datetime', but those didn't help - Fiddle2.
The reason why it happens is your points array.
If fact, after filling, it looks like this:
points = [ [], [], [x, y], [x, y]]
Those two empty arrays create unwanted behaviour.
Fix the initial array and it works
var points = [];
example: https://jsfiddle.net/hbwosk3o/3/