Angularjs, nvd3, and real time line chart - javascript

I have to find a way to workaround this bug https://github.com/novus/nvd3/issues/335
What I want to do is to find a way to do the transformation after I put a new data inside the array of the samples that are shown in the chart. My code is this:
var windowDimension = 60; //seconds since we update every second
$scope.summary = {};
$scope.ddd = [
{
"key": "Series 1",
"values": []
}
];
var summaryResource = summaryService.getSummaryResource();
summaryResource.get(function(data) {
$timeout(function() {
$scope.summary = data;
}, 500);
});
/**/
$scope.onTimeout = function(){
summaryResource.get(function(data) {
$timeout(function() {
$scope.summary = data;
}, 500);
});
mytimeout = $timeout($scope.onTimeout,1000);
$scope.ddd[0].values.push([$scope.summary.timeAsLong, $scope.summary.totalThroughput]);
if($scope.ddd[0].values.length > windowDimension)
$scope.ddd[0].values.shift();
};
var mytimeout = $timeout($scope.onTimeout,1000);
This code is inside the controller of the page and the page has a "nvd3-line-chart" directive that accept "ddd" as the source of the data.
Everything works, as in the bug is explained but I want to avoid the y transformation and do the call to transform the graph between the values.push and the values.shift calls.
The transform call is this
path.attr("d", line).attr("transform", null).transition()
.ease("linear").attr("transform", "translate(" + x(-1) + ")");
but I don't know how to call it inside the controller

Related

How to scaleX an object from the side, instead of centered in Animate CC HTML5?

I managed to create a progress bar where I used queue.on("progress", handleProgress); to change the scaleX of a movieclip I named as bar_mc.
var queue = new createjs.LoadQueue();
queue.on("complete", handleComplete);
queue.on("progress", handleProgress);
queue.loadFile({
id: "img",
src: "images/image.jpg"
});
function handleComplete(evt) {
console.log("Done!");
createjs.Ticker.removeAllEventListeners();
};
function handleProgress(event) {
console.log("Event Loading: " + (queue.progress.toFixed(2) * 100) + "%");
};
createjs.Ticker.addEventListener("tick", handleTick.bind(this));
function handleTick(event) {
console.log("ticking");
this.bar_mc.scaleX = queue.progress;
};
It works, but it starts at the center like in a):
How do I make it start from the side like in b)?
Also, this gave me an error when I tried it, but is there a way to put the this.bar_mc.scaleX into function handleProgress(event) instead of using a separate ticker function to animate bar_mc?
Update: I'm sure there is a way to do it using code, but I don't know how to do that. All I had to do was change the transformation point in Animate:
Now bar_mc scales from the side!
Update 2: Used Muhammed Maruf's insight about "fileprogress" and changed the code. Now it looks cleaner without having to use a separate ticker to make bar_mc scale.
Adding var root before the queue.on codes made me able to add root.bar_mc.scaleX into function handleProgress(event) without needing to change "progress":
var root = this;
root.bar_mc.scaleX = 0;
var queue = new createjs.LoadQueue();
queue.on("complete", handleComplete);
queue.on("progress", handleProgress);
queue.loadFile({
id: "img",
src: "images/image.jpg"
});
function handleComplete(evt) {
console.log("Done!");
// do other stuff
};
function handleProgress(event) {
console.log("Event Loading: " + (queue.progress.toFixed(2) * 100) + "%");
root.bar_mc.scaleX = queue.progress;
};
This is how I updated the code.
Note that I used "fileload" instead of "complete" and "fileprogress" instead of "progress".
var root = this;
root.bar_mc.scaleX = 0;
var source = "https://loremflickr.com/cache/resized/65535_49259263438_7e86e005b3_h_1280_960_nofilter.jpg?" + Math.random();
var queue = new createjs.LoadQueue();
queue.on("fileload", handleComplete);
queue.on("fileprogress", handleProgress);
queue.loadFile({
id: "img",
src: source
});
function handleComplete(evt) {
console.log("Done!");
console.log(queue.progress);
root.bar_mc.scaleX = queue.progress;
};
function handleProgress(event) {
console.log("Event Loading: " + (queue.progress.toFixed(2) * 100) + "%, " + queue.progress);
root.bar_mc.scaleX = queue.progress;
};

ReactJS: Uncaught RangeError after setState

I'm trying to create a simple drawing application using ReactJS and Flux. However, I came across a bug [Uncaught RangeError: Maximum call stack size exceeded] (it's more specifically a ReactJS problem) I've been trying figure out for a while now. It occurs when I'm trying to do setState() in a component. Below is part of my code to better illustrate my problem.
DrawingCanvas.react.js
var React = require('react');
var MouseInteractionsMixin = require('../utils/MouseInteractionsMixin');
var StrokeActionCreators = require('../actions/StrokeActionCreators');
var StrokeStore = require('../stores/StrokeStore');
function getStateFromStores() {
return {
currentStroke: StrokeStore.getCurrentStroke()
};
}
var DrawingCanvas = React.createClass({
mixins: [MouseInteractionsMixin],
styles: {
canvas: {
border: '1px solid black'
}
},
getInitialState: function() {
return getStateFromStores();
},
componentWillMount: function() {
StrokeStore.addChangeListener(this._update);
},
componentDidMount: function() {
this._onChange();
},
componentDidUpdate: function() {
this._onChange();
},
_onChange: function() {
if(this.state.dragging) {
StrokeActionCreators.beginStroke(this.state.mouse.point);
} else {
if(this.state.mouse.event == 'mouseup') {
StrokeActionCreators.endStroke(this.state.currentStroke);
}
}
},
_update: function() {
this.setState(getStateFromStores()); // where the problem occurs
context = this.getDOMNode().getContext('2d');
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
this._draw(context);
},
_draw: function(context) {
context.strokeStyle = '#000000';
context.lineJoin = 'round';
context.lineWidth = 5;
// Draw current stroke (incompleted)
for(var index = 1; index < this.state.currentStroke.points.length; index++) {
context.beginPath();
context.moveTo(this.state.currentStroke.points[index - 1].x, this.state.currentStroke.points[index - 1].y);
context.lineTo(this.state.currentStroke.points[index].x, this.state.currentStroke.points[index].y);
context.closePath();
context.stroke();
}
/*// Draw the other completed strokes
for(var strokeIndex = 0; strokeIndex < this.state.strokes.length; strokeIndex++) {
var stroke = this.state.strokes[strokeIndex];
for(var currentPointIndex = 1; currentPointIndex < stroke.points.length; currentPointIndex++) {
var previousPointIndex = currentPointIndex - 1;
context.beginPath();
context.moveTo(stroke.points[previousPointIndex].x, stroke.points[previousPointIndex].y);
context.lineTo(stroke.points[currentPointIndex].x, stroke.points[currentPointIndex].y);
context.closePath();
context.stroke();
}
}*/
},
render: function() {
return (
<canvas style={this.styles.canvas} width={this.props.width} height={this.props.height} />
);
}
});
module.exports = DrawingCanvas;
Basically, what I'm trying to do is trigger an action when the mouse is clicked and starts moving on the canvas, which sends the mouse position data to the StrokeStore. The StrokeStore stores all the mouse position data, so that eventually the DrawingCanvas component requests for the data (hence the getStateFromStores() function) and draws on the canvas, but when I try to set the state in the _update() function and try drawing on the canvas, there would be a large amount of mouse position data coming in (even after moving the mouse a tiny bit then stopping it) and an error "Uncaught RangeError: Maximum call stack size exceeded" would come up.
I've tried not storing the mouse position data as a state of the component, but rather separate variables outside the component which I use inside the component, and it works perfectly fine, so I think it's a problem with the setState().
I'd really like to get it working as states (or any other way so that I can contain the data in the stores).
EDIT:
StrokeStore.js
var AppDispatcher = require('../dispatchers/AppDispatcher');
var AppConstants = require('../constants/AppConstants');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var ActionTypes = AppConstants.ActionTypes;
var CHANGE_EVENT = 'change';
var _currentStroke = {
// Points array contains point objects with x and y variables
points: []
};
// Strokes array contains stroke objects with similar structure to currentStroke
var _strokes = [];
function _addPointToCurrentStroke(point) {
_currentStroke.points.push(point);
}
function _addStrokeToStrokes(stroke) {
_strokes.push(stroke);
}
function _clearCurrentStroke() {
_currentStroke = {
points: []
};
}
var StrokeStore = assign({}, EventEmitter.prototype, {
emitChange: function() {
this.emit(CHANGE_EVENT);
},
/**
* #param {function} callback
*/
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getCurrentStroke: function() {
return _currentStroke;
},
getStrokes: function() {
return _strokes;
}
});
AppDispatcher.register(function(payload) {
var action = payload.action;
switch(action.type) {
case ActionTypes.BEGIN_STROKE:
_addPointToCurrentStroke(action.point);
break;
case ActionTypes.END_STROKE:
_addStrokeToStrokes(action.stroke);
_clearCurrentStroke();
break;
default:
}
StrokeStore.emitChange();
});
module.exports = StrokeStore;
Any help is appreciated, thanks!

AngularJS element directive with ng-init runs before view renders

I am attempting to loop through an array and create multiple instances of a custom directive that creates different graphs based on some variables on the rootScope. Everything works fine except when I try to place those in a view and call ng-init to a method on the scope and pass it arguments.
What I am finding is that ng-init seems to run before anything (and I think ng-init is the wrong approach), which causes errors because the variables being set in the method aren't set yet when the ng-init runs in the view.
When I first load the index view, then go into this view, all is well, but when I try to load this view first or reload it, I am getting the errors. The ng-init is trying to call the chart() method before anything else runs.
On the index view, I have this chart in a modal that gets called onclick, so ng-init is not needed, therefore it works great.
I am a little stuck and what I need is advice on the "right" or better way to accomplish this.
On the detail view, I need to loop across that array to get four charts based on four different objects of data.
The data is a static file for now, as this is a prototype.
My code is essentially this:
View:
<div id="chart_list">
<div class="chart" ng-repeat="val in ['abc', 'def', 'ghi', 'jkl']" ng-init="chart('line', val, itemId, val)">
<h3>{{val | uppercase}}</h3>
<chart/>
</div>
</div>
AppController method:
// get data set up for chart consumption
$scope.chart = function(chart, kpi, socId, chartId) {
$rootScope.visualize = {};
$rootScope.visualize.chart = chart;
$rootScope.visualize.chartId = chartId;
$rootScope.visualize.val = val;
$rootScope.visualize.item = $rootScope.itemList[itemId];
$rootScope.visualize.valName = $rootScope.visualize.item[val].name;
};
DetailView controller:
app.controller('ItemDetailController', function($scope, $rootScope, $routeParams, ItemList) {
var itemId = $scope.itemId = $routeParams.itemId;
ItemList.get({}, function(res) {
$rootScope.itemList = res.data;
$scope.item = $rootScope.itemList[itemId];
});
});
Service:
app.factory('ItemList', function($resource){
return $resource("/api/item-list.json");
});
Directive:
app.directive('chart', function() {
return {
restrict: 'E',
link: function(scope, element, attrs){
if ($(window).width() <= 1200){
var width = 300;
} else {
var width = 450;
}
var visualize = scope.visualize;
var data = visualize.soc[visualize.val].data;
var numTicks = Object.keys(data).length;
element.append('<div class="chart_container"><div id="y_axis' + visualize.chartId +'" class="y_axis"></div><div id="chart' + visualize.chartId + '" class="chart"></div><div id="x_axis' + visualize.chartId + '" class="x_axis"></div></div>');
element.append('<div id="legend_container' + visualize.chartId +'" class="legend_container"><div id="smoother' + visualize.chartId +'" title="Smoothing"></div><div id="legend' + visualize.chartId +'"></div></div>');
var valSeries = [];
var valSeries2 = [];
var valSeries3 = [];
var valMap = {};
var i = 0;
Object.keys(data).forEach(function(propertyName) {
var value = data[propertyName];
var val2 = (Math.random() * (102 - 87) + 86) / 100;
var val3 = (Math.random() * (95 - 70) + 69) / 100;
valSeries.push({x: i, y: data[propertyName].amount});
valSeries2.push({x: i, y: data[propertyName].amount * val2});
valSeries3.push({x: i, y: data[propertyName].amount * val3});
valMap[i] = data[propertyName].name;
i++;
});
var graph = new Rickshaw.Graph({
element: document.querySelector('#chart' + visualize.chartId),
width: width,
height: 150,
renderer: visualize.chart,
stroke: true,
series: [
{
data: valSeries3,
color: '#F0AD4E',
name: 'Three years ago',
},
{
data: valSeries2,
color: '#5BC0DE',
name: 'Two years ago',
},
{
data: valSeries,
color: '#5CB85C',
name: 'Past year'
}
]
});
var format = function(n) {
var map = valMap;
return map[n];
}
var x_ticks = new Rickshaw.Graph.Axis.X({
graph: graph,
width: width,
orientation: 'bottom',
element: document.getElementById('x_axis' + visualize.chartId),
pixelsPerTick: width/numTicks,
tickFormat: format
});
var y_axis = new Rickshaw.Graph.Axis.Y({
graph: graph,
orientation: 'left',
tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
element: document.getElementById('y_axis' + visualize.chartId),
});
graph.render();
var hoverDetail = new Rickshaw.Graph.HoverDetail({
graph: graph,
formatter: function(series, x, y) {
var content = app.lib[visualize.dataFormat](parseInt(y)) + "<br>";
return content;
}
});
var legend = new Rickshaw.Graph.Legend( {
graph: graph,
element: document.getElementById('legend' + visualize.chartId)
} );
var shelving = new Rickshaw.Graph.Behavior.Series.Toggle( {
graph: graph,
legend: legend
} );
}
};
});
Edit:
I fixed this by removing the chart method altogether and replacing ng-init with attributes. Something like this:
<chart data-attrone="somedata" data-attrtwo="some other data" />
Then the attrs are available in the directive with attrs.attrone, etc.
Attribute names must be lower-case.
Hope this helps someone in the future.
I fixed this by removing the chart method altogether and replacing ng-init with attributes. Something like this:
<chart data-attrone="somedata" data-attrtwo="some other data" />
Then the attrs are available in the directive with attrs.attrone, etc.
Attribute names must be lower-case.
Hope this helps someone in the future.

How should multiple progress bars be handled in javascript

I have a number of progress bars each tied to a div which are updated using 'setTimeouts'.
An example of how it runs is like this :
myDiv._timer = setTimeout(function () {
func_name(data)
}, 1);
Edit: As requested a working example of my one progress bar: http://jsfiddle.net/H4SCr/
The question however is, I have multiple div's with progression bars with their own data to use to calculate progression. Which means with say 5 on the go i have 5 different timeouts running.
I'm no expert in javascript, but surely theres a way to structure this to tie to just one time out for all progress bars, or is my current approach the best method ?
Note: i don't use jQuery. I prefer to go with just vanilla javascript to learn!
Check this out:
http://jsfiddle.net/MZc8X/11/
I created an array of objects which contains the container id and its increment value.
// array to maintain progress bars
var pbArr = [{
pid: 'bar1', // parent container id
incr: 1 // increment value
}, {
pid: 'bar2',
incr: 2
}, {
pid: 'bar3',
incr: 3
}, {
pid: 'bar4',
incr: 4
}, {
pid: 'bar5',
incr: 5
}];
And, then call a function to create a progress bar...
var loopCnt = 1; // loop count to maintain width
var pb_timeout; // progress bar timeout function
// create progress bar function
var createPB = function () {
var is_all_pb_complete = true; // flag to check whether all progress bar are completed executed
for (var i = 0; i < pbArr.length; i++) {
var childDiv = document.querySelector('#' + pbArr[i].pid + ' div'); // child div
var newWidth = loopCnt * pbArr[i].incr; // new width
if (newWidth <= 100) {
is_all_pb_complete = false;
childDiv.style.width = newWidth + '%';
} else {
childDiv.style.width = '100%';
}
}
if (is_all_pb_complete) { // if true, then clear timeout
clearTimeout(pb_timeout);
return;
}
loopCnt++; // increment loop count
// recall function
pb_timeout = setTimeout(function () {
createPB();
}, 1000);
}
// call function to initiate progress bars
createPB();
Hope, it works for you.

Scope issue with a sequence of fadeIn's

I the following code I have a UL with x3 LI's. I want the LI's to fadeIn in a sequence but am loosing scope somewhere I think. The problem is that only the last item in the sequence is run. I initially thought this was to do with a loop, so I removed all of them. Any help would be great.
Thanks is Advance.
function Sequence() {
var sequence = [];
var pos = 0;
Sequence.prototype.add = function(obj) {
sequence.push(obj);
};
Sequence.prototype.start = function() {
sequence[pos].run();
};
Sequence.prototype.next = function() {
pos++;
sequence[pos].run();
};
};
function fadeIn(params) {
this.id = params.id;
this.onComplete = params.onComplete;
var self = this;
var timer;
var i = params.opacity;
fadeIn.prototype.run = function(){
timer = setInterval(function() {
params.element.style.opacity = i / 10;
i++;
if (i / 10 == 1) {
clearInterval(timer);
self.onComplete();
}
}, params.fps);
}
};
var sequence = new Sequence();
var fader = document.getElementById('fader1');
var items = fader.getElementsByTagName("li");
sequence.add(new fadeIn({
"id": "instance_0",
"element": items[0],
"opacity": 0,
"fps": 80,
"onComplete": function() {
sequence.next();
}
}));
sequence.add(new fadeIn({
"id": "instance_1",
"element": items[1],
"opacity": 0,
"fps": 80,
"onComplete": function() {
sequence.next();
}
}));
sequence.start();
Yes, this is a scope issue. The problem is in the line:
fadeIn.prototype.run = function(){
When you define a method on the prototype, you're defining the method on all instances of the fadeIn class. So each time you call the constructor, you're redefining the method with the new params in the closure.
The solution is to define the method on this (or, as you've renamed it, self), which is the new instance, rather than the class:
self.run = function(){
Working example here: http://jsfiddle.net/nrabinowitz/wrQMa/3/

Categories

Resources