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

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.

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 HeatMap crossfilter grey un-selected

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')

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/

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