I'm running into an issue when trying to implement a normalized stacked bar chart using D3v4.
The problem occurs due to my data format which contains nested object arrays populated dynamically on the server side.
var data = [{x:"data1", y:[{name:"red", value:10}, {name:"green", value:20}]},
{x:"data2", y:[{name:"red", value:30}, {name:"green", value:5}]}];
Calling d3.stack() on this will not work since d3 doesn't know how to traverse into the object array y. (https://jsfiddle.net/xv1qgqjg/)
Is there any way to tell d3.stack() where to find the relevant data similar to the .data(function(d){ return d.y; }) used elsewhere?
It doesn't seem to be possible. According to the documentation regarding stack(data[, arguments…]),
Any additional arguments are arbitrary; they are simply propagated to accessors along with the this object.
Thus, you'll have to change your data, creating an array which you can pass to d3.stack(), such as this:
[{red:10,green:20},
{red:30,green:5}]
Given the data array in your question, there are several ways for creating the above-mentioned array. Here is my solution (the new array is called newData):
newData = [];
data.forEach(d => {
var tempObj = {}
d.y.forEach(e => {
tempObj[e.name] = e.value;
})
newData.push(tempObj);
});
Here is a demo:
var data = [{x:"data1", y:[{name:"red", value:10}, {name:"green", value:20}]},
{x:"data2", y:[{name:"red", value:30}, {name:"green", value:5}]}];
newData = [];
data.forEach(d => {
var tempObj = {}
d.y.forEach(e => {
tempObj[e.name] = e.value;
})
newData.push(tempObj);
});
var stack = d3.stack()
.keys(["red", "green"])
.order(d3.stackOrderNone)
.offset(d3.stackOffsetExpand);
var series = stack(newData);
console.dir(series);
<script src="https://d3js.org/d3.v4.min.js"></script>
Related
selection.node() returns only the first node. Can we get an array of all nodes from a selection?
EDIT Added some code to help us.
The attempt with each() is the only one producing the wanted
output, although quite verbose.
Calling sel[0] also returns an array with DOM nodes, but it's hacky (depends on the internal structure of the library) and includes an unwanted "parentNode" field.
// creating a selection to experiment with
var data= [1,2,3,4]
var sel = d3.select("li")
.data(data)
.enter().append("li").html(identity);
function identity(d){return d}
console.log(sel); // array[1] with array[4] with the <li>'s
// using .node()
var res1 = sel.node();
console.log(res1); // first <li> only
// using .each() to accumulate nodes in an array
var res2 = [];
function appendToRes2(){
res2.push(this);
}
sel.each(appendToRes2);
console.log(res2); // array[4] with the <li>'s (what I want)
// calling sel[0]
var res3 = sel[0];
console.log(res3); // array[4] with the <li>'s plus a "parentNode"
// #thisOneGuy's suggestion
var res4 = d3.selectAll(sel);
console.log(res4); // array[1] with array[1] with array[4] with the <li>'s
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
EDIT 2 Why do I want to do that?
To call array methods like reduce and map on the DOM nodes. D3 provides filter but to use others I first need to extract the node array from the selection.
I originally wrote this as a comment, but decided to turn it into an answer...
It looks like d3 v4 will include the functionality you want. If you don't want to wait, you can steal the implementation now and add it to the selection prototype:
d3.selection.prototype.nodes = function(){
var nodes = new Array(this.size()), i = -1;
this.each(function() { nodes[++i] = this; });
return nodes;
}
Usage example:
d3.selection.prototype.nodes = function(){
var nodes = new Array(this.size()), i = -1;
this.each(function() { nodes[++i] = this; });
return nodes;
}
var data= [1,2,3,4]
var sel = d3.select("li")
.data(data)
.enter().append("li").html(identity);
function identity(d){return d}
console.log(sel.nodes());
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Since it came from #mbostock, it's a good bet is the best implementation available.
I have the following code to handle double taps...
var onClick = function (e) {
if ((d3.event.timeStamp - last) < 500) {
return callback(e);
}
last = d3.event.timeStamp;
};
But this returns...
{
"uuid":"e2befb22-849b-4b56-91b4-8c297311491b",
"title":"JRG-024",
"weight":3,
"x":-4120.238083042915,
"y":2759.9261895569307,
"px":-4102.444457966419,
"py":2753.0045790866943
}
Notice the x,y,px,py, etc I want to avoid these and get the clean data that was originally bound. I also tried return callback(d3.select(this).data()[0]) but this still returns the location data. Is there a way to remove this data from the response, short of filtering?
Here's an example based on my comment
d3.select('body')
.selectAll('.data')
.data([10,20,30])
.enter()
.append('div')
.attr('class', 'data');
var node = d3.select('.data').node();
console.log(node.__data__);
The console output is 10
I ended up just putting everything in a property and passing it like that. Something similar to this...
var d3Data = data.map(function(item){
var result = {
...
originalData : item
}
return result
})
Not accepting because it is hacky hacky hacky.
I'm new to crossfilter.js. Whenever I used the range selector I get the strange blocky highlighted area on my bar chart outside of my filter range. I can't figure out what I'm doing wrong.
How I can prevent this blocky section from highlighting outside my range?
d3.csv("mydata.csv", function(data) {
data.forEach(function(d) {
d.conf = d3.round(+d.Conf,1);
console.log(d.conf);
});
var facts = crossfilter(data); // Put data into crossfilter
var confDim = facts.dimension(function (d) { return d.conf;}); // conf # filter
var confTotal = confDim.group().reduceCount(function (d) { return d.conf;});
var confChart = dc.barChart("#conf-chart");
confChart
.width(500).height(200)
.dimension(confDim)
.group(confTotal,"Confidence Number")
.x(d3.scale.linear().domain([0,5]).range([10,400]))
.yAxisLabel("Number of data points")
.brushOn(true);
dc.renderAll();
});
This line that you've commented as being a filter is actually just setting up the dimension in its entirety:
var confDim = facts.dimension(function (d) { return d.conf;}); // conf # filter
You need to define a filter using one of the filter functions. See the documentation here.
Be careful to read the documentation for dimension.group carefully as it contains a gotcha where the filter on the dimension being grouped won't be applied. To work around this you may need to actually define 2 dimensions that are identical and use one for filtering and the other for grouping.
edit See here for the non-working example of what I'm trying to do: http://bl.ocks.org/elsherbini/5814788
I am using dc.js to plot data collected from bee hives at my university. I am pushing new data to the graphs on every database change (using the magic of Meteor). When the database is over 5000 records or so, rerendering the lines gets really slow. So I want to use simplify.js to preprocess the lines before rendering. To see what I'm talking about, go to http://datacomb.meteor.com/. The page freezes after a couple of seconds, so be warned.
I have started to extend dc.js with a simpleLineChart, which would inherit from the existing dc.lineChart object/function. Here is what I have so far:
dc.simpleLineChart = function(parent, chartGroup) {
var _chart = dc.lineChart(),
_tolerance = 1,
_highQuality = false,
_helperDataArray;
_chart.tolerance = function (_) {
if (!arguments.length) return _tolerance;
_tolerance = _;
return _chart;
};
_chart.highQuality = function (_) {
if (!arguments.length) return _highQuality;
_highQuality = _;
return _chart;
};
return _chart.anchor(parent, chartGroup);
}
simplify.js takes in an array of data, a tolerance, and a boolean highQuality, and returns a new array with fewer elements based on it's simplification algorithm.
dc.js uses crossfilter.js. dc.js charts are associated with a particular crossfilter dimension and group. Eventually, it uses the data from someGroup().all() as the data to pass to a d3.svg.line(). I can't find where this is happening in the dc.js source, but this is where I need to intervene. I want to find this method, and override it in the dc.simpleLineChart object that I am making.
I was thinking something like
_chart.theMethodINeedToOverride = function(){
var helperDataArray = theChartGroup().all().map(function(d) { return {
x: _chart.keyAccessor()(d),
y: _chart.valueAccessor()(d)};})
var simplifiedData = simplify(helperDataArray, _tolerance, _highQuality)
g.datum(simplifiedData); // I know I'm binding some data at some point
// I'm just not sure to what or when
}
Can anyone help me either identify which method I need to override, or even better, show me how to do so?
dc.js source: https://github.com/NickQiZhu/dc.js/blob/master/dc.js
edit:
I think I may have found the function I need to override. The original function is
function createGrouping(stackedCssClass, group) {
var g = _chart.chartBodyG().select("g." + stackedCssClass);
if (g.empty())
g = _chart.chartBodyG().append("g").attr("class", stackedCssClass);
g.datum(group.all());
return g;
}
And I have tried to override it like so
function createGrouping(stackedCssClass, group) {
var g = _chart.chartBodyG().select("g." + stackedCssClass);
if (g.empty())
g = _chart.chartBodyG().append("g").attr("class", stackedCssClass);
var helperDataArray = group().all().map(function(d) { return {
x: _chart.keyAccessor()(d),
y: _chart.valueAccessor()(d)};})
var simplifiedData = simplify(helperDataArray, _tolerance, _highQuality)
g.datum(simplifiedData);
return g;
}
However, when I make a simpleLineChart, it is just a linechart with a tolerance() and highQuality() method. See here: http://bl.ocks.org/elsherbini/5814788
Well, I pretty much did what I set out to do.
http://bl.ocks.org/elsherbini/5814788
The key was to not only modify the createGrouping function, but also the lineY function in the code. (lineY gets set to tell the d3.svg.line() instance how to set the y value of a given point d)
I changed it to
var lineY = function(d, dataIndex, groupIndex) {
return _chart.y()(_chart.valueAccessor()(d));
};
The way lineY was written before, it was looking up the y value in an array, rather than using the data bound to the group element. This array had it's data set before i made my changes, so it was still using the old, pre-simplification data.
Iam trying to Draw multiple line in jqplot using json Data
I have doubt on jqPlot, i am using json data to plot a chart in jqPlot.I have two sets
of json data like this
var jsonData1 = {"PriceTicks": [{"Price":5.5,"TickDate":"Jan"},
{"Price":6.8,"TickDate":"Mar"},
{"Price":1.9,"TickDate":"June"},
{"Price":7.1,"TickDate":"Dec"}]};
var jsonData2 = {"PriceTicks": [{"Price":1.5,"TickDate":"Jan"},
{"Price":2.8,"TickDate":"Feb"},
{"Price":3.9,"TickDate":"Apr"},
{"Price":9.1,"TickDate":"Dec"}]};
suppose i have a javascript arrays i can call like this
$.jqplot('chartdiv', [cosPoints, sinPoints, powPoints1, powPoints2, powPoints3],
{
title:'Line Style Options',
seriesDefaults: {
showMarker:false,
},
}
);
where cosPoints,sinePoints etc are the javascript arrays in the similar way i have to
call the json data , i Used the following way it is failed ,please help me.
$.jqplot('jsonChartDiv', [jsonData1,jsonData2] {
title:'Fruits',
series:[{showMarker:false}],
dataRenderer: $.jqplot.ciParser,
axes: {
xaxis: {
renderer:$.jqplot.CategoryAxisRenderer,
}
},
});
I didn't manage to get the ciParser Renderer Plugin to work with your Data Format Requirement.
In the following jsFiddle you see a working example to convert your JSON objects to the required datastructure which is needed to display a Line Chart.
JSFiddle
// get array with both series
arSeries = [jsonData1, jsonData2];
// initalizat the output array for jqPlot
arJQ = [];
// for each Data Series
for (var z = 0; z < arSeries.length; z++)
{
// array for the prices
jqPrices = [];
var prices = arSeries[z].PriceTicks;
for (var i = 0; i < prices.length; i++)
{
jqPrices.push([prices[i].TickDate.toString(), prices[i].Price]);
}
// Result [["Jan",1.5],["Feb",2.8],["Apr",3.9],["Dec",9.1]]
// add to series array
arJQ.push(jqPrices);
}
To prevent the line going backward you should spedify either your date more granular or with the same tickes on each data record.