how to update plotly.js chart with new data from website? - javascript

I am trying to update the chart with plotly.js with data from a server. My function should plot updated data in the charts every time new data is sent.
I have two different graphs in my chart. Both with the same X value but different values for the Y axis. I can create the expected graphs in the chart with the first values. But when new values are sent, the restyle function did not update the values.
I don't know how to parse the data to the restyle function. Or, do I need to use a different function?
var source = new EventSource("http://old.iolab.sk/evaluation/sse/sse.php");
source.onmessage = function(event) {
count++;
if(count == 1){ // only on first data from server
var layout = {title: "Website graph"};
var trace1 = {
x: [event.data.match(/\d+/g)[0]],
y: [(event.data.match(/\d+/g)[2] + "." + event.data.match(/\d+/g)[3])], // y1 from server
name: "hehe",
type: 'scatter'
};
var trace2 = {
x: [event.data.match(/\d+/g)[0]],
y: [(event.data.match(/\d+/g)[5] + "." + event.data.match(/\d+/g)[6])], // y2 from server
name: "hehe",
type: 'scatter'
};
Plotly.newPlot("websiteGraph", [trace1, trace2], layout);
return;
}
if(!isClicked){ // keeps updating the chart with data from the server until the button is clicked
trace1 = {'x': [[event.data.match(/\d+/g)[0]]], 'y': [[(event.data.match(/\d+/g)[2] + "." + event.data.match(/\d+/g)[3])]]},
trace2 = {'x': [[event.data.match(/\d+/g)[0]]], 'y': [[(event.data.match(/\d+/g)[5] + "." + event.data.match(/\d+/g)[6])]]};
Plotly.restyle("websiteGraph", trace1+trace2);
}
return;
}
This is the data from the server, one ID is for one update:
id: 0
data: {
data: "x": "0",
data: "y1": "0.05",
data: "y2": "1.03"
data: }
id: 1
data: {
data: "x": "1",
data: "y1": "0.077452406437284",
data: "y2": "1.0998476951564"
data: }
id: 2
data: {
data: "x": "2",
data: "y1": "0.1048994967025",
data: "y2": "1.0893908270191"
data: }
id: 3
data: {
data: "x": "3",
data: "y1": "0.13233595624294",
data: "y2": "1.0086295347546"
data: }

To answer your question, try using Plotly.react or Plotly.newPlot for new data.
Plotly.restyle seems to be wrong (layout changes only?), as well as some argument passed into the functions.
The comments and examples below are from the Plotly docs at https://plotly.com/javascript/plotlyjs-function-reference/#plotlyrestyle.
To see the code in action, check out this fiddle: https://jsfiddle.net/a0cj6bnm/
In restyle, arrays are assumed to be used in conjunction with the trace indices provided. Therefore, to apply an array as a value, you need to wrap it in an additional array. For example:
var graphDiv = document.getElementById(graphDiv);
var update = {
opacity: 0.5,
marker: {
size: [40, 60, 80, 100],
color: ['rgb(93, 164, 214)', 'rgb(255, 144, 14)', 'rgb(44, 160, 101)', 'rgb(255, 65, 54)']
}
}
Plotly.restyle(graphDiv, update, 0);
Note: To me, it seems that restyle is for layout changes only. Not sure though. Instead of 0, restyle can get an array of values but this had no effect on my tests.
newPlot draws a new plot in an element, overwriting any existing plot. To update an existing plot in a , it is much more efficient to use Plotly.react than overwrite it.
The function seems to expect an array and a layout hash as an argument.
var graphDiv = document.getElementById(graphDiv);
var layout = {
title: "Website graph",
xaxis: {
title: 'Year',
showgrid: false,
zeroline: false
},
yaxis: {
title: 'Percent',
showline: false
}
};
var trace1 = {
x: [1],
y: [1], // y1 from server
name: "hehe1",
type: 'scatter'
};
var trace2 = {
x: [2],
y: [2],
name: "hehe2",
type: 'scatter'
};
Plotly.newPlot(graphDiv, [trace1, trace2], layout);
Plotly.react has the same signature as Plotly.newPlot above, and can be used in its place to create a plot, but when called again on the same will update it far more efficiently than Plotly.newPlot, which would destroy and recreate the plot. Plotly.react is as fast as Plotly.restyle/Plotly.relayout documented below.
var graphDiv = document.getElementById(graphDiv);
var trace1 = {
x: [1,2,3,4,5],
y: [1,3,5,7], // y1 from server
name: "hehe1",
type: 'scatter'
};
var trace2 = {
x: [2,3,4,5,7,8],
y: [2,2,2,2,3,4],
name: "hehe2",
type: 'scatter'
};
Plotly.react(graphDiv, [trace1, trace2], layout);

Related

Getting Zoomdata data to work with echarts index.js stacked line chart

I am working with a echarts javascript chart and trying to get it to work with my data in Zoomdata. I have the data grouped by 20 different computers so I am looking to do a stacked line chart with 20 lines. I know how to hard code this but I would like to link the data in Zoomdata to the code to display in the chart. Right now it just plots all 20 computers on one line.
import echarts from 'echarts'; //
import styles from './index.css';
// create chart container
const chartContainer = document.createElement('div');
chartContainer.style.width = '100%';
chartContainer.style.height = '100%';
chartContainer.classList.add(styles.chartContainer);
controller.element.appendChild(chartContainer);
const groupAccessor = controller.dataAccessors['Group By'];
const metricAccessor = controller.dataAccessors.Size;
//Need help
//Part Im having trouble with linking data in zoomdata to this chart
const chart = echarts.init(chartContainer);
const option = {
xAxis: {
type: 'category',
data: []
},
yAxis: {
type: 'value'
},
series: [
{
name:'邮件营销',
type:'line',
stack: '总量',
data:[120, 132, 101, 134, 90, 230, 210]
},
{
name:'联盟广告',
type:'line',
stack: '总量',
data:[220, 182, 191, 234, 290, 330, 310]
},
{
name:'视频广告',
type:'line',
stack: '总量',
data:[150, 232, 201, 154, 190, 330, 410]
},
{
name:'直接访问',
type:'line',
stack: '总量',
data:[320, 332, 301, 334, 390, 330, 320]
},
{
name:'搜索引擎',
type:'line',
stack: '总量',
data:[820, 932, 901, 934, 1290, 1330, 1320]
}
]
};
//Rest of code
controller.update = data => {
// Called when new data arrives
option.series[0].data = reshapeData(data);
chart.setOption(option);
};
function reshapeData(data) {
return data.map(d => ({
name: groupAccessor.raw(d),
value: metricAccessor.raw(d),
datum: d,
itemStyle: { //tell the chart you would like to use the colors selected
color: groupAccessor.color(d),//tell the chart you would like to use the colors selected
}, //tell the chart you would like to use the colors selected
}));
}
chart.on('mousemove', param => {
controller.tooltip.show({
event: param.event.event,
data: () => param.data.datum,
});
});
chart.on('mouseout', param => {
controller.tooltip.hide();
});
chart.on('click', param => {
controller.menu.show({
event: param.event.event,
data: () => param.data.datum,
});
});
controller.createAxisLabel({
picks: 'Group By',
position: 'bottom',
orientation: 'horizontal',
});
controller.createAxisLabel({
picks: 'Size',
position: 'bottom',
orientation: 'horizontal',
});
The json looks like:
[
{
current: {
count: 1508,
metrics: null,
na: false
},
group: [
"Computer1"
]
},
{..},
{..}
]
Thanks for adding the json details. If I understood it good, the value you want to display on each line must be current.count, and the name of each series is given by the first item in the group array (I don't know why it's an array, though).
Here is the code I would write if I want to map your data on ECharts:
/*
* incremental update counter. This will be displayed
* on the xAxis by being pushed to options.xAxis.data array.
*/
let updateCount = 0,
// initialize series as empty
const options = {
xAxis: {
type: 'category',
data: []
},
yAxis: {
type: 'value'
},
series: []
}
controller.update = data => {
updateCount++
if (options.series.length > 0) {
// Called when new data arrives
options.xAxis.data.push('record ' + updateCount)
options.series = updateData(data)
} else {
// Called only once to initialize
options.xAxis.data.push('record ' + updateCount)
options.series = initData(data)
}
// you can remove the following line if your chart is already reactive.
chart.setOption(option)
}
// the init function.
const initData = data => {
// transform each item in the data array into a series entry.
data.map(item => {
return {
name: item.group[0],
type: 'line',
stack: 'defaultStack',
data: [item.current.count]
}
})
}
// the update function.
const updateData = newData => {
// push new data counts to its respective series data.
options.series.forEach((item, index) => {
item.data.push(newData[index].current.count)
}
}
It's a bit long but more secure way to parse your raw data into an ECharts option. Let me know if you have any issue with this, I haven't tested yet so it's only brain code.

How do I use the return values from csv ajax request for x and y values?

I am trying to import a csv file and use it as the data source for my scatter chart in ChartJs. When I print dataPoints I get object arrays of the correct value so I know the ajax request is pulling properly. But I cant seem to pass these values into my datasets data as x and y values.
I was able to successfully pull the data and plug it into x and y values using canvasJS unfortunately that framework isn't free so I am trying to switch to chart.js. Ive tried plugging in data: [{dataPoints: pullData()}] or data: pullData() or data: [dataPoints] and plenty of other combinations but none seem to work. I am familiar with coding in swift and java but I'm particularly new to Javascript. Any help would be greatly appreciated!
const CHART = document.getElementById("scatterChart");
console.log(CHART);
var scatterChart = new Chart(CHART, {
type: 'scatter',
data: {
datasets: [{
label: 'Scatter Dataset',
data: [{
x: -10,
y: 0
}, {
x: 0,
y: 10
}, {
x: 10,
y: 5
}],
borderColor: 'black',
borderWidth: 1,
pointBackgroundColor: '#00bcd6',
pointRadius: 5,
pointHoverRadius: 5,
fill: false,
tension: 0.5,
showLine: true
}]
},
options: {
scales: {
xAxes: [{
type: 'linear',
position: 'bottom'
}],
yAxes: [{
type: 'linear',
position: 'left'
}]
}
}
});
window.onload = function(){
$.ajax({
type: "GET",
url: "mockData.csv",
dataType: "text",
success: function (result){
var data = $.csv.toArrays(result);
var dataPoints = [];
for (var i = 0; i < data.length; i++)
dataPoints.push({
x: data[i][0],
y: data[i][1]
});
console.log(dataPoints)
return dataPoints
}
})
};
My goal is to plot the points from the csv (which is just 5 rows and 2 columns of basic numbers) but instead no points show up. Where as when I hand jam the values into x and y everything shows up fine.
You need to move the creation of the chart to inside the success function of the ajax call, or pass dataPoints into some other function that creates the chart.
After #terpinmd suggestion I was able to resolve the problem. Below is the code solution:
function getDataPointsFromCSV(csv) {
var dataPoints = csvLines = points = [];
for (var i = 0; i < csv.length; i++)
dataPoints.push({
x: csv[i][0],
y: csv[i][1]
});
console.log(dataPoints)
return dataPoints
}
window.onload = function(){
var dataPoints = [];
$.ajax({
type: "GET",
url: "mockData.csv",
dataType: "text",
success: function (result){
var data = $.csv.toArrays(result);
const CHART = document.getElementById("scatterChart");
console.log(CHART);
var scatterChart = new Chart(CHART, {
type: 'scatter',
data: {
datasets: [{
label: 'Scatter Dataset',
data: getDataPointsFromCSV(data),
borderColor: 'black',
borderWidth: 1,
pointBackgroundColor: '#00bcd6',
pointRadius: 5,
pointHoverRadius: 5,
fill: false,
tension: 0.5,
showLine: true
}]
},
options: {
scales: {
xAxes: [{
type: 'linear',
position: 'bottom'
}],
yAxes: [{
type: 'linear',
position: 'left'
}]
}
}
});
}
})
};

create a chart.js point dataset from json

I am trying to create a line plot from the json response from a webApi. I would like to create a time point dataset which looks like below:
var adddata = {
datasets: [{
label: "a",
backgroundColor: "rgba(255,0,0,0.5)",
fill: false,
data: [{
x: newDateString(0),
y: 22
}, {
x: newDateString(2),
y: 25
}, {
x: newDateString(4),
y: 12
}, {
x: newDateString(5),
y: 22
}],
}, {
label: "b",
backgroundColor: "rgba(0,255,0,0.5)",
fill: false,
data: [{
x: newDate(0),
y: 34
}, {
x: newDate(1),
y: 22
}, {
x: newDate(4),
y: 2
}, {
x: newDate(5),
y: 13
}]
}]
};
Now if I have a json which looks like:
{"Values":{"2018-01-17 09:24:34":"0","2018-01-17 09:24:31":"0","2018-01-17 09:24:33":"0","2018-01-17 09:24:35":"0"}}
Would it be possible to create a data set defining individual points?
You can parse your JSON string and transform resulting json object to dataset like below:
var json_str = '{"Values":{"2018-01-16 09:24:34":"10","2018-01-16 19:24:31":"5","2018-01-17 09:24:33":"8","2018-01-18 09:24:35":"9"}}'
var json = JSON.parse(json_str);
var data = [];
for (var d in json.Values) {
console.log(d, json.Values[d]);
data.push({
x : new Date(d),
y : json.Values[d]
})
}
var dataset = {
label : "c",
backgroundColor : "rgba(0,0,255,0.5)",
borderColor : 'green',
fill : false,
data : data
};
// add to existing datasets
adddata.push(dataset);
So this is the result: https://jsfiddle.net/beaver71/mfvc9p9x/

C3.js - stacked bar chart - can I make values to contain instead of stack each other?

I'm using C3.js library to create a stacked bar chart (my current code is in jsfiddle on the bottom). The problem is that by default the columns are... stacked. Since I need to create columns with min, average and max values, I'd like the values to rather contain each other, not stack. E.g. if I have min = 10, average = 50 and max = 100, I'd like the bar to be of the height 100, not 160. Is there any built in way to support such behavior?
My current code:
<div id="chart"></div>
<script>
var chart = c3.generate({
bindTo: '#chart',
data: {
columns: [
['min', 10, 25, 15],
['avg', 50, 33, 51],
['max', 100, 75, 200]
],
type: 'bar',
groups: [
['min', 'avg', 'max']
]
}
});
</script>
Here is a jsfiddle with my code:
https://jsfiddle.net/gguej6n0/
Right, I'm adding this as another answer as it's completely different plus if I changed my original answer the comments would make no sense..
This time I'm taking advantage of err... feature (bug?) found in c3 that caused another user to unintentionally get the effect that you wanted.
c3.js generate a stacked bar from JSON payload
Basically, if you supply the data as json you can get the effect you want if you supply each datum as a separate point
e.g. doing this will overplot min and max on the same column even if they are meant to be stacked
[{ "x-axis": "0",
"min": 30
},
{ "x-axis": "0",
"max": 40
}],
Whereas, this way will stack them:
[{ "x-axis": "0",
"min": 30,
"max": 40
}],
So what we need is a routine to turn the original column-based data into a json object where every datum is parcelled up separately:
http://jsfiddle.net/gvn3y0q6/5/
var data = [
['min', 10, 25, 15, 12],
['avg', 50, 33, 51, 24],
['max', 100, 75, 200, 76]
];
var json = [];
data.forEach (function (datum) {
var series = datum[0];
for (var i = 1; i < datum.length; i++) {
var jdatum = {"x-axis": i-1};
jdatum[series] = datum[i];
json.push (jdatum);
}
});
var chart = c3.generate({
data: {
x: "x-axis",
json:json,
keys: {
x: "x-axis",
value: ["max", "avg", "min"]
},
groups: [
['max', 'avg', 'min']
],
type: 'bar',
},
bar: {
width: {
ratio: 0.95
}
},
axis: {
x: {
padding: {
left: 0.5,
right: 0.5,
}
}
},
tooltip: {
grouped: true,
contents: function (d, defaultTitleFormat, defaultValueFormat, color) {
var data = this.api.data.shown().map (function(series) {
var matchArr = series.values.filter (function (datum) {
return datum.value != undefined && datum.x === d[0].x;
});
matchArr[0].name = series.id;
return matchArr[0];
});
return this.getTooltipContent(data, defaultTitleFormat, defaultValueFormat, color);
}
}
});
This time, hiding a series doesn't affect the other series. There's still some tooltip jiggery-pokery needed though otherwise we only get one of the values reported in the tooltip when hovering over one of the 'stacks'. It looks like the data includes a lot of empty value points, which leads me to think this is a bug of some sort I'm taking advantage of here..
So bearing in mind this might get fixed at some point in the future (or maybe left in, if someone points out it's useful for doing this sort of plot) - then this seems to do what you want
I'm not aware of anything built-in that will make the bars merge into one another as you describe, but could a side-by-side view help? If so, remove the grouping.
See https://jsfiddle.net/gguej6n0/1/
var chart = c3.generate({
bindTo: '#chart',
data: {
columns: [
['min', 10, 25, 15],
['avg', 50, 33, 51],
['max', 100, 75, 200]
],
type: 'bar'
},
bar: {
width: {
ratio: 0.3
}
}
});
I don't think c3 has an 'overplot' bars option, but you could massage your data...
Basically process the data beforehand so max is actually max - avg, and avg is actually avg - min
Then the tooltip routine restores the correct totals for showing to a user
Just be careful if you pass the data onto anything else and remember the data has been changed and restore it (or keep a copy)
https://jsfiddle.net/gguej6n0/5/
var data = [
['min', 10, 25, 15],
['avg', 50, 33, 51],
['max', 100, 75, 200]
];
for (var n = data.length-1; n > 0; n--) {
for (var m = 1; m < data[n].length; m++) {
data[n][m] -= data[n-1][m];
}
}
var chart = c3.generate({
bindTo: '#chart',
data: {
columns: data,
type: 'bar',
groups: [
['min', 'avg', 'max']
]
},
tooltip: {
contents: function (d, defaultTitleFormat, defaultValueFormat, color) {
var dc = d.map (function (dd) {
return {value: dd.value, x: dd.x, id: dd.id, name: dd.name, index: dd.index};
})
for (var n= 1; n < dc.length; n++) {
dc[n].value += dc[n-1].value;
}
return this.getTooltipContent(dc, defaultTitleFormat, defaultValueFormat, color);
}
}
});

Can I assign a different radius for each pie slice using Highcharts?

I am using Highcharts and it is working just amazing, i am stuck at a place where i want to plot a pie chart in which every pie slice (in a single pie chart) has a different radius.
Below is the image attached of the expexted pie chart.
You can skip making it a donout or designing it this specific. I just want to know how each pie slice can have different radius.
Each series in a pie chart can have their own size. So, I stacked a bunch of pie series calculating their begin and end angles. You'll have to do a little clean up to get the tooltips displaying the value instead of 100, but I think it's a workable solution.
Note: The following code makes a bad assumption that the data points add to 100. void fixes that assumption in his fiddle http://jsfiddle.net/58zfb8gy/1.
http://jsfiddle.net/58zfb8gy/
$(function() {
var data = [{
name: 'Thane',
y: 25,
color: 'red'
}, {
name: 'Nagpur',
y: 15,
color: 'blue'
}, {
name: 'Pune',
y: 30,
color: 'purple'
}, {
name: 'Mumbai',
y: 30,
color: 'green'
}];
var start = -90;
var series = [];
for (var i = 0; i < data.length; i++) {
var end = start + 360 * data[i].y / 100;
data[i].y = 100;
series.push({
type: 'pie',
size: 100 + 50 * i,
innerSize: 50,
startAngle: start,
endAngle: end,
data: [data[i]]
});
start = end;
};
$('#container').highcharts({
series: series
});
});
Another way I toyed with, that I didn't like as much, was having each series have invisible points:
series = [{
type: 'pie',
size: 100,
innerSize: 50,
data: [{y:25, color: 'red'}, {y:75, color:'rgba(0,0,0,0)'}]
},{
type: 'pie',
size: 150,
innerSize: 50,
data: [{y:25, color: 'rgba(0,0,0,0)'},{y:15, color: 'blue'}, {y:60, color:'rgba(0,0,0,0)'}]
}, ... ];
The variablepie series type, introduced in Highcharts 6.0.0, handles this with less code. In this series type you can specify a z-parameter for each data point to alter its z-size.
For example (JSFiddle, documentation):
Highcharts.chart('container', {
chart: {
type: 'variablepie'
},
title: {
text: 'Variable pie'
},
series: [{
minPointSize: 10,
innerSize: '20%',
zMin: 0,
name: 'countries',
data: [{
name: 'Pune',
y: 35,
z: 25
}, {
name: 'Mumbai',
y: 30,
z: 20
}, {
name: 'Nagpur',
y: 15,
z: 15
} , {
name: 'Thane',
y: 25,
z: 10
}]
}]
});
This requires including:
<script src="https://code.highcharts.com/modules/variable-pie.js"></script>

Categories