I am using working with the code from How to create a row chart from multiple columns.
However I need to get the averages of the data. I normally do this via value accessor but am unsure of how to do this with this functionality.
function regroup(dim, cols) {
var _groupAll = dim.groupAll().reduce(
function(p, v) { // add
cols.forEach(function(c) {
p[c] += v[c];
});
return p;
},
function(p, v) { // remove
cols.forEach(function(c) {
p[c] -= v[c];
});
return p;
},
function() { // init
var p = {};
cols.forEach(function(c) {
p[c] = 0;
});
return p;
});
return {
all: function() {
// or _.pairs, anything to turn the object into an array
return d3.map(_groupAll.value()).entries();
}
};
}
I just need to be able to get the current sum of each column and divide it by the count of the rows based on the current filter state.
The code is similar to the one in this fiddle multi column fiddle
It seems that the easiest way to get the count of records is to create another crossfilter.groupAll() with the default reduction:
var _recordCount = dim.groupAll();
Then you can divide each sum by the current count when rotating the results:
all: function() {
var count = _recordCount.value();
// or _.pairs, anything to turn the object into an array
return d3.map(_groupAll.value())
.entries().map(function(kv) {
return {key: kv.key, value: kv.value / count};
});
}
Here's the updated fiddle: http://jsfiddle.net/37uET/32/
Related
I'd like to create a stacked bar chart using DC.JS.
I've tried to utilize the documentation from DC.JS (graph,source code) to no avail - Below is my attempt (here is my attempt in jsfiddle) and my most recent attempt in CodePen.
I'd like the 'Name' as the X axis and 'Type' as the stacks.
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
<script src="https://rawgithub.com/NickQiZhu/dc.js/master/web/js/crossfilter.js"></script>
<script src="https://cdnjs.site44.com/dc2.js"></script>
<div id="chart"></div>
Javascript
var data = [ {"Name":"Abby","Type":"Apple"}, {"Name":"Abby","Type":"Banana"}, {"Name":"Bob","Type":"Apple"} ]
data.forEach(function(x) {
x.Speed = +x.Type;
});
var ndx = crossfilter(data)
var xdim = ndx.dimension(function (d) {return d.Name;});
function root_function(dim,stack_name) {
return dim.group().reduce(
function(p, v) {
p[v[stack_name]] = (p[v[stack_name]] || 0) + v.Speed;
return p;},
function(p, v) {
p[v[stack_name]] = (p[v[stack_name]] || 0) - v.Speed;
return p;},
function() {
return {};
});}
var ydim = root_function(xdim,'Type')
function sel_stack(i) {
return function(d) {
return d.value[i];
};}
var chart = dc.barChart("#chart");
chart
.x(d3.scale.ordinal().domain(xdim))
.dimension(xdim)
.group(ydim, "1", sel_stack('1'))
.xUnits(dc.units.ordinal);
for(var i = 2; i<6; ++i)
chart.stack(ydim, ''+i, sel_stack(i));
chart.render();
I've been fiddling with this for some time and I have some additional findings:
When I replace the data array with the following it works
data = [ {"Name":"Abby","Type":"1"}, {"Name":"Abby","Type":"2"}, {"Name":"Bob","Type":"1"} ]
But it only works when I swapped out dc 1.7.5 (https://cdnjs.cloudflare.com/ajax/libs/dc/1.7.5/dc.min.js) with dc 2.1.0-dev (https://github.com/dc-js/dc.js/blob/develop/web/js/dc.js)
However when I replace the data array with the following it doesn't work:
data = [ {"Name":"Abby","Type":"3"}, {"Name":"Abby","Type":"4"}, {"Name":"Bob","Type":"2"} ]
I believe the root issue lies in the root_function.
v.Speed is always NaN in your current example. Because +x.Type attempts to convert a string like "Apple" into a number and fails. If you just want to count, then add or subtract 1 in your reducer, rather than v.Speed. Then you need to update your sel_stack code and chart code to handle this change, of course.
Here's a working example for the 2 types in your data. You'll have to update it to handle arbitrary numbers of types, probably by building an array of all your types up front and then looping through it to add stacks to the chart: http://codepen.io/anon/pen/GjjyOv?editors=1010
var data = [ {"Name":"Abby","Type":"Apple"}, {"Name":"Abby","Type":"Banana"}, {"Name":"Bob","Type":"Apple"} ]
var ndx = crossfilter(data)
var xdim = ndx.dimension(function (d) {return d.Name;});
In the reducer, just add and subtract 1 to count:
var ydim = xdim.group().reduce(
function(p, v) {
p[v.Type] = (p[v.Type] || 0) + 1;
return p;},
function(p, v) {
p[v.Type] = (p[v.Type] || 0) - 1;
return p;},
function() {
return {};
});
sel_stack no longer takes a number, but a key:
function sel_stack(valueKey) {
return function(d) {
return d.value[valueKey];
};}
var chart = dc.barChart("#chart");
Here we hard-code the stack key, for the purpose of the example:
chart
.x(d3.scale.ordinal().domain(xdim))
.dimension(xdim)
.group(ydim, "Apple", sel_stack('Apple'))
.xUnits(dc.units.ordinal);
Again, the other hard-coded stack key. You'll need to recreate the loop after creating some sort of data structure that holds all of your stack values.
//for(var i = 2; i<6; ++i)
chart.stack(ydim, 'Banana', sel_stack('Banana'));
chart.render();
I am trying to learn graphs well and implemented the following depth-first search in javascript. The DFS function is working ok, but the checkRoutes function is the source of my troubles. The checkRoutes function accepts two inputs and returns true if there is a possible path between two nodes/vertices, and false if not. it does this by starting at a node, checking the adjacency list, and then checking the adjacency lists of every item in the adjacency list via recursion.
My solution works for only one case - when you check two vertices once, but due to the way I'm storing the possibleVertices array globally, "possibleVertices" doesn't get cleared out each time. how could I push and store to the "possibleToVisit" array inside "checkRoute" instead of globally in this class? Would it be better to have this array stored on the constructor?
var possibleToVisit = [];
function dfs(v) {
this.marked[v] = true;
if (this.adj[v] !== undefined) {
console.log("visited vertex " + v);
}
for (var i = 0; i < this.adj[v].length; i++) {
var w = this.adj[v][i];
if (!this.marked[w]) {
possibleToVisit.push(w)
this.dfs(w);
}
}
console.log(possibleToVisit);
}
function checkRoute(v, v2) {
this.dfs(v);
if (possibleToVisit.indexOf(v2) === -1) {
return false;
}
return true;
}
g = new Graph(5);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 3);
g.addEdge(2, 4);
// g.showGraph();
// g.dfs(0);
console.log(g.checkRoute(0, 4));//true
console.log(g.checkRoute(0, 5));//false
https://jsfiddle.net/youngfreezy/t1ora6ab/3/#update
You can write a DFS "starter" function, which will reset all variables, and return something if necessary:
function Graph(v) {
this.startDfs = startDfs;
this.possibleToVisit = [];
}
// ...
function startDfs(v) {
this.possibleToVisit = []; // here, you can reset any values
this.dfs(v);
return true; // here, you can return a custom object containing 'possibleToVisit'
}
And call it only using startDfs:
function checkRoute(v, v2) {
this.startDfs(v);
if (this.possibleToVisit.indexOf(v2) === -1) {
return false;
}
return true;
}
Here is the updated JSFiddle.
Arrays in Javascript get passed as reference, so something like
function fill(a,l){
for(var i = 0;i<l;i++)
a.push(i + 10);
}
function check(idx, max){
var arr = [];
fill(arr,max);
console.log(arr[idx]); // 14
}
check(4,10)
would work and everytime check gets called arr is fresh and clean.
You can use a marked[] array (which is filled up during the dfs() call) to determine whether a particular vertex can be reached from a known vertex s.
Please take a look at the depth first search implementation in the following library:
https://github.com/chen0040/js-graph-algorithms
It provides an object oriented approach to the graph creation as well as the depth first search algorithm.
The sample code for its depth first search algorithm is given here:
var jsgraphs = require('js-graph-algorithms');
var g = new jsgraphs.Graph(6);
g.addEdge(0, 5);
g.addEdge(2, 4);
g.addEdge(2, 3);
g.addEdge(1, 2);
g.addEdge(0, 1);
g.addEdge(3, 4);
g.addEdge(3, 5);
g.addEdge(0, 2);
var starting_vertex = 0;
var dfs = new jsgraphs.DepthFirstSearch(g, starting_vertex);
for(var v=1; v < g.V; ++v) {
if(dfs.hasPathTo(v)) {
console.log(s + " is connected to " + v);
console.log("path: " + dfs.pathTo(v));
} else {
console.log('No path from ' + s + ' to ' + v);
}
}
I've just started working with Lodash, and am trying to average out any value (at the lowest level) that's an array. No matter what I try, the original JSON persists.
Do I need to build a brand new object and push everything into it? Why can't I simply manipulate it like I would an array?
function averageIt(mtcs) {
function jsonAvg(dataSet) {
function avg(elmt) {
var sum = 0;
for( var i = 0; elmt[i]; i++ ){
sum += parseInt( elmt[i], 10 );
}
return Math.round(sum/elmt.length * 100) / 100;
}
_.forEach(dataSet.json, function(day) {
_.mapValues(day, function(n) {
return _.isArray(n) ? avg(n) : n;
});
});
console.log("JSON Averaged:", dataSet.json);
return dataSet;
}
_.forIn(mtcs.dataSets, function(set) {
set = jsonAvg(set);
});
console.log("Averaged Metrics:", mtcs);
return mtcs;
}
Regards
- - - Really Confused "Programmer"
If I understand it correctly, when you use _.mapValues is where the avg function is applied. But as you are using a map function, the result you set in the return is not stored anywhere, and the dataSet.json remains the same. You could do this:
_.each(dataSet.json, function(day, index) {
dataSet.json[index] = _.mapValues(day, function(n) {
return _.isArray(n) ? avg(n) : n;
});
});
Supposing that dataSet.json is an array. If it is an object, you should not apply an each to it, but do something like Object.keys(dataSet.json) to convert its properties to an array.
I need to create the following array dynamically, for example:
var data = { point: [
{ x:5, y:8 },
{ x:8, y:10},
]};
console.log(data.point[0].x); // 5 n=0
console.log(data.point[1].y); // 10 n=1
At some point my application needs to expand the array to more than 2 items (n=0, n=1). Please let me know how to do that (i.e. n = 9 ).
You could use Array.push method to add element to an array.
var point = {x:1,y:1};
data.point.push(point);
you can use method 'push' like this code
var data = { point: [
{ x:5, y:8 },
{ x:8, y:10},
]};
console.log(data.point[0].x); // 5 n=0
console.log(data.point[1].y); // 10 n=1
data.point.push({x:4,y:3});
console.log(JSON.stringify(data.point));
You could do something like this:
var data = {'points' : []};
function addPoint(x, y) {
data.points.push({'x' : x, 'y' : y});
}
function getPoint(index) {
return data.points[index];
}
addPoint(10, 20);
addPoint(5, 3);
var p = getPoint(1);
alert(p.x + ", " + p.y); //alerts => "5, 3"
This would work for any arbitrary number of points.
Update
To clear the array
function clearPoints() {
data.points = [];
}
Update 2
These little functions will work okay if you have a simple page. If this points handling is going to end up being part of a larger system, it may be better to do something like this:
var data = {
'points' : [],
addPoint : function(x, y) {
this.points.push({
'x' : x,
'y' : y
});
},
getPoint : function(index) {
return this.points[index];
},
clearPoints : function() {
this.points = [];
},
removePoint : function(index) {
this.points.splice(index, 1);
}
};
Example usage:
alert(data.points.length); // => 0
data.addPoint(1, 2);
data.addPoint(8, 12);
data.addPoint(3, 7);
alert(data.points.length); // => 3
var p = data.getPoint(2); //p = {x:3, y:7};
data.removePoint(1);
alert(data.points.length); // => 2
data.clearPoints();
alert(data.points.length); // => 0
It may allow you to keep your point handling a little cleaner and easier to use and update.
You can either use the push() function as stated, or add additional items to the array using an index, which is preferred in the Google Style Guide. The latter method does require that you have a pointer to the last index.
Note that since assigning values to an array is faster than using
push() you should use assignment where possible.
for(var i=lastIndex; i < numOfNewPoints; i++){
data.point[i] = {x:4, y:3};
}
When using Crossfilter (https://github.com/square/crossfilter), I specify functions to use when adding and removing data from a group. It's fairly trivial to keep track of a running average (using CoffeeScript):
reduceAdd = (p, v) ->
++p.count;
p.sum += v.digit;
p
reduceRemove = (p, v) ->
--p.count;
p.sum -= v.digit;
p
reduceInitial = ->
{
count: 0
sum: 0
average: ->
return 0 if this.count == 0
return this.sum / this.count
}
Is it possible to keep track of the max and min of each group? I can't figure out a way short of keeping all elements in a huge array and doing a d3.min / d3.max. It seems that adding/removing data would be extremely inefficient.
I also looked for a way to tell Crossfilter to completely rebuild the group from scratch, rather than removing items from an existing group. If a filter is applied, the group is reset and rebuilt. Nothing obvious.
You can use dimension.top(1) and dimension.bottom(1) to retrieve the current min and max. These methods respect any filters that may be active on the crossfilter.
The best solution I came up with, was to keep track of all values in an ordered list and to add elements with a simple quicksort-style insertion function (cp. how to insert a number into a sorted array) and to remove them using indexOf.
Common functions:
function insertElement(element, array) {
array.splice(locationOfElement(element, array) + 1, 0, element);
return array;
}
function removeElement(element, array) {
var index = array.indexOf(element);
if (index >= 0) array.splice(index, 1);
return array;
}
function locationOfElement(element, array, start, end) {
start = start || 0;
end = end || array.length;
var pivot = parseInt(start + (end - start) / 2, 10);
if (array[pivot] === element) return pivot;
if (end - start <= 1)
return array[pivot] > element ? pivot - 1 : pivot;
if (array[pivot] < element) {
return locationOfElement(element, array, pivot, end);
} else {
return locationOfElement(element, array, start, pivot);
}
}
function maxElement(array) {
return (array.length > 0) ?
array[array.length - 1] : null;
}
function minElement(array) {
return (array.length > 0) ?
array[0] : null;
}
Functions to use when adding and removing data from a group to track min / max:
minMaxDimension = cf.dimension(function (d) {
return d.key;
});
var reduceAdd = function(p, v) {
insertElement(v.value, p.elements);
return p;
};
var reduceRemove = function(p, v) {
removeElement(v.value, p.elements);
return p;
};
var reduceInitial = function() {
return {
elements: [],
max: function() { return maxElement(elements); },
min: function() { return minElement(elements); }
}
}
minMaxGroup = minMaxDimension
.group()
.reduce(reduceAdd, reduceRemove, reduceInitial)
.orderNatural()
.top(Infinity);
After playing around with this for a bit, you can rebuild the group by just calling the group method again.