I'm using Angular-nvD3. I have a simple chart that looks like this:
HTML:
<nvd3 options="options" data="data"></nvd3>
JS:
$scope.options = {
chart: {
type: 'pieChart',
height: 450,
x: function (d) { return d.key; },
y: function (d) { return d.y; },
showLabels: true,
duration: 1100,
showLegend: false
}
};
My data object is just a simple array of objects with key and y properties. On some DOM event, I update the data from the server and change the data object. When I do this, my chart is resized.
Why is this happening and how can I prevent it?
Update:
// This is the function that is called on the DOM event.
var loadAllData = function () {
var result = getData();
result.$promise.then(function (returnedAmounts) {
loadChartsData(returnedAmounts.expenses, $scope.data);
}, function (error) {
// Error.
});
}
var loadChartsData = function (group, chartsData) {
// Iterate over the group
for (var i = 0; i < group.length; i++) {
chartsData[i] = {
key: group[i].name || group[i].key,
y: group[i].amount
};
}
}
try to set your config.deepWatchData = false, that way the chart will only update when you tell it to. (by using api.refresh)
Related
With canvas.toJSON(); I cannot export the custom canvas attributes.
I have to use prototype of canvas. But I don't know how to build a prototype structure.
Existing (allowed) data:
var clipFath = this.clipPath, data = {
version: fabric.version,
objects: this._toObjects (methodName, propertiesToInclude),
};
I need it this way:
var clipFath = this.clipPath, data = {
version: fabric.version,
objects: this._toObjects (methodName, propertiesToInclude),
custom_settings_json: this.custom_settings /* <-- */
};
Original source line 7698:
In the following example, I can add custom settings to fabric objects. I need a similar structure for canvas. Source
fabric.Object.prototype.toObject = (function (toObject) {
return function (propertiesToInclude) {
propertiesToInclude = (propertiesToInclude || []).concat(
['custom_attr_1','custom_attr_2'] /* <- */
);
return toObject.apply(this, [propertiesToInclude]);
};
})(fabric.Object.prototype.toObject);
I tried this to set for canvas, as below
var canvas = new fabric.Canvas('canvas');
var custom = {
"data1": 1,
"data2": 2
}
canvas.custom_settings_json = custom;
var json_data = canvas.toJSON();
console.log(json_data);
// console log:
{
"version":"2.4.1",
"objects":[{.....}],
"custom_settings_json": {
"data1": 1,
"data2": 2
}
}
But I am getting this result without custom_settings_json in toJSON output.
// console log:
{
"version":"2.4.1",
"objects":[{.....}]
}
You can extend toJSON of canvas. As besically you need to add properties after getting the canvas data as json, just extend with your custom property after.
DEMO
fabric.Canvas.prototype.toJSON = (function(toJSON) {
return function(propertiesToInclude) {
return fabric.util.object.extend(toJSON.call(this,propertiesToInclude), {
custom_settings_json: this.custom_settings_json
});
}
})(fabric.Canvas.prototype.toJSON);
var canvas = new fabric.Canvas('c', {
"custom_settings_json": {
"data1": 1,
"data2": 2
}
});
canvas.add(new fabric.Circle({
left: 10,
top: 10,
radius: 50
}))
console.log(canvas.toJSON())
canvas{
border:2px solid;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c"></canvas>
You need to extend _toObjectMethod method from fabric.StaticCanvas.
I made a exemple jsfiddle.
Open the console and you will see the json from toJSON method with custom parameters data1,data2,data3.
//rewrite core
fabric.StaticCanvas.prototype._toObjectMethod = (function(toObjectMethod) {
return function(propertiesToInclude) {
return fabric.util.object.extend(toObjectMethod.call(this, "toDatalessObject", propertiesToInclude), {
data1: this.data1,
data2: this.data2,
data3: this.data3,
});
};
})(fabric.StaticCanvas.prototype._toObjectMethod);
//end
var myCanvas = new fabric.Canvas('my-canvas');
myCanvas.data1 = 1;
myCanvas.data2 = 2;
myCanvas.data3 = 4;
console.log(myCanvas.toJSON(['test']));
<script src='https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.min.js'></script>
<canvas id="my-canvas" width="550" height="550"> </canvas>
I have a chart, using angular-chart.js, which the x and y axis values varies depending on which item a user clicks on. When one of the items is clicked, it omits the first unit so instead of displaying 2.52 it displays .52. If you see below, the log is showing the correct information, just not displaying it.
The website I have provided a link to above provides examples which I have followed and I am a bit stumped why this is happening. It is quite lengthy my code for populting the chart with the correct data but I will try and provide only the necessary code.
Any ideas why the correct value isn't being displayed in the Y axis?
service which gathers the data for the graph
app.factory('loadPatentItemService', ['$http', '$timeout', function($http, $timeout) {
var factory = {};
var selectedData = null;
var selectedLabel = null;
var selectedItem = null;
var REST_SERVICE_URI = '../json/cost-data.json';
//function is invoked from the view and data is passed depending which item was clicked
factory.select = function(item) {
selectedItem = item;
factory.getPatent();
return [selectedItem];
}
factory.getPatent = function() {
var itemId = [];
itemId.push(selectedItem.id)
$http.get(REST_SERVICE_URI)
.then(function(response){
var items = response.data.dataset;
var graphData = [];
var graphLabel = [];
for (i = 0;i < items.length; i++) {
if(items[i].id == itemId) {
graphData.push(items[i].data);
graphLabel.push(items[i].label);
factory.graphLabel(graphLabel);
factory.graphData(graphData);
}
}
}),
function(errResponse) {
console.log('error')
}
return selectedItem;
}
factory.graphData = function(data) {
selectedData = data;
return [data];
}
factory.graphLabel = function(label) {
selectedLabel = label;
return [label];
}
factory.getData = function() {
return selectedData;
}
factory.getLabel = function() {
return selectedLabel;
}
return factory;
}])
graph controller
app.controller("lineCtrl", ['$scope', '$timeout', 'loadPatentItemService', function ($scope, $timeout, loadPatentItemService) {
var initGraph = loadPatentItemService.getPatent();
var getGraphData = loadPatentItemService.getData();
var getGraphLabel = loadPatentItemService.getLabel();
$scope.labels = getGraphLabel[0];
$scope.data = getGraphData[0];
console.log(getGraphData[0], getGraphLabel[0])
$scope.datasetOverride = [{ yAxisID: 'y-axis-1' }, { yAxisID: 'y-axis-2' }];
$scope.options = {
scales: {
yAxes: [
{
id: 'y-axis-1',
type: 'linear',
display: true,
position: 'left'
},
{
id: 'y-axis-2',
type: 'linear',
display: true,
position: 'right'
}
]
}
};
}])
For the last two days I've been reading about promises, callbacks, and generators trying to figure out how I can set a value from a async function that I can then use in a $scope object.
I can't get the promise to resolve then to the var, it just returns the promise and not the value, which is what its supposed to do from what I understand. This is where I thought maybe a generator would help maybe?
So, how do I set a var from an async function so that I can use that var in the object $scope.options_scn_cst
This is specifically for nvd3, as what I'm trying to do, is to loop through the results from the loopback function Rpt_scn_cost_v to find the max value, and then set that as the max value in the Y axis in yDomain: [min, max] for nvd3.
My code right now looks like this:
function maxYvalue2() {
return Rpt_scn_cost_v.find({filter: { where: {scenario_id: $stateParams.id}}}).$promise.then(function(response){
var maxYvalue = 0;
var currMaxYvalue = 0;
for (var i=0;i<response.length;i++) {
var currMaxYvalue = parseFloat(response[i].cur_cost) + parseFloat(response[i].tgt_cost);
if (currMaxYvalue > maxYvalue) {
maxYvalue = currMaxYvalue;
};
}
return maxYvalue;
console.log("yVal: ", maxYvalue)
});
};
var mxY = 100000 // <======== when I set it to this maxYvalue2(), it resolves in a promise
console.log('var', mxY)
$scope.options_scn_cst = {
chart: {
type: 'lineChart',
height: 450,
margin : {
top: 20,
right: 20,
bottom: 40,
left: 55
},
x: function(d){ return d.x; },
y: function(d){ return d.y; },
useInteractiveGuideline: true,
dispatch: {
stateChange: function(e){ console.log("stateChange"); },
changeState: function(e){ console.log("changeState"); },
tooltipShow: function(e){ console.log("tooltipShow"); },
tooltipHide: function(e){ console.log("tooltipHide"); }
},
xAxis: {
axisLabel: '',
tickFormat: function(d) { return d3.time.format('%b %y')(new Date(d)); }
},
yDomain: [0, mxY], // <============ I then set mxY here in the $scope object
yAxis: {
axisLabel: '$ / month',
tickFormat: function(d){
return d3.format('$,.0f')(d);
},
axisLabelDistance: -10
},
callback: function(chart){}
},
title: {
enable: true,
text: 'Scenario Costs Over Time'
},
subtitle: {
enable: false,
text: 'Put your Subtitle here.',
css: {
'text-align': 'center',
'margin': '10px 13px 0px 7px'
}
},
caption: {
enable: false,
html: 'Put your Caption Here.',
css: {
'text-align': 'justify',
'margin': '10px 13px 0px 7px'
}
}
};
and then I call the $scope object like this then:
<nvd3 data="data_scn_cst" options="options_scn_cst"></nvd3>
I tried this way as well using $q, but it returns the promise and not the value too...
function maxYvalue2() {
var deferred = $q.defer();
Rpt_scn_cost_v.find({filter: { where: {scenario_id: $stateParams.id}}}).$promise.then(function(response){
var maxYvalue = 0;
var currMaxYvalue = 0;
for (var i=0;i<response.length;i++) {
var currMaxYvalue = parseFloat(response[i].cur_cost) + parseFloat(response[i].tgt_cost);
if (currMaxYvalue > maxYvalue) {
maxYvalue = currMaxYvalue;
};
}
deferred.resolve(maxYvalue)
console.log("yVal: ", maxYvalue)
});
return deferred.promise
};
var mxY = maxYvalue2() // <======== when I set it to this maxYvalue2(), it resolves in a promise
console.log('var', mxY)
Rpt_scn_cost_v.find returns a promise, I assume. This lets you chain the promise and use the result:
function maxYvalue2() {
return Rpt_scn_cost_v.find({filter: { where: {scenario_id: $stateParams.id}}}).then(function(response){
var maxYvalue = 0;
var currMaxYvalue = 0;
for (var i=0;i<response.length;i++) {
var currMaxYvalue = parseFloat(response[i].cur_cost) + parseFloat(response[i].tgt_cost);
if (currMaxYvalue > maxYvalue) {
maxYvalue = currMaxYvalue;
}
}
return maxYvalue;
});
}
Use by:
maxYvalue2().then(function(maxYvalue) {
console.log('maxYvalue=', maxYvalue);
});
Because your code is by nature asynchronous, you will never get it into your var synchronously, whichever methods you try.
You have a few ways to work around that. One option is to wait until the promise resolves (using its then method) and then, and only then, create your $scope.options_scn_cst object.
maxYvalue2().then(function(maxYvalue) {
// Now that we know maxYvalue, define the scope variable
$scope.options_scn_cst = {...}
})
This of course requires that the <nvd3> directive that you use watches over its options binding to detect that the variable is there once it is defined.
If it is no the case and the nvd3 is your directive, you can use $scope.$watch('options', function() {...})
If it is not the case and the nvd3 directive is not yours, you could surround it in some ng-if element to only display it once options_scn_cst exists:
<nvd3 ng-if="options_scn_cst" data="data_scn_cst" options="options_scn_cst"></nvd3>
My code is not working well, i don't know how to have a handsontable in my .js file. I wrote code in html last time, which i know it's wrong in meteor, but now i changed all of the code.
I saw Meteor Handsontable example which is what i need, but i use https://github.com/awsp/handsontable-meteor instead of using the source code of handsontable, because the last version does not work. i have no error from this, but i don't see any table, i don't know why.
body.js
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { Cells } from './cells.js';
import './main.html';
if (Meteor.isClient) {
Template.handsontable.rendered = function () {
var myData = []; // Need this to create instance
var container = document.getElementById("hot");
var hot = new Handsontable(container, { // Create Handsontable instance
data: myData,
startRows: 5,
startCols: 5,
colHeaders: true,
minSpareRows: 1,
contextMenu: true,
afterChange: function (change, source) { // "change" is an array of arrays.
if (source !== "loadData") { // Don't need to run this when data is loaded
for (i = 0; i < change.length; i++) { // For each change, get the change info and update the record
var rowNum = change[i][0]; // Which row it appears on Handsontable
var row = myData[rowNum]; // Now we have the whole row of data, including _id
var key = change[i][1]; // Handsontable docs calls this "prop"
var oldVal = change[i][2];
var newVal = change[i][3];
var setModifier = {$set: {}}; // Need to build $set object
setModifier.$set[key] = newVal; // So that we can assign 'key' dynamically using bracket notation of JavaScript object
Cells.update(row._id,setModifier);
}
}
}
});
console.log(hot);
Tracker.autorun( function () { // Tracker function for reactivity
myData = Cells.find().fetch(); // Tie in our data
hot.loadData(myData);
});
};
}
FlowRouter.route('/data-view/', {
name: 'data-view',
action() {
BlazeLayout.render('handsontable');
}
});
main.html
<template name="handsontable">
<div class="handsontable" id="hot"></div>
</template>
cells.js
import { Mongo } from 'meteor/mongo';
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
class CellsCollection extends Mongo.Collection {
}
export const Cells = new CellsCollection('Cells');
Cells.schema = new SimpleSchema({
x: {
type: Number
},
y: {
type: Number
},
data: {
type: String
}
});
Cells.attachSchema(Cells.schema);
Meteor.methods({
'updateCell': function(y, x, data){
cell = Cells.findOne({x: x, y: y});
if (cell != undefined){
if (data != null){
Cells.update({_id: cell._id}, {$set: {x: x, y: y, data: data}});
}else{
Cells.remove({_id: cell._id});
}
}
},
'createCell': function(y, x, data){
if (Cells.findOne({x: x, y: y}) == undefined) {
if (data != null) {
Cells.insert({x: x, y: y, data: data});
}
} else{
Meteor.call("updateCell", y, x, data);
}
}
});
How to solve it? Thanks.
I'm trying to use high charts via angular to take advantage of double binding. I'm having an issue rendering the data, the graph works but the data is not showing up in the chart. When I check the DOM console I can get the array but for some reason its not showing up in the graph.
cpvmPartners = [];
cpvmPlannedCpm = [];
actualCpm = [];
graphData = [];
cpvm = [];
plannedPrepared = [];
getData = function(){
$.getJSON('/cpvmdata', function(data) {
for(k in data){
if(data[k]['audience'] == 'GCM'){
graphData.push([data[k]['partner'],data[k]['plannedcpm']])
actualCpm.push(Math.round((data[k]['mediacost']/data[k]['impressions']*1000)))
cpvmPlannedCpm.push(data[k]['plannedcpm'])
cpvmPartners.push(data[k]['partner'])
}
}
});
}
prepareData = function(){
for(var i = 0; i < actualCpm.length; i++) {
actualPrepared.push({name: "CPM", data: actualCpm[i]})
plannedPrepared.push({name: "Planned CPM", data: cpvmPlannedCpm[i]})
}
}
myApp = angular.module('main', ['highcharts-ng']);
myApp.controller('graphController', function ($scope) {
getData();
prepareData();
$scope.highchartsNG = {
options: {
chart: {
type: 'bar'
}
},
series: [{
data: actualCpm
}],
title: {
text: 'Hello'
},
loading: false
}
});
So the getData() function you call in the angular controller is asynchronous:
By the time you have gotten the data, you have already made your chart in $scope.highChartNg
That is why you can see your data the console but you don't actually set it to the actualCpm by the time angular is done. To fix this you need to create the chart IN your $.getJSON function like so:
var options = {
chart: {
renderTo: 'container',
type: 'spline'
},
series: [{}]
};
$.getJSON('data.json', function(data) {
options.series[0].data = data;
var chart = new Highcharts.Chart(options);
});
You can see more here: http://www.highcharts.com/docs/working-with-data/custom-preprocessing
Easier just to use
$http.get
Angular service.
Blending jQuery and Angular is troublesome for scoping.
$http.get('cpvmdata')
.then(function(response){
$scope.output = response.data;
for(k in $scope.output){
if($scope.output[k]['audience'] == 'GCM'){
$scope.planned.push($scope.output[k]['plannedcpm'])
$scope.partners.push($scope.output[k]['partner'])
$scope.cpm.push(Math.round(($scope.output[k]['mediacost']/$scope.output[k]['impressions']*1000)))
}
}
});