Crossfilter isn't applying filter to multi line-chart. What am I missing? - dc.js

I am new to using crossfilter, dc.js, and d3.js. I am struggling to get the filters to apply to my composite line chart. I've gone through several tutorials, but apparently am missing something as the charts don't change or look different at all if I remove the dimension with the filter applied.
Here is an example of my data:
var data = array(
{
price:{value: 38}
shipment:{start_date: "2017-12-06", end_date: "2018-01-15"}
side:"sell"
},
{
price:{value: 44}
shipment:{start_date: "2017-10-08", end_date: "2018-01-15"}
side:"sell"
},
{
price:{value: 38}
shipment:{start_date: "2017-11-15", end_date: "2018-01-15"}
side:"buy"
},
{
price:{value: 38}
shipment:{start_date: "2017-10-25", end_date: "2018-01-15"}
side:"buy"
}
);
And here is where I declare my dimensions:
` var crossFilteredData = crossfilter(data);
// Dimension by start_date
var dateDimension = crossFilteredData.dimension(function(d) {
var date = Date.parse(d.shipment.start_date);
return date;
});
// Dimension by side
var sideDimension = crossFilteredData.dimension(function(d) {
console.log(d.side);
return d.side;
});
sideDimension.filter("buy");
sideDimension.top(Infinity);`
After declaring my dimensions and applying a filter to the sideDimension, I am building my group and calculating a date's max price and min price for each day:
var performanceByDateGroup = dateDimension.group().reduce(
function (p, v) {
++p.count;
p.sum += v.price.value;
// Calculate Min
if (p.minPrice > v.price.value) {
p.minPrice = v.price.value;
}
// Calculate Max
if (p.maxPrice < v.price.value) {
p.maxPrice = v.price.value;
}
return p;
},
function (p, v) {
--p.count;
p.sum -= v.price.value;
return p;
},
function () {
return {
count: 0,
sum: 0,
minPrice: 1000,
maxPrice: 0
};
}
);
Lastly, I put the dimension and groups into the composite line chart:
priceChart
.width(960)
.height(400)
.margins({top: 10, right: 10, bottom: 40, left: 10})
.transitionDuration(500)
.elasticY(true)
.renderHorizontalGridLines(true)
.yAxisLabel('Price')
.shareTitle(false)
.x(d3.time.scale().domain([Date.parse("2017-11-01"), Date.parse("2018-03-31")]))
.xAxisLabel('Shipment Start Date')
.legend(dc.legend().x(40).y(0).itemHeight(16).gap(4))
.compose([
dc.lineChart(priceChart)
.dimension(dateDimension)
.group(performanceByDateGroup, 'Min Price')
.colors('red')
.renderTitle(true)
.title(function(d) {
return 'Min: $' + d.value.minPrice.toFixed(2);
})
.valueAccessor(function (d) {
return d.value.minPrice;
}),
dc.lineChart(priceChart)
.dimension(dateDimension)
.group(performanceByDateGroup, 'Max Price')
.colors('green')
.renderTitle(true)
.title(function(d) {
return 'Max: $' + d.value.maxPrice.toFixed(2);
})
.valueAccessor(function (d) {
return d.value.maxPrice;
})
])
.brushOn(false);
dc.renderAll();
The chart shows all the plotted points, as if the entire sideDimension variable is not being recognized at all. If I remove the sideDimension variable and filter, the chart looks the exact same.
I greatly appreciate any help or suggestions you can offer.

It's difficult, but not impossible to calculate min and max values using a crossfilter reduction.
When crossfilter is evaluating a group, it will first add all the records and then remove the records that don't match the filters. This is so that the result is consistent whether or not the filters existed when the dimension was created. (For example, you want zeros for values that exist but are filtered out.)
In this case, you are not doing anything with minPrice and maxPrice inside of your reduceRemove function:
function (p, v) {
--p.count;
p.sum -= v.price.value;
return p;
},
So as we observe, the records are added but never removed.
However, the situation is worse than this, because min and max are more complicated aggregations than sums and averages. Think about it: you can remember the min and max, but when those are removed, what value do you fall back on?
reductio has handy functions for doing min and max, or if you want to do it yourself, this example shows how.

Related

assign custom reduction value to variable

I calculate the average of specific column with below code:
var averageGroup = all.reduce(
function(p, v) {
++p.number;
p.StockDay += v.StockDay ;
p.average= p.StockDay/p.number ;
return p;
},
function(p, v) {
--p.number;
p.StockDay -= v.StockDay ;
p.average= p.StockDay/p.number ;
return p;
},
function() {
return {
number: 0,
average: 0,
StockDay : 0,
};
}
);
Now I want to assign this average value to variable because I will use it within my dc.barChart.
I want to assign different colors to columns where value is more than average.
The problem is when I make filter on charts it redraw the barchart but it doesn't change color according to new average which is calculated in averageGroup above.
var stockChart= dc.barChart('#stock')
.width(2000)
.height(600)
.margins({top: 10, right: 0, bottom: 130, left: 80})
.dimension(areaDim)
.group(areaGroup)
.valueAccessor(function(p) {
return p.value.avg;
})
.x(d3.scale.ordinal())
.xUnits(dc.units.ordinal)
.ordering(function(p) {return -p.value.avg})
.colors(d3.scale.ordinal().domain(["positive", "negative"])
.range(["#FF0000", "#00FF00"]))
.colorAccessor(function(p) {
if (p.value.avg> mean) {
return "positive";
}
return "negative";
})
.elasticX(true)
.renderlet(function (chart) {
chart.selectAll("g.x text")
.attr('dx', '-40')
.attr('transform', "rotate(-45)");
})
.elasticY(true)
.yAxis().tickFormat(d3.format('.3s'))
I calculate the mean variable shown below: But it gives me static value and doesn't change as I filter charts.
var selectedData = data.filter(function(d) {
return d.StockDay;
})
mean = d3.mean(selectedData,function(d) { return d.StockDay]})
But I want this mean variable come from averageGroup custom reduction function so when I filter charts and average changes my dc.barchart will change color according to the filtered average.

dc.js bubble chart - multidimension grouping issue and unable to get custom reducer to work

I'm currently trying to produce a dashboard in dc.js for my master's thesis and I have hit a real roadblock today if anyone could please help it would be much appreciated. I'm new to Javascript and dc so I'll try my best to explain...
My data format (Probe Request with visible SSID):
{"vendor":"Huawei Technologies Co.Ltd","SSID":"eduroam","timestamp":"2018-07-10 12:25:26","longitude":-1.9361,"mac":"dc:d9:16:##:##:##","packet":"PR-REQ","latitude":52.4505,"identifier":"Client"}
My data format (Probe Request with Broadcast / protected SSID):
{"vendor":"Nokia","SSID":"Broadcast","timestamp":"2018-07-10 12:25:26","longitude":-1.9361,"mac":"dc:d9:16:##:##:##","packet":"PR-REQ","latitude":52.4505,"identifier":"Client"}
I'm trying to produce a bubble chart which will display vendors as a bubble (size denoted by volume of packets collected for that vendor) then plot the bubble against X axis unprotected (any SSID != broadcast) & Y axis protected (packets where "Broadcast" is in the data)
Sketch of what I mean
What I've managed to get so far
I've managed to get a bar/ row/pie charts to work as they only require me to use one dimension and run them through a group. But I think I'm fundamentally misunderstanding how to pass multiple dimensions to a group.
for each at the top adds a new value of 0 / 1 to triple if Broadcast is present in the data.
Then I'm using a custom reducer to count 0 / 1 related to unpro & pro which will be used to plot the X / Y
I've tried reworking the nasdaq example and I'm getting nowhere
Code:
queue()
.defer(d3.json, "/uniquedevices")
.await(plotVendor);
function plotVendor(error, packetsJson) {
var packets = packetsJson;
packets.forEach(function (d) {
if(d["SSID"] == "Broadcast") {
d.unpro = 0;
d.pro = 1;
} else {
d.unpro = 1;
d.pro = 0;
}
});
var ndx = crossfilter(packets);
var vendorDimension = ndx.dimension(function(d) {
return [ d.vendor, d.unpro, d.pro ];
});
var vendorGroup = vendorDimension.group().reduce(
function (p, v) {
++p.count;
p.numun += v.unpro;
p.numpr += v.pro;
return p;
},
function (p, v) {
--p.count;
p.numun -= v.unpro;
p.numpr -= v.pro;
return p;
},
function () {
return {
numun: 0,
numpr: 0
};
}
);
var vendorBubble = dc.bubbleChart("#vendorBubble");
vendorBubble
.width(990)
.height(250)
.transitionDuration(1500)
.margins({top: 10, right: 50, bottom: 30, left: 40})
.dimension(vendorDimension)
.group(vendorGroup)
.yAxisPadding(100)
.xAxisPadding(500)
.keyAccessor(function (p) {
return p.key[1];
})
.valueAccessor(function (p) {
return p.key[2];
})
.radiusValueAccessor(function (d) { return Object.keys(d).length;
})
.maxBubbleRelativeSize(0.3)
.x(d3.scale.linear().domain([0, 10]))
.y(d3.scale.linear().domain([0, 10]))
.r(d3.scale.linear().domain([0, 20]))
dc.renderAll();
};
Here is a fiddle: http://jsfiddle.net/adamistheanswer/tm9fzc4r/1/
I think you are aggregating the data right and the missing bits are
your accessors should look inside of value (that's where crossfilter aggregates)
.keyAccessor(function (p) {
return p.value.numpr;
})
.valueAccessor(function (p) {
return p.value.numun;
})
.radiusValueAccessor(function (d) {
return d.value.count;
})
your key should just be the vendor - crossfilter dimensions aren't geometric dimensions, they are what you filter and bin on:
var vendorDimension = ndx.dimension(function(d) {
return d.vendor;
});
you probably need to initialize count because ++undefined is NaN:
function () { // reduce-init
return {
count: 0,
numun: 0,
numpr: 0
};
}
Fork of your fiddle, with all the dependencies added, wrapping function disabled, and elasticX/elasticY (probably not what you want but easier to debug):
https://jsfiddle.net/gordonwoodhull/spw5oxkj/16/

How to decide dimensions and groups in dc.js?

I am new to dc.js and facing issues in deciding dimensions and groups. I have data like this
this.data = [
{Type:'Type1', Day:1, Count: 20},
{Type:'Type2', Day:1, Count: 10},
{Type:'Type1', Day:2, Count: 30},
{Type:'Type2', Day:2, Count: 10}
]
I have to show a composite chart of two linecharts one for type Type1 and other for Type2. My x-axis will be Day. So one of my dimensions will be Day
var ndx = crossfilter(this.data);
var dayDim = ndx.dimension(function(d) { return d.Day; })
How the grouping will be done? If I do it on Count, the total count of a particular Day shows up which I don't want.
Your question isn't entirely clear, but it sounds like you want to group by both Type and Day
One way to do it is to use composite keys:
var typeDayDimension = ndx.dimension(function(d) {return [d.Type, d.Day]; }),
typeDayGroup = typeDayDimension.group().reduceSum(function(d) { return d.Count; });
Then you could use the series chart to generate two line charts inside a composite chart.
var chart = dc.seriesChart("#test");
chart
.width(768)
.height(480)
.chart(function(c) { return dc.lineChart(c); })
// ...
.dimension(typeDayDimension)
.group(typeDayGroup)
.seriesAccessor(function(d) {return d.key[0];})
.keyAccessor(function(d) {return +d.key[1];}) // convert to number
// ...
See the series chart example for more details.
Although what Gordon suggested is working perfectly fine, if you want to achieve the same result using composite chart then you can use group.reduce(add, remove, initial) method.
function reduceAdd(p, v) {
if (v.Type === "Type1") {
p.docCount += v.Count;
}
return p;
}
function reduceRemove(p, v) {
if (v.Type === "Type1") {
p.docCount -= v.Count;
}
return p;
}
function reduceInitial() {
return { docCount: 0 };
}
Here's an example: http://jsfiddle.net/curtisp/7frw79q6
Quoting Gordon:
Series chart is just a composite chart with the automatic splitting of the data and generation of the child charts.

d3.time/crossfilter days are off by one

I've been trying to create a dc.js rowchart showing stats per day, my dimension and group are
var dayNameFormat = d3.time.format("%A");
var weekDayFormat = d3.time.format('%w'); //weekday as a decimal number [0(Sunday),6].
var dayOfWeek = ndx.dimension(function(d) {
return weekDayFormat(d.date) + '.' + dayNameFormat(d.date);
});
var dayOfWeekGroup = dayOfWeek.group().reduce(
function(p, d) {
++p.count;
p.totalPoints += +d.points_per_date;
p.averagePoints = (p.totalPoints / p.count);
if (d.student_name in p.studentNames) {
p.studentNames[d.student_name] += 1
} else {
p.studentNames[d.student_name] = 1;
p.studentCount++;
}
return p;
},
function(p, d) {
--p.count;
p.totalPoints -= +d.points_per_date;
p.averagePoints = (p.totalPoints / p.count);
if (p.studentNames[d.student_name] === 0) {
delete p.studentNames[d.student_name];
p.studentCount--;
}
return p;
},
function() {
return {
count: 0,
totalPoints: 0,
averagePoints: 0,
studentNames: {},
studentCount: 0
};
});
and chart
dayOfWeekChart
.width(250)
.height(180)
.margins({
top: 20,
left: 20,
right: 10,
bottom: 20
})
.dimension(dayOfWeek)
.group(dayOfWeekGroup)
.valueAccessor(function(d) {
return d.value.totalPoints
})
.renderLabel(true)
.label(function(d) {
return d.key.split('.')[1] + '(' + d.value.totalPoints + ' points)';
})
.renderTitle(true)
.title(function(d) {
return d.key.split('.')[1];
})
.elasticX(true);
I expected the results to match those of my database query
The total values are correct, but the days have been offset by a day (Sunday has Monday's total)
My fiddle https://jsfiddle.net/santoshsewlal/txrLw9Lc/
I've been doing my head in trying to get this right, any help will be great.
Thanks
It appears to be a UTC date/time problem. Dealing with data from multiple time zones is always confusing!
All of your timestamps are very near to the next day - they are all timestamped at 22:00. So it depends on the timezone which day they should be interpreted as. I guess you might be in the eastern hemisphere, which adds a couple of hours to these timestamps when you read them in your spreadsheet?
You're chopping off the time with substr:
d.date = dateFormat.parse(d.activity_date.substr(0, 10));
I'd suggest trying to parse the whole time instead:
var dateFormat = d3.time.format('%Y-%m-%dT%H:%M:%S.%LZ');
data.forEach(function(d, i) {
d.index = i;
d.date = dateFormat.parse(d.activity_date);
However, I'm no expert in timezones so I can't promise anything. Just pointing out where the problem likely lies.

How to display a graph of top n items?

I have a list of events and want to show who are the top participants (the ones that have come more often)
What I've done is a rowChart
var dim = ndx.dimension (function(d) {
if (!d.guest) {
return "Not mentioned";
}
return d.guest;
});
var group = dim.group().reduceSum(function(d) { return 1; });
var graph = dc.rowChart (".topvisitor")
.margins({top: 0, right: 10, bottom: 20, left: 10})
.height(300)
.width(200)
.cap(10)
.x(d3.scale.ordinal())
.elasticX(true)
.ordering(function(d){return -d.value})
.dimension(dim)
.group(group);
That kind of works, but there is a big "other" that I'd want to remove. Am I abusing the rowChart to create a topN graph?
So the path I followed is to filter the data first (by creating a fake group that has a new all() function that returns a top(n) of the real group):
var group = dim.group().reduceSum(function(d) { return 1; });
var filteredGroup = (function (source_group) {return {
all:function () {
return source_group.top(10).filter(function(d) {
return d.key != "Not mentioned";
});
}
};})(group);
and for the graph, use this group
.group(filteredGroup);
I think it does the trick, despite Gordon's approval, still feels a bit hackish, but it does the job (cap+filter some data)
you have to remove the cap, or implement the top function (same as all in this case)
As a side note: rowChart seems to be one of the chart where you can override the data function, so:
.data(function (group) {
return group.top(10);
})
would Work too (but wouldn't filter the "Not mentioned" items

Resources