Updating data points that have changed on invisble HighCharts series issue - javascript

I am using HighCharts to show a graph containing a line and bar chart. By default the bar series is not visible until you click the series header. I have code that updates the bars on the fly but I have noticed that I cannot get existing information whilst the chart is invisible.
When I get data from the server I attempt to update any existing points, and add new ones if we have any. This code works great if the series is visible, but if the series is not visible then chart.series[1].data[idx] returns undefined. Clicking the series will show the chart so I know the data is there somewhere. What am I doing wrong?
if (chart.series[1].data[idx] && chart.series[1].data[idx].y != inRate)
{
chart.series[1].data[idx].update(inRate);
redraw = true;
}
else if (!chart.series[1].data[idx])
{
var time = IntervalToTime(i, doorInterval);
chart.series[1].addPoint([time, inRate], false, false);
redraw = true;
}
After some investigation I made some progress. Apparently the data array isnt filled until the series has been visible at least once, so you cannot check for the data existence there. I found a processed data queue where data is held which seems to work but doing this method causes an error when attempting to call chart.series[1].data[idx].update(inRate)
if (chart.series[1].processedXData[idx] && chart.series[1].processedYData[idx] != inRate)
{
chart.series[1].data[idx].update(inRate);
redraw = true;
}
else if (!chart.series[1].processedXData[idx])
{
var time = IntervalToTime(i, doorInterval);
chart.series[1].addPoint([time, inRate], false, false);
redraw = true;
}

After some investigation I found the issue. Apparently the data array isnt filled until the series has been visible at least once, so you cannot check for the data existence there. I found a processed data queue where data is held which seems to work.
if (chart.series[1].processedXData[idx] && chart.series[1].processedYData[idx] != inRate)
{
chart.series[1].data[idx].update(inRate);
redraw = true;
}
else if (!chart.series[1].processedXData[idx])
{
var time = IntervalToTime(i, doorInterval);
chart.series[1].addPoint([time, inRate], false, false);
redraw = true;
}

Related

amCharts 4 chart not updating with switchbutton, slider

For a radar chart, I'm trying to toggle (with a switch button) or slide (with a slider) between different sets of data to display. (I'll include the button here first, but I'm eventually try to extend that to a slider later.). 1. Initialize button and keep track of user toggle. 2. Pick which data set to use. both generateRadarData and generateRadarData2 work well on their own if I use chart.data = either one.
The below is the edited attempt:
var chartSwitchButton = chart.chartContainer.createChild(am4core.SwitchButton);
chartSwitchButton.events.on("toggle", function () {chart.data = whichData();})
function whichData() {
var dataToUse = chart.data;
if (chartSwitchButton.isActive) {dataToUse = generateRadarData();
} else {
dataToUse = generateRadarData2();}
return dataToUse};
chart.data = whichData();
I have tried commenting out the last line (since ideally it would have been updated via the event listener), but then no data displays.
Here is a more recent attempt to update the data using invalidateRawData:
chartSwitchButton.events.on("toggle", function (event) {
chart.data = whichData();
chart.invalidateRawData();
});
function whichData() {
var data = [];
if (chartSwitchButton.isActive) {
chart.data = generateRadarData();
} else {
chart.data = generateRadarData2();
}
chart.invalidateRawData(); //also tried invalidateData. tried this command in event listener as well as here.
data.push(chart.data); //attempt to replace/update raw data
//console.log(chart.data);
return chart.data; //this return line is necessary to output data but theoretically shouldn't be.
}
and have tried implementing the if-else w/in the event listener without having to use whichData as a separate function like so:
chartSwitchButton.events.on("toggle", function () {if (chartSwitchButton.isActive) {
chart.data = generateRadarData();
} else {
chart.data = generateRadarData2();
}
chart.invalidateRawData();})
I'm still unable to switch between the two sets of data with user interaction. In fact, if I don't return something for chart.data or declare what chart.data is supposed to be outside of the events.on or whichData(), then none of my data prints at all.
If anybody has suggestions on how to do this with a button (or a slider would be even better) that would be awesome.
Basically, after setting up the button, I tried to (a) keep track of the state of the button (as determined by user), (b) determine which state the button is in, and (c) pick a data set to use based off of that info. This version is edited from a previous attempt as per initial comments below. Thanks for your help.
Documentation is "toggled" not "toggle" in the events listener. The event does not recognize "toggle" but needed "toggled". https://www.amcharts.com/docs/v4/reference/switchbutton/#toggled_event

Vis.js network: how to add a node on click inside the canvas?

Manipulation methods of vis.js only include addNodeMode(), but not something like addNode(). I wonder if there's some nice way to create a node on click. May be by manipulating the data instead of network itself?
Of'course, one may go
network.on('click',function(params){
if((params.nodes.length == 0) && (params.edges.length == 0)) {
network.addNodeMode(); // doesn't add, one more click needed
//# generate click in the same place. Use params.pointer.canvas
// or params.pointer.DOM to set appropriate coordinates
}
})
but then we have also to prevent infinit loops since we generate a click event in a click handler..
Ok, here's my current implementation:
...
data = ...
nodes = new vis.DataSet(data.nodes); // make nodes manipulatable
data = { nodes:nodes, edges:edges };
...
var network = new vis.Network(container, data, options);
network.on('click',function(params){
if((params.nodes.length == 0) && (params.edges.length == 0)) {
var updatedIds = nodes.add([{
label:'new',
x:params.pointer.canvas.x,
y:params.pointer.canvas.y
}]);
network.selectNodes([updatedIds[0]]);
network.editNode();
}
})
It's not perfect since it actually creates a node and starts editing it, so if we cancel editing, the node stays. It also creates unwanted shadows of nodes. But it's already a working prototype which is enough to start with.
You can add nodes dynamically by using the update method of the vis.DataSet class. See this documentation page for details: https://visjs.github.io/vis-data/data/dataset.html

Plot real time data into line chart

I have some data collected in real time that I want to plot to line charts.
I would like to plot the data received in say last 60 calls into a line chart(pause plotting when not pulling data), each name:value pair gets 1 chart(so there would be 6 charts in this case). Of course I need to update the charts every second or every time httpGet() gets called.
I am not sure how to proceed to next step after I have got response(the data to plot) from the server...the end I am looking for is pretty much something "CPU usage history" style.
Here is my JavaScript file for collecting data from an aggregation server:
//httpGet() adopted from SO/247483
function httpGet()
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "foo.com/sensordata", false );
xmlHttp.send( null );
console.log(xmlHttp.responseText);
return xmlHttp.responseText;
}
var interval;
//this is called by a button in the html file
function start(btn)
{
if(btn.value=="Start"){
interval = setInterval(httpGet, 1000);
btn.value="Stop";
} else {
btn.value="Start";
clearInterval(interval);
}
}
So clicking the "Start" button will start making GET requests for data every 1 second. Server returns data in JSON format, this is an example output from console:
{
"Time":"2015/06/04 18:35:39",
"SensorA":{"Value1":0.34804,"Value2":-0.39175,"Value3":-0.61718},
"SensorB":{"Value1":516,"Value2":1,"Value2":2103}
}
Thank you!
I've made this demo (be sure to click on the Start Feed button!) that uses ZingChart. In the callback for the get request, I loop through the data and put it into the right format for ZingChart, then when it's all scrubbed, I use the modify method to set the values attribute in scale-x, which takes an array of Unix time values in milliseconds, and the setseriesvalues method to update the data in the chart.
for(var i = 0; i < 6; i++){
zingchart.exec('myChart','modify',{
update:false,
data:{
"scale-x":{
"values":dataObj.date
}
}
});
zingchart.exec('myChart','setseriesvalues',{
update:false,
graphid:i,
values:[dataObj[Object.keys(dataObj)[(i + 1)]]]
});
}
zingchart.exec('myChart','update');
Since I'm calling modify and setseriesvalues a bunch of times in a row, I set the update option on both to false, which queues up the changes, with everything that has been queued being pushed to the chart when update is called.
(Yeah, I do fake the data here -- I'm alternating between two different JSON files, but you should get the idea.)

SlickGrid w/ DataView not immediately reflecting changes in underlying data

I have the following code that builds a grid using slickgrid.js.
var grid;
var gridDataview;
var gridOptions = {
enableCellNavigation: true,
enableColumnReorder: true,
forceFitColumns: false,
topPanelHeight: 25
};
function createGrid(data) {
gridDataview = new Slick.Data.DataView({ inlineFilters: true });
grid = new Slick.Grid("#grid", gridDataview, data.columns, gridOptions);
grid.setSelectionModel(new Slick.RowSelectionModel());
var pager = new Slick.Controls.Pager(gridDataview, grid, $("#pager"));
var columnpicker = new Slick.Controls.ColumnPicker(data.columns, grid, gridOptions);
grid.onSort.subscribe(function (e, args) {
sortdir = args.sortAsc ? 1 : -1;
sortcol = args.sortCol.field;
// using native sort with comparer
// preferred method but can be very slow in IE with huge datasets
gridDataview.sort(comparer, args.sortAsc);
});
// if you don't want the items that are not visible (due to being filtered out
// or being on a different page) to stay selected, pass 'false' to the second arg
gridDataview.syncGridSelection(grid, true);
$("#gridContainer").resizable();
}
I am using this with knockout-js and initially only create the grid after the user makes a selection from a listbox, at which point i fetch data from an rest service and build the grid. each subsequent user selection will not create the grid, only update the data.
self.selectedInstrument.subscribe(function (newValue) {
$.getJSON('/data/' + self.selectedCategory().id + '/' + newValue.id, function (data) {
self.cotData(data);
if (grid == null) {
debugger;
createGrid(data);
}
//gridDataview.beginUpdate();
gridDataview.setItems(data.data);
//gridDataview.endUpdate();
});
});
What's happening is:
1. when the grid is initially create, no data is shown, just the column headers. if i move re-order a column header, then the data is shown.
2. If i sort a column, the sort is not visibly reflected. if i start scrolling, then i see the sort being reflected.
3. if i add a grid.render() to the end of the subscription handler above, i do see the data immediately, but then i'm not able to vertically scroll any longer. things seem badly broken at this point.
Any thoughts on what may be happening here? This started happening after i modified the code to create a DataView rather than loading the data right into the grid immediately. I need a DataView because i want to allow sorting and later different types of aggregation and groupings.
Is this possibly related to usage of slickgrid with knockout js?
Thanks much
Not sure why yet, as i'm still feeling my way around SlickGrid, but i had to add the following two subscriptions. the first allowed the grid to display rows immediately when new data is loaded and the second solved a similar issue, but when sorting:
// wire up model events to drive the grid
gridDataview.onRowCountChanged.subscribe(function (e, args) {
grid.updateRowCount();
grid.render();
});
gridDataview.onRowsChanged.subscribe(function (e, args) {
grid.invalidateRows(args.rows);
grid.render();
});

Using window.setTimeout() and window.setInterval() in this situation

This might be a newb question but....
Recently, I have been using window.setTimeout which makes a recursive call to the parent function, which makes an ajax poll to the server.
function connectToVM(portal) {
//making the ajax call here....
if(response.responseText !== "")
{
windowInterval = window.setTimeout(function() {
connectToVM(portal)
}
, 4000);
}
}
windowInterval is my global var here.
if(!checkIfChartExists()) {
window.clearInterval(windowInterval);
}
Now, instead of making use of variables here, I know that I can simple pass the function to clearTimeout, but that also causes all the other intervals to be stopped :(
The reason why I am doing this is the server does a timeout, only when there is a response.
My scenario is, I have a chart which updates every timeout interval.
AFAIK, when we set the interval, there is a specific value set to the variable(if set to a variable). So when I print my variable(every time when the timeout function is called), I get some int values which are unique.
I have many tabs, and many tabs can have same chart.. which just makes use of the same interval which is triggered earlier.
Now I have just 1 chart.. But I have many charts to show which are of the same type. Say gauge Chart.
I also have to clear the timeout whenever there is no chart present in the current selected tab - which I am doing.
So I am planning to make just 1 function which just makes the call to the server by passing in the required params to this function.
But in order to poll, I am using window.setTimeout thing I mentioned above.
This works for 1 chart.
Now, I try to add 1 more chart, with different set of params to poll the server, I will need to make use of some different setTimeout function, which has a id different than that of the earlier triggered timeout.
I also have to consider that if the 1st chart is already present, the timeout is already triggered and have to keep it running.
So, now I have to trigger the second timeout.
But there is no second timeout here.
I was wondering if there is any alternate approach to this, as I can't really predict how many chart's there will be on runtime.
Question 1 : Can we flood our browser with many timeout's?
Question 2 : How to get the id of that particular timeout, so that I can clearTimeout(id) on it?
Question 3 : Since we can't assign / make variables on the fly, how to set / make such a data structure which can hold such a pointer to the particular chart's index / id.. so that we can easily get hold of it and clear it.
Question 4 : Is this the only way we can poll the server(via AJAX) if we have to poll continually?
Lastly, I recognize this is a very complex issue I have posted in here. But I am sure I will find some useful information about the approach from the forums.
I don't have much experience doing all these stuffs in JS, but any help is appreciated!
Update
Sorry I have to post my code in here.. But I am using Extjs to get my chart portlets. My code for the function connectToVM is this :
function connectToVM(portalId, host, port, user, passwd, db) {
try{
if(Ext.getCmp(portalId))
{
var gaugeChartForTitle = Ext.getCmp(portalId);
if(typeof portalOriginalTitle === 'undefined')
portalOriginalTitle = gaugeChartForTitle.title;
var gaugeChartDiv = document.getElementById(portalId);
Ext.Ajax.request({
url: "/connectToVM?" +
Ext.urlEncode({
host: host,
port: port,
user: user,
passwd: passwd,
db: db
}),
method: 'GET',
success: function (response, options) {
if(response.responseText !== "")
{
gaugeChartDiv.style.background = "";
gaugeChartForTitle.setTitle(portalOriginalTitle);
console.log("Virtual Machine at "+ host +" : BUSY % : "+response.responseText);
virtualMachineStore.loadData(generateVirtualMachineData(response.responseText)); //Setting the data1 value of the store and loading it for display!
windowInterval = window.setTimeout(function() {
connectToVM(portalId, host, port, user, passwd, db)
}
, 4000);
}
else
{
windowInterval = window.setTimeout(function() {
connectToVM(portalId, host, port, user, passwd, db)
}
, 10000); //Retry every 10 seconds to check if the connection is established!
gaugeChartDiv.style.background = "red";
gaugeChartForTitle.setTitle(portalOriginalTitle +" - Connection Failure. Reconnecting!");
}
},
failure: function ( result, request) {
}
});
}
}
catch(err) {
}
}
Now, I trigger my function using this :
function setWindowIntervalForVM(portalId) {
//console.log("isIntervalActivated inside setWindowIntervalForVM() : "+isIntervalActivated);
if(!isIntervalActivated) {
connectToVM(portalId, host, port, user, pwd, db);
}
}
function checkIfWindowIntervalIsActivated(portal) {
if(!isIntervalActivated) {
setWindowIntervalForVM(portal.id);
isIntervalActivated = true;
} else {
window.clearInterval(windowInterval);
windowInterval = null;
isIntervalActivated = false;
}
}
So checkIfWindowIntervalIsActivated() is my parent function call which I call in these scenarios :
1) Whenever the Gauge Chart is newly created.. I Trigger this call and have a boolean isIntervalActivated which if it is false, triggers the server poll.
2) So now if I have the chart already in tab 1(since the user selected it), I now change to tab 2 which does not have it. So I simply set isIntervalActivated to true which stops the poll. This is handled for 1 chart. Now the question here is, if I want to make this function re-usable, say I want to drop one more chart of same type but with different server parameters to poll, how to make use of the same windowInterval variable which has my 1st chart's triggered timeout value. P.S: The value changes for every ajax request it makes. So there'z no 1 single value :(
3) I stop the poll whenever there is no chart of same type present.. in other tab. which makes perfect sense. Now, I am caching all my portlets whenever user drops in a new portlet / on the page load, pulling all the user configured portlets. In such a case, I have to trigger all of the charts' ajax calls.. each polling to its configured destination. Now, I do not know how many charts there will be, as in my function name, I am polling to VM's. So if the user consumes VM1, it switches to VM2 and so on.
So it's absolutely impossible to just create same function for many such similar charts.
So just wanted to check if I can re-use the same timeOut thing, or take a totally different approach to this problem :( :(
I hope it's a bit clear now, if not I can explain my situation more.
Please ask me more questions if required :)
Thanks again!
If I understood correctly and you're trying to support multiple charts updating concurrently, I'd switch from keeping the chart data inside the connectToVM() closure to an explicit array of chart objects and use a single interval to update all charts.
Something like the following (treat it as pseudo-code):
var charts = [
// an array of chart objects, see addChart()
];
function addChart() {
// when you need to add or remove a chart, update the charts object, like this:
charts.push({
update: updateChart,
nextUpdateTime: null, // or Date.now() if you don't care about old browsers.
chartData: {host: ..., port: ..., user: ..., passwd: ..., db: ...,
originalTitle: ..., portalId: ...},
});
restartUpdates();
}
var activeInterval = null;
function restartUpdates() {
if (activeInterval) {
clearInterval(activeInterval);
}
activeInterval = setInterval(updateCharts, 5000);
}
// updates all active charts
function updateCharts() {
var now = new Date().getTime();
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
if (chart.nextUpdateTime !== null && chart.nextUpdateTime < now) {
chart.nextUpdateTime = null; // chart.update() will re-set this
try {
chart.update(chart);
} catch(e) {
// handle the error
}
}
}
// update a single chart.
// #param |chart| is an item from the |charts| array.
function updateChart(chart) {
// ...same as your connectToVM() using properties from chart.chartData...
Ext.Ajax.request(
// ...
success: function (response, options) {
// ...same as before...
// ...but instead of re-setting the timeout:
// windowInterval = window.setTimeout(function() {
// connectToVM(portalId, host, port, user, passwd, db)
// }
// , 4000);
// ...update the chart's nextUpdateTime:
chart.nextUpdateTime = (new Date().getTime()) + 4000;
}
);
}
initial answer below
Thanks for the detailed question! It feels you're missing something very obvious wrt questions #2/3, but it's hard to tell what specifically without seeing more of your code. Can you post a more complete, yes simple example demonstrating the problem you're trying to solve? Perhaps the function handling changing the active tab in pseudocode would help, like this:
function selectTab(tabID) {
// ...activate tab #tabID in the GUI...
if (tabID == 1) {
// there's chart #1 on tab #1, need to stop any active timeouts and start a new one
connectToVM("chart #1");
} else if (tabID == 2) {
// no charts on tab #2.. need to stop any active timeouts
} else if (tabID == 3) {
// ...
}
}
One thing I don't understand is whether there's always a single chart, that needs updating, at any point of time?
Also, do you know the concepts mentioned in A re-introduction to JavaScript, specifically objects?
As for the questions:
1: yes, too many timeouts should be avoided (thousands a second will probably make the CPU hot and the browser sluggish), although I'd be more worried about the server, which has to handle the requests from multiple clients.
2/3: see above.
4: The Comet page lists a lot of alternatives to basic AJAX polling (server-sent events, long-polling, websockets), but I'd worry about this later.
Yes
var x = window.setTimeout(...); window.clearTimeout(x);
Store it as a data attribute on the active tab, a property on your object, or as a global variable. Many different ways. Example code would have made it easier to answer this.
Based on your comments:
var windowInterval;
function connectToVM(portal) {
if(windowInterval)window.clearTimeout(windowInterval);
windowInterval = window.setTimeout(function() { ... }, 4000);
}

Categories