dc.js HeatMap crossfilter grey un-selected - dc.js

I would like my heatmap to react the same way if the date on the heatmap's x asis is selected or if the date is selected from the yearSlicer (rowChart)
I've tried using these posts:
Is there a way to set a default color for the un-selected boxes in heatmap in dc.js when crossfilter is applied?
dc.js heatmap deselected colors
.colors function in barChart dc.js with only two options
Trying to Assign Color for Null values in a map D3,js v4
yearSlicer(rowChart):
var yearSlicerDimension = ndx.dimension(function (d) {
return "" + d.Year;
})
yearSlicerGroup = yearSlicerDimension.group().reduceSum(function (d) {
return d.FTE;
});
yearSlicer
.title("")
.width(300)
.height(480)
.dimension(yearSlicerDimension)
.group(yearSlicerGroup)
.valueAccessor(function (kv) {
return kv.value > 0 ? 1 : 0; // If funtion to detect 0's
})
.ordering(function (d) { return -d.Year })
.xAxis().ticks(0)
Heat Map:
var dimension = ndx.dimension(function (d) { return [d.Year, d["Manager"]]; }),
FTEMonthGroup = dimension.group().reduce(
function reduceAdd(p, v) {
if (p.n === 0) {
p.color = 0;
p.toolTip = 0;
}
++p.n;
p.color += v.FTE;
p.toolTip += v.FTE;
return p;
},
function reduceRemove(p, v) {
--p.n;
p.color = "null";
return p;
},
function reduceInitial() {
return { n: 0, color: 0, toolTip: 0 };
});
heatMap
.width(900)
.height(800)
.dimension(dimension)
.group(FTEMonthGroup)
.margins({ left: 200, top: 30, right: 10, bottom: 20 })
.keyAccessor(function (d) { return d.key[0]; })
.valueAccessor(function (d) { return +d.key[1]; })
.colorAccessor(function (d) { return +d.value.color; })
.title(function (d) {
return "Manager: " + d.key[1] + "\n" +
"FTE: " + d.value.toolTip + "\n" +
"Date: " + d.key[0] + "";
})
.calculateColorDomain()
.on('renderlet', function (chart) {
chart.selectAll("g.cols.axis text")
.attr("transform", function () {
var coord = this.getBBox();
var x = coord.x + (coord.width / 2),
y = coord.y + (coord.height / 2);
return "rotate(-45 " + x + " " + y + ")"
})
});
What I would like:
What I get:

Right now it looks like your color accessor will return NaN for any bin which has anything removed from it, because
+"null" === NaN
Note that
+null === 0
but we don't necessarily want that, because we want an exceptional color for bins that had everything removed, not "the zero color".
It would be easier to try it out if you included a fiddle or something, but I think you should be able to get there with the following changes:
Properly null out your color when n goes to zero:
function reduceAdd(p, v) {
++p.n;
p.color += v.FTE;
p.toolTip += v.FTE;
return p;
},
function reduceRemove(p, v) {
--p.n;
if (p.n === 0) {
p.color = null;
p.toolTip = null;
}
return p;
},
We only need to check on remove, not on add, because n never goes to zero on add, by definition. It's okay to pair = null with += v.FTE because if you use += on a variable that contains null, it will get automatically coerced to 0 (unlike undefined which will go to NaN).
Use a color calculator to detect null and produce gray
Use the recently un-deprecated colorCalculator to detect nulls and use the gray of our choosing for them. Otherwise we pass the color through the color scale as usual:
heatMap
.colorCalculator(function(d, i) {
return d.value.color === null ?
'#ccc' : heatMap.colors()(d.value.color);
});
Note that internally, dc.js charts use the .selected and .deselected classes to show which items are selected by the brush and which are not, but that would be complicated here (especially if you still want the brush to work), so it's easier just to use the same color.
Again, this is all untested, but I think it's the right principle. Lmk if there are any issues and I'll be glad to fix it.
alternately... scale.unknown()?
It may also be possible to use scale.unknown() for this purpose, but it is brand-new to D3 5.8 and I haven't tried it. Something like
heatMap.colors().unknown('#ccc')

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.

Display multiple bar on barChart from a custom reducer

I have a group with custom reducer calculating various total and average values. The goal is to show them all on the same barChart. But I can only get the first bar to show. Here is the JSFiddler
https://jsfiddle.net/71k0guxe/15/
Is it possible to show all the value on the barChart?
Thanks in advance!
Data
ID,SurveySent,ResponseReceived
1,Yes,No
2,No,No
3,Yes,Yes
4,No,No
5,Yes,Yes
6,No,No
7,Yes,No
8,No,No
9,Yes,No
10,No,No
Code
var chart = dc.barChart("#test");
//d3.csv("morley.csv", function(error, experiments) {
var experiments = d3.csvParse(d3.select('pre#data').text());
var ndx = crossfilter(experiments),
dimStat = ndx.dimension(function(d) {return "Statistics";}),
groupStat = dimStat.group().reduce(reduceAdd, reduceRemove, reduceInitial);
function reduceAdd(p, v) {
++p.count;
if (v.SurveySent === "Yes") p.sent++;
if (v.ResponseReceived === "Yes") p.received++;
return p;
}
function reduceRemove(p, v) {
--p.count;
if (v.SurveySent === "Yes") p.sent--;
if (v.ResponseReceived === "Yes") p.received--;
return p;
}
function reduceInitial() {
return {count: 0, sent: 0, received: 0};
}
chart
.width(400)
.height(400)
.xUnits(dc.units.ordinal)
.label(function(d) { return d.data.value })
.elasticY(true)
.x(d3.scaleOrdinal().domain(["Total", "Sent", "Received"]))
.brushOn(false)
.yAxisLabel("This is the Y Axis!")
.dimension(dimStat)
.group(groupStat)
.valueAccessor(function (d) {
//Is it possible to return count sent and received all from here?
return d.value.count;
})
.on('renderlet', function(chart) {
chart.selectAll('rect').on("click", function(d) {
console.log("click!", d);
});
});
chart.render();
Just got some idea from the FAQ section of dc.js/wiki/FAQ
Fake Groups
"dc.js uses a very limited part of the crossfilter API - in fact, it really only uses dimension.filter() and group.all()."
I don't care about filtering, so i just need to mark up my own group.all. Basically transpose it from one row to multiple row. Works my purpose.
/* solution */
var groupStatTranposed = group_transpose(groupStat);
function group_transpose(source_group, f) {
return {
all:function () {
return [
{key: "Total", value: source_group.all()[0].value.count},
{key: "Sent", value: source_group.all()[0].value.sent},
{key: "Received", value: source_group.all()[0].value.received}
];
}
};
}
//use groupStatTranposed in the chart.
/** solution */

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/

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

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.

dc.js - "TypeError: _x is undefined" when using the brush

I am quite stuck with an issue on a series of charts I realized with dc.js.
The issue is in a scenario similar to the Nasdaq example on the main dc.js site: a stacked chart filtered using a bar chart with the brush.
The problem is that if I keep the browser console open, after everything loads, if I try to use the brush to select a period, an error appears in the console.
In Firefox I get this:
TypeError: _x is undefined[Learn More] coordinate-grid-mixin.js:468:12
prepareXAxis coordinate-grid-mixin.js:468:12
drawChart coordinate-grid-mixin.js:1139:8
_dc/dc.coordinateGridMixin/_chart._doRedraw coordinate-grid-mixin.js:1128:8
_dc/dc.baseMixin/_chart.redraw base-mixin.js:743:21
_dc/dc.redrawAll core.js:250:8
_dc/dc.baseMixin/_chart.redrawGroup base-mixin.js:794:12
_dc/dc.coordinateGridMixin/_chart._brushing/< coordinate-grid-mixin.js:1032:16
_dc/dc.events.trigger/< events.js:34:12
While in Chrome I get this:
Uncaught TypeError: Cannot read property 'domain' of undefined
at prepareXAxis (coordinate-grid-mixin.js:468)
at drawChart (coordinate-grid-mixin.js:1139)
at Object._chart._doRedraw (coordinate-grid-mixin.js:1128)
at Object._chart.redraw (base-mixin.js:706)
at Object.dc.redrawAll (core.js:250)
at Object._chart.redrawGroup (base-mixin.js:757)
at coordinate-grid-mixin.js:1032
at events.js:34
I tried different dc.js versions and I get the same issues (from the stable 2.0.5 to the last one that has been used for the logs above 2.1.9). The only difference I got with a dc.min.js version is that instead of "undefined _x" I got an "undefined z".
Here is a picture just to get a feeling:
Now the good part of the issue, is that the charts work fine. I can visualize the data and filter it, nonetheless, the console gets soon filled up with the same error that continues to appear.
here are the more relevant parts of my code:
// a bit of copy paste to get the sense
var dateDim = ndx.dimension(function (d) { return d.date; });
var grpTime =
dateDim
.group(function (d) { return d3.time.minute(d); })
.reduce(dash_reduceAdd, dash_reduceSub, dash_reduceInit);
var minDate = d3.time.minute(dateDim.bottom(1)[0]["date"]);
var maxDate = d3.time.minute(dateDim.top(1)[0]["date"]);
stackChart
.renderArea(true)
.height(200)
.margins({top: 15, right: 50, bottom: 20, left: 40})
.x(d3.time.scale().domain([minDate, maxDate]))
.xUnits(d3.time.minutes)
.legend(dc.legend().x(70).y(20).itemHeight(13).gap(5))
.renderHorizontalGridLines(true)
.title(stackChartTitle)
.elasticY(true)
.transitionDuration(1500)
.dimension(dateDim)
.group(grpTime, str_a)
.valueAccessor(function(d) { return d.value.val_a_Avg; })
.stack(grpTime, str_b, function(d) { return d.value.val_b_Avg; })
.ordinalColors(['#4faf00', '#5c00e6'])
.hidableStacks(true)
.rangeChart(volumeChart)
.controlsUseVisibility(true)
.brushOn(false)
;
volumeChart
.height(60)
.margins({top: 0, right: 50, bottom: 20, left: 40})
.dimension(dateDim)
.group(grpTime)
.valueAccessor(function(d) { return d.value.vAvg; })
.centerBar(true)
.gap(1)
.x(d3.time.scale().domain([minDate, maxDate]))
.xUnits(d3.time.minutes)
.elasticY(true)
.alwaysUseRounding(true)
.on('renderlet', function (chart) {
var rangeFilter = chart.filter();
var focusFilter = chart.focusChart().filter();
if (focusFilter && !rangeFilter) {
dc.events.trigger(function () {
chart.focusChart().replaceFilter(rangeFilter);
});
}
})
.brushOn(true)
.controlsUseVisibility(true)
;
stackChart.render();
volumeChart.render();
Any help would really be appreciated, I am not really sure how to move to fix this issue.
EDIT
I am adding the reduce functions here:
function groupArrayAdd(keyfn) {
var bisect = d3.bisector(keyfn);
return function(elements, item) {
var pos = bisect.right(elements, keyfn(item));
elements.splice(pos, 0, item);
return elements;
};
}
function groupArrayRemove(keyfn) {
var bisect = d3.bisector(keyfn);
return function(elements, item) {
var pos = bisect.left(elements, keyfn(item));
if(keyfn(elements[pos])===keyfn(item))
elements.splice(pos, 1);
return elements;
};
}
// Custom reduce functions
function dash_reduceAdd(p, v) {
++p.count;
// Sums
p.val_a_Sum += v.va;
p.val_b_Sum += v.vb;
// Averages
p.val_a_Avg = p.count ? p.val_a_Sum / p.count : 0;
p.val_b_Avg = p.count ? p.val_b_Sum / p.count : 0;
// Maxes
p.vaMax = groupArrayAdd(function (d) { return d.va; })(p.vaMax, v.va);
p.vbMax = groupArrayAdd(function (d) { return d.vb; })(p.vbMax, v.vb);
return p;
}
function dash_reduceSub(p, v) {
--p.count;
// Sums
p.val_a_Sum -= v.va;
p.val_b_Sum -= v.vb;
// Averages
p.val_a_Avg = p.count ? p.val_a_Sum / p.count : 0;
p.val_b_Avg = p.count ? p.val_b_Sum / p.count : 0;
// Maxes
p.vaMax = groupArrayAdd(function (d) { return d.va; })(p.vaMax, v.va);
p.vbMax = groupArrayAdd(function (d) { return d.vb; })(p.vbMax, v.vb);
return p;
}
function dash_reduceInit() {
return { count:0,
val_a_Sum:0, val_b_Sum:0,
val_a_Avg: 0, val_b_Avg: 0,
vaMax: [], vbMax:[] };
}
I found the issue causing it. It took me really quite some time. I went disassembling the code, removing one part after the other and checking if the issue persisted...
...at the end I found a really stupid problem. I forgot to remove one lineChart variable that was declared but never rendered...I havo no clue how I didn't noticed it before but anyway, after removing that chart the issue disappeared.
It was impossible here to find a solution from the code, I am answering just because it might be a clue for future people having that kind of error message.

Resources