Static response WORKS! While Asynchronous doesn't work - javascript

I'm creating an angular directive for realtime chart displaying here is the code which returns everything including link:function() { } inside directive.
Here is the code for static directive which works perfectly
angular.module('app').directive("flotChartRealtime", [
function() {
return {
restrict: "AE",
link: function(scope, ele) {
var realTimedata,
realTimedata2,
totalPoints,
getSeriesObj,
getRandomData,
getRandomData2,
updateInterval,
plot,
update;
return realTimedata = [],
realTimedata2 = [],
totalPoints = 100,
getSeriesObj = function() {
return [
{
data: getRandomData(),
lines: {
show: true,
lineWidth: 1,
fill: true,
fillColor: {
colors: [
{
opacity: 0
}, {
opacity: 1
}
]
},
steps: false
},
shadowSize: 0
}, {
data: getRandomData2(),
lines: {
lineWidth: 0,
fill: true,
fillColor: {
colors: [
{
opacity: .5
}, {
opacity: 1
}
]
},
steps: false
},
shadowSize: 0
}
];
},
getRandomData = function() {
if (realTimedata.length > 0)
realTimedata = realTimedata.slice(1);
// Do a random walk
//console.log(realTimedata);
while (realTimedata.length < totalPoints) {
var prev = realTimedata.length > 0 ? realTimedata[realTimedata.length - 1] : 50,
y = prev + Math.random() * 10 - 5;
if (y < 0) {
y = 0;
} else if (y > 100) {
y = 100;
}
realTimedata.push(y);
}
// Zip the generated y values with the x values
var res = [];
for (var i = 0; i < realTimedata.length; ++i) {
res.push([i, realTimedata[i]]);
}
return res;
},
getRandomData2 = function() {
if (realTimedata2.length > 0)
realTimedata2 = realTimedata2.slice(1);
// Do a random walk
while (realTimedata2.length < totalPoints) {
var prev = realTimedata2.length > 0 ? realTimedata[realTimedata2.length] : 50,
y = prev - 25;
if (y < 0) {
y = 0;
} else if (y > 100) {
y = 100;
}
realTimedata2.push(y);
}
var res = [];
for (var i = 0; i < realTimedata2.length; ++i) {
res.push([i, realTimedata2[i]]);
}
return res;
},
// Set up the control widget
updateInterval = 500,
plot = $.plot(ele[0], getSeriesObj(), {
yaxis: {
color: '#f3f3f3',
min: 0,
max: 100,
tickFormatter: function(val, axis) {
return "";
}
},
xaxis: {
color: '#f3f3f3',
min: 0,
max: 100,
tickFormatter: function(val, axis) {
return "";
}
},
grid: {
hoverable: true,
clickable: false,
borderWidth: 0,
aboveData: false
},
colors: ['#eee', scope.settings.color.themeprimary],
}),
update = function() {
plot.setData(getSeriesObj()); // getting .data filled here perfectly
plot.draw();
setTimeout(update, updateInterval);
},
update();
}
};
}
]);
My code with HTTP request which doesn't work
getSeriesObj = function () {
return [{
data: getRandomData(function(res) {
console.log(res) // getting array result here from http call but not returning to data:
return res;
}),
lines: {
show: true,
lineWidth: 1,
fill: true,
fillColor: {
colors: [{
opacity: 0
}, {
opacity: 1
}]
},
steps: false
},
shadowSize: 0
}, {
data: getRandomData2(function (res) {
return res;
}),
lines: {
lineWidth: 0,
fill: true,
fillColor: {
colors: [{
opacity: .5
}, {
opacity: 1
}]
},
steps: false
},
shadowSize: 0
}];
},
getRandomData = function (callback) {
var authToken = window.localStorage.getItem('token');
var url = $rootScope.apiPath + 'Elasticsearch/countget?token=' + authToken;
var res = [];
$http.get(url).then(function (result) {
realTimedata = result.data;
if (realTimedata.length > 0)
//result = [10,22,33,11,32,88,77,66,21,90,92,98,99.9,88.8,76,66,56,88];
for (var i = 0; i < realTimedata.length; ++i) {
var y = realTimedata[i] + Math.random() * 10 - 5;
if (y < 0) {
y = 0;
} else if (y > 100) {
y = 100;
}
res.push([i, y]);
}
callback(res);
});
},
Problem:
When i try following code:
update = function () {
//console.log(getSeriesObj());
plot.setData(getSeriesObj()); // .data property gets undefined
plot.draw();
setTimeout(update, updateInterval);
}
function getSeriesObj() return array of object which return data property to undefined what can be the reason?
how can i resolve this?
Note: This is far different from this question.

When do this
data: getRandomData(function(res) {
return res;
});
You assign the rValue of getRandomData to data.
As stated in your post, getRandomData now has no return statement, so return undefined.
The main problem here is that you expect that plot.setData(getSeriesObj()); work synchronously
Steps
get the data to fill plot
set the data to the plot
draw it
update the values again
Now as the http request work async you cannot expect to retrieve a value from getSeriesObj(). You have to think that getSeriesObj work now async so you can only work with callback that will be fired when the resource is ready to be used
so the update method become
update = function () {
var updateTime = +new Date;
getSeriesObj(function(res){ // execute that stuff when ready
plot.setData(res);
plot.draw();
setTimeout(update, Math.max(10, updateInterval - (+new Date - updateTime)) );
});
}
and getSeriesObj
getSeriesObj = function (callback) {
getRandomData(function(res) {
getRandomData2(function(res2){
var data = [{
data: res,
lines: {
show: true,
lineWidth: 1,
fill: true,
fillColor: {
colors: [{
opacity: 0
}, {
opacity: 1
}]
},
steps: false
},
shadowSize: 0
}, {
data: res2,
lines: {
lineWidth: 0,
fill: true,
fillColor: {
colors: [{
opacity: .5
}, {
opacity: 1
}]
},
steps: false
},
shadowSize: 0
}];
callback(data); // now the ressource obj is now ready to be used
});
});
}

Related

How to make a dynamically growing data chart in Chart.JS?

I am trying to develop a Crash Game, where a multiplier (Y) increases exponentially and dynamically over time (X), causing the chart to re-render at each tick.
You can see an example of the chart game here
TL;DR: I am trying to achieve a "zoom-out" effect of the chart as my ticks increase in values (x,y).
Where my code fails is when ticks data values (x,y, respectively time and multiplier) surpass suggestedMax tick values. The only reason I am using suggestedMax is to have some labels on the chart at the beginning.
I have tried to achieve this by using both line and scatter chart type, but the final outcome is simply unacceptable from a performance point of view.
Here is my code:
const HomePlaygroundView = () => {
var chart = undefined;
const chartText = useRef(null);
let last_tick_received = 0;
const incrementChart = () => {
last_tick_received += 100;
};
const onServerTickReceived = (multiplier, msLapsed) => {
// Update chart multiplied
if (chart.data.datasets[0].data.length >= 100) {
// Halve the array to save performance (lol)
for (let i = 1; i < 100; i += 2) {
console.log("Reducing chart data");
chart.data.datasets[0].data.splice(i, 1);
}
}
chart.data.datasets[0].data.push({
x: msLapsed,
y: multiplier,
});
// This is basically my zoom out effect implementation...
if (multiplier >= 2.5) { // Increase suggestedMax only if bigger data needs to be fit
chart.options.scales.yAxes[0].ticks.suggestedMax = multiplier;
}
if (msLapsed > 9000) { // Same logic as above
chart.options.scales.xAxes[0].ticks.suggestedMax = msLapsed;
}
if (msLapsed < 10000) {
// Fit msLapsed in the pre-existing 10 seconds labels of x axis (this is a hell of a workaround)
let willInsertAtIndex = undefined;
for (let i = 0; i < chart.data.labels.length; i++) {
let current = chart.data.labels;
if (current < msLapsed) {
// Insert at i + 1? Check the next index if it's bigger than msLapsed
let nextVal = chart.data.labels[i + 1];
if (nextVal) {
if (nextVal > msLapsed) {
willInsertAtIndex = i + 1;
break;
}
} else {
willInsertAtIndex = i + 1;
break;
}
}
}
if (willInsertAtIndex) {
chart.data.labels.splice(willInsertAtIndex, 0, msLapsed);
}
} else {
chart.data.labels.push(msLapsed);
}
// Decimate data every so and so
chartText.current.innerText = `${multiplier}x`;
// Re-render canvas
chart.update();
};
useEffect(() => {
console.log("rendered chart");
var ctx = document.getElementById("myChart").getContext("2d");
ctx.height = "350px";
chart = new Chart(ctx, {
// The type of chart we want to create
type: "scatter",
// The data for our dataset
data: {
labels: [...Array(11).keys()].map((s) => s * 1000),
datasets: [
{
label: "testt",
backgroundColor: "transparent",
borderColor: "rgb(255, 99, 132)",
borderWidth: 10,
showLine: true,
borderJoinStyle: "round",
borderCapStyle: "round",
data: [
{
y: 1,
x: 0,
},
],
},
],
animation: {
duration: 0,
},
responsiveAnimationDuration: 100, // animation duration after a resize
},
// Configuration options go here
options: {
spanGaps: true,
events: [],
responsive: true,
maintainAspectRatio: false,
legend: {
display: false,
},
elements: {
point: {
radius: 0,
},
},
scales: {
xAxes: [
{
type: "linear",
ticks: {
callback: function (value, index, values) {
let s = Math.round(value / 1000);
return s.toString() + "s";
//return value;
},
autoSkipPadding: 100,
autoSkip: true,
suggestedMax: 10000,
stepSize: 100,
min: 0,
},
},
],
yAxes: [
{
ticks: {
// Include a dollar sign in the ticks
callback: function (value, index, values) {
return Math.round(value).toString() + "x"; // Display steps by 0,5
},
min: 1,
suggestedMax: 2.5,
stepSize: 0.01,
autoSkip: true,
autoSkipPadding: 150,
},
},
],
},
},
});
let lastTick = 1.0;
let dateStart = new Date().getTime();
setTimeout(() => {
chartText.current.innerText = "Go!";
setTimeout(() => {
setInterval(() => {
let timePassed = new Date().getTime() - dateStart;
//console.log(timePassed);
let calculateTick = Math.pow(
1.01,
0.00530133800509 * timePassed
).toFixed(2);
console.log(timePassed);
onServerTickReceived(calculateTick, timePassed);
}, 50);
}, 1000);
}, 2000);
});
const classes = useStyles();
return (
<div className={classes.canvasContainer}>
<span ref={chartText} className={classes.canvasText}>
Ready...?
</span>
<canvas id="myChart"></canvas>
</div>
);
};
export default HomePlaygroundView;

Chart js animating a line while changing x-axis labels

I achieved animating a plot using Jukka Kurkela example here.
Now I am having trouble customizing this plot further.
Logic of the custom plot
The plot starts animating with the x-axis labels being 0-20. When the plot reaches 20 then update the x-axis to be 20-40. Increment i or 20 until the x-axis reach its limit.
How to apply the logic above to the Example below?
// Generating data
var data = [];
var prev = 100;
for (var i=0;i<200;i++) {
prev += 5 - Math.random()*10;
data.push({x: i, y: prev});
}
var delayBetweenPoints = 100;
var started = {};
var ctx2 = document.getElementById("chart2").getContext("2d");
var chart2 = new Chart(ctx2, {
type: "line",
data: {
datasets: [
{
backgroundColor: "transparent",
borderColor: "rgb(255, 99, 132)",
borderWidth: 1,
pointRadius: 0,
data: data,
fill: true,
animation: (context) => {
var delay = 0;
var index = context.dataIndex;
if (!started[index]) {
delay = index * delayBetweenPoints;
started[index] = true;
}
var {x,y} = index > 0 ? context.chart.getDatasetMeta(0).data[index-1].getProps(['x','y'],
true) : {x: 0, y: 100};
return {
x: {
easing: "linear",
duration: delayBetweenPoints,
from: x,
delay
},
y: {
easing: "linear",
duration: delayBetweenPoints * 500,
from: y,
delay
},
skip: {
type: 'boolean',
duration: delayBetweenPoints,
from: true,
to: false,
delay: delay
}
};
}
}
]
},
options: {
scales: {
x: {
type: 'linear'
}
}
}
});
<div class="chart">
<canvas id="chart2"></canvas>
</div>
<script src="https://www.chartjs.org/dist/master/Chart.js"></script>
Solved it! Instead of incrementing 20 seconds, it is incrementing every 5 seconds ahead of time. Definitely a better experience for the user.
Got help from Rowf Abd's post.
var myData = [];
var prev = 100;
for (var i=0;i<60;i++) {
prev += 5 - Math.random()*10;
myData.push({x: i, y: prev});
}
var ctx = document.getElementById('myChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
data: [myData[0]],
pointRadius: 0,
fill: false,
borderColor: "black",
lineTension: 0
}]
},
options: {
legend: {
onClick: (e) => e.stopPropagation()
},
title:{
fontColor: 'Black'
},
layout: {
padding: {
right: 10
}
},
scales: {
xAxes: [{
type: 'linear',
ticks: {
}
}],
yAxes: [{
scaleLabel: {
// fontFamily: 'Lato',
fontSize: 19,
fontColor: "Black"
}
}]
}
}
});
var next = function() {
var data = chart.data.datasets[0].data;
var count = data.length;
var xabsmin = 20;
var xabsmax = 60;
var incVar = 5;
data[count] = data[count - 1];
chart.update({duration: 0});
data[count] = myData[count];
chart.update();
if (count < myData.length - 1) {
setTimeout(next, 500);
}
if (data[count].x < xabsmin) {
chart.config.options.scales.xAxes[0].ticks.min = xabsmin - xabsmin;
chart.config.options.scales.xAxes[0].ticks.max = xabsmin;
chart.update();
}
if(data[count].x >= xabsmin && data[count].x < (xabsmax)){
var currentT = parseFloat(data[count].x);
var modDiv = (currentT % incVar);
var tempXMax = (currentT) + (incVar - modDiv);
chart.config.options.scales.xAxes[0].ticks.max = tempXMax;
chart.config.options.scales.xAxes[0].ticks.min = tempXMax - xabsmin;
chart.update();
}
}
setTimeout(next, 500);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js"></script>
<canvas id="myChart"></canvas>

how to calculate the angle between the mid of a clicked donut element and the negative y-axis

Consider the following codesample donut chart using jquery-flot , now as i have added the 'image' class on click of the donut, i want to dynamically add the degree in the 'image' class so that the clicked item will be facing down at the bottom ( like on the -ve side of the y-axis ).`
var data = [{
label: "Pause",
data: 150
}, {
label: "No Pause",
data: 100
}, {
label: "yes Pause",
data: 80
}, {
label: "Sleeping",
data: 250
}];
var options = {
series: {
pie: {
show: true,
innerRadius: 0.5,
radius: 1,
startAngle: 1,
}
},
grid: {
hoverable: true,
clickable: true
},
legend: {
show: false
},
stroke: {
width: 4
},
tooltip: true,
tooltipOpts: {
cssClass: "flotTip",
content: "%s: %p.0%",
defaultTheme: false
}
};
$("#pie-placeholder").bind("plotclick", function(event, pos, obj) {
$("#pie-placeholder").addClass('image')
});
var plot = $.plot($("#pie-placeholder"), data, options);
`
Note:- this is done using Jquery flot
Here you can find my solution to your problem if I got you right.
$("#pie-placeholder").bind("plotclick", function(event, pos, obj) {
if (obj) {
var percentInRads = 0.02;
var currSegmentInRads = percentInRads * obj.datapoint[0]
var currSegmentOffset = currSegmentInRads / 2;
var currSegmentStart = currSegmentOffset >= 0.5 ? -0.5 + currSegmentOffset : 0.5 - currSegmentOffset;
var total = 0;
var beforeTotal = 0;
for (var idx = 0; idx < data.length; idx++) {
var segment = data[idx];
if (idx < obj.seriesIndex) {
beforeTotal += segment.data;
}
total += segment.data;
}
var beforePart = (beforeTotal / total * 100) * percentInRads;
var chartStartAngle = currSegmentStart - beforePart;
options.series.pie.startAngle = chartStartAngle;
$.plot($("#pie-placeholder"), data, options);
console.log(obj.series);
}
});

HighCharts Dynamic multiseries Column Chart showing only 1 set of series at a time

I am using highcharts on a project, I am having trouble with the creation of multiple series of data updating dynamically generating a column chart, my aim is to keep all the series at a static position and change according to the data.
Till now i have achieved this : https://jsfiddle.net/jk05qcq4/
Highcharts.chart('container', {
chart: {
type: 'column',
backgroundColor: null,
animation: Highcharts.svg, // don't animate in old IE
marginRight: 10,
events: {
load: function() {
var iter = 0;
// set up the updating of the chart each second
var series = this.series[0];
var series2 = this.series[1];
var series3 = this.series[2];
var series4 = this.series[3];
var series5 = this.series[4];
var series6 = this.series[5];
myInterval = setInterval(function() {
var len = Object.keys(BleedEnthalpy).length;
var len2 = Object.keys(BypassRatio).length,
x = new Date().getTime();
if (iter < len) {
series.addPoint([x, BleedEnthalpy[iter]], false, true);
series2.addPoint([x, BypassRatio[iter]], false, true);
series3.addPoint([x, CorrCoreSpeed[iter]], false, true);
series4.addPoint([x, CorrFanSpeed[iter]], false, true);
series5.addPoint([x, FuelFlowRatio[iter]], false, true);
series6.addPoint([x, HPCOutletTemp[iter]], true, true);
iter++;
} else {
clearInterval(myInterval);
}
}, 1000);
}
}
},
title: {
text: null
},
xAxis: {
type: 'datetime',
tickPixelInterval: 150
},
yAxis: [{
title: {
text: 'Value'
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
}, {
}],
tooltip: {
formatter: function() {
return '<b>' + this.series.name + '</b><br/>' +
Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +
Highcharts.numberFormat(this.y, 4);
}
},
legend: {
enabled: true
},
exporting: {
enabled: false
},
rangeSelector: {
enabled: false
},
navigator: {
enabled: false
},
scrollbar: {
enabled: false
},
series: [{
name: 'R data',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: BleedEnthalpy
});
}
return data;
}())
}, {
name: 'Bypass ratio',
maxPointWidth: 90,
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: BypassRatio
});
}
return data;
}())
},
{
name: 'CorrCoreSpeed',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: CorrCoreSpeed
});
}
return data;
}())
},
{
name: 'CorrFanSpeed',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: CorrFanSpeed
});
}
return data;
}())
},
{
name: 'FuelFlowRatio',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: FuelFlowRatio
});
}
return data;
}())
},
{
name: 'HPCOutletTemp',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: HPCOutletTemp
});
}
return data;
}())
}
]
});
I solved the answer by reducing the size of the for loop from -19 to 0 for all the series:
{
name: 'HPCOutletTemp',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = 0; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: HPCOutletTemp
});
}
return data;
}())
}
check the fiddle for more understanding : https://jsfiddle.net/qkdwu3p3/

How select values from multidimensional array to draw graphics?

have JSON
<c:set var="json_text">
{
"FailedCount":[{"FailedCount_MEAS_VALUE":1,"DATETIME_CURRENT":"12:01"},
{"FailedCount_MEAS_VALUE":0,"DATETIME_CURRENT":"12:02"},
{"FailedCount_MEAS_VALUE":3,"DATETIME_CURRENT":"12:03"},
{"FailedCount_MEAS_VALUE":4,"DATETIME_CURRENT":"12:04"}],
"SucceededCount":[{"SucceededCount_MEAS_VALUE":110},
{"SucceededCount_MEAS_VALUE":120},
{"SucceededCount_MEAS_VALUE":130},
{"SucceededCount_MEAS_VALUE":140}]
}
</c:set>
prepare data
function culcJson() {
var jsonObj = ${json_text};
var VALUES=[];
var n = jsonObj.FailedCount.length, m = 5;
var mas = [];
for (var i = 0; i < m; i++){
mas[i] = [];
for (var j = 0; j < n; j++){
if (i==0) {
mas[i][j] = jsonObj.FailedCount[j].FailedCount_MEAS_VALUE;
}
if (i==1)
{
mas[i][j] = jsonObj.SucceededCount[j].SucceededCount_MEAS_VALUE;
}
if (i==2)
{
mas[i][j] =jsonObj.FailedCount[j].FailedCount_MEAS_VALUE+jsonObj.SucceededCount[j].SucceededCount_MEAS_VALUE;
}
if (i==3)
{
var KPI = jsonObj.SucceededCount[j].SucceededCount_MEAS_VALUE / (jsonObj.SucceededCount[j].SucceededCount_MEAS_VALUE + jsonObj.FailedCount[j].FailedCount_MEAS_VALUE) * 100;
mas[i][j] = +KPI.toFixed(2);
}
if (i==4)
{
mas[i][j] =jsonObj.FailedCount[j].DATETIME_CURRENT;
}
VALUES.push(mas[i][j]);
}}
console.log(mas);
return VALUES;
}
trying to build chart depending KPI from DATETIME
$(function () {
var VALUES;
VALUES=culcJson();
result_newjson.innerHTML = VALUES;
$('#container').highcharts({
chart: {
zoomType: 'x'
},
title: {
text: '${title}'
},
xAxis: {
categories: ["12:01","12:02","12:03","12:04"]
},
yAxis: {
title: {
text: ''
}
},
legend: {
enabled: true
},
plotOptions: {
area: {
fillColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1},
stops: [
[0, Highcharts.getOptions().colors[0]],
[1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
]
},
marker: {
radius: 2
},
lineWidth: 1,
states: {
hover: {
lineWidth: 1
}
},
threshold: null
}
},
series: [
{
name: 'KPI',
data: [99.5,100,98.56,99.99]
}
]
});
});
How to correctly refer to VALUES array to select only the time and KPI instead of static values
categories: ["12:01","12:02","12:03","12:04"] и data: [99.5,100,98.56,99.99]
?

Categories