dc.js clicking on one chart not filtering the other (different dimensions) - d3.js

I've been happily using DC.js for some time. Today is the first time I've created two charts, from two dimensions, both running off the same crossfiltered variable, facts - and they don't filter each other. The bubble chart filters the bar chart but not vice versa. Am I making some obvious error? Very grateful for any pointers.
var facts = crossfilter(data);
var all = facts.groupAll();
print_filter(facts);
var appDimension = facts.dimension(function(d){ return d.ShortName; });
var appGroup = appDimension.group().reduce(
function(p,v){ p.count++; v.NumUsers==0?p.numUsers=1:p.numUsers=v.NumUsers; p.numClients=v.NumClients; p.lc=v.lc; p.LifeCycle=v.LifeCycle; p.fv=v.fv; p.FutureValue=v.FutureValue; return p; },
function(p,v){ p.count--; v.NumUsers==0?p.numUsers=1:p.numUsers=v.NumUsers; p.numClients=v.NumClients; p.lc=v.lc; p.LifeCycle=v.LifeCycle; p.fv=v.fv; p.FutureValue=v.FutureValue; return p; },
function(){ return { count:0, numUsers: 0, numClients: 0, lc: 0, LifeCycle: '', fv: 0, FutureValue: '' }; }
);
var lifeCycleDimension = facts.dimension(function(d){ return d.LifeCycle; });
var tempName='';
var lifeCycleGroup = lifeCycleDimension.group().reduce(
function(p,v){
if (tempName!=v.ShortName) {
p++;
tempName=v.ShortName;
}
return p;
},
function(p,v){
if (tempName!=v.ShortName) {
p--;
tempName=v.ShortName;
}
return p;
},
function(){ return 0; }
);
var yearlyBubbleChart = dc.bubbleChart('#col1')
.width(360)
.height(600)
.margins({top: 10, right: 100, bottom: 30, left: 40})
.dimension(appDimension)
.group(appGroup)
.clipPadding(200)
.yAxisLabel('Number of users')
.xAxisLabel('Number of clients')
// .ordinalColors(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628'])
.colorAccessor(function(p){
return p.value.lc;
})
.colors(colorbrewer.RdYlGn[9])
.colorDomain([1,6])
.keyAccessor(function (p) {
return p.value.numClients;
})
.valueAccessor(function (p) {
return p.value.numUsers;
})
.radiusValueAccessor(function (p) {
return p.value.fv;
})
.title(function(d){ return 'App: '+d.key + '\nNum users: '+d.value.numUsers+'\nNum clients: '+d.value.numClients+'\nLife cycle: '+d.value.LifeCycle+'\nFuture value: '+d.value.FutureValue; })
.maxBubbleRelativeSize(0.15)
.x(d3.scale.linear().domain([0, 608]))
.y(d3.scale.log().base(Math.E).domain([1, 120000]))
.r(d3.scale.linear().domain([0.5, 3]))
// .elasticY(true)
// .elasticX(true)
.yAxisPadding(100)
.xAxisPadding(500)
.renderHorizontalGridLines(true)
.renderVerticalGridLines(true);
yearlyBubbleChart.yAxis().tickFormat(function(d){ return Math.round(d); });
yearlyBubbleChart.xAxis().ticks(5);
var barAmountChart = dc.barChart('#barCount')
.width(530)
.height(200)
.margins({top: 10, right: 50, bottom: 30, left: 45})
.dimension(lifeCycleDimension)
.gap(1)
.group(lifeCycleGroup)
.title(function(d){ return d.value+' apps in '+d.key+' phase '; })
.x(d3.scale.ordinal().domain(['Idea','Plan 2017','New','Re-Newed','SunSet','Legacy']))
.xUnits(dc.units.ordinal);
barAmountChart.yAxis().ticks(4);

Related

How to apply a function to an array in DC.js

I have two charts, the first, a line chart, in which the user can brush.
Based on the selection, a bar chart (only one bar) updates its value thanks to a specific function. I would like to apply this specific function in an efficient manner to the new array.
I started by following the complex reduce example. There is something wrong with my logic because the function std gets
the all dataset instead of an array. It seems that to put the function within the valueAccessor is not the right thing to do.
This is my code:
/**********************************
* Step0: javascript functions *
**********************************/
// instead of calculating the desired metric on every change, which is slow, we'll just maintain
// these arrays of rows and calculate the metrics when needed in the accessor
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;
};
}
function groupArrayInit() {
return [];
}
/**********************************
* Step1: Load data from json file *
**********************************/
d3.json("{% url "block__time_series" portfolio value_date %}").then(function(data){
const dateFormatSpecifier = "%Y-%m-%d";
const dateFormat = d3.timeFormat(dateFormatSpecifier);
const dateFormatParser = d3.timeParse(dateFormatSpecifier);
const numberFormat = d3.format('.2f');
data.forEach(function(d) {
d.dd = dateFormatParser(d.date);
d.month = d3.timeMonth(d.dd); // pre-calculate month for better performance
d.returns = +d.returns;
d.value = +d.value;
});
/******************************************************
* Step2: Create the dc.js chart objects & ling to div *
******************************************************/
const myBarChart = new dc.BarChart('#my_bar_chart');
const myLineChart = dc.compositeChart('#my_line_chart');
const palette_color_block4 = ["#6c5373", "#8badd9", "#b6d6f2", "#45788c", "#6E87F2", "#996A4E",
"#BF7761", "#735360", "#D994B0", "#6C5373", "#7F805E", "#A6A27A", "#48BDCC", "#FFC956", "#f2f2f2"]
/************************************************
* Step3: Run the data through crossfilter *
************************************************/
var facts = crossfilter(data), // Gets our 'facts' into crossfilter
returns = function (d) {return +d.returns}
/*Here my function that I want to use */
function std(kv) {
return d3.deviation(kv.value, returns);
}
/******************************************************
* Step4: Create the Dimensions *
******************************************************/
const dateDimension = facts.dimension(d => d.dd);
var returnsDimension = facts.dimension(returns);
var volGroup = dateDimension.group().reduce(groupArrayAdd(returns), groupArrayRemove(returns),
groupArrayInit);
var valueGroup = dateDimension.group().reduceSum(function (d) {return d.value; });
const moveMonths = facts.dimension(d => d.month);
const monthlyMoveGroup = moveMonths.group().reduceSum(d => d.value);
/***************************************
* Step5: Create the Visualisations *
***************************************/
myBarChart /* dc.BarChart('#my_bar_chart', 'chartGroup')*/
.width(400)
.height(200)
.x(d3.scaleBand())
.xUnits(dc.units.ordinal)
.colorAccessor(d => d.key)
.ordinalColors(palette_color_block4)
.margins({left: 80, top: 30, right: 10, bottom: 20})
.elasticY(false)
.brushOn(false)
.controlsUseVisibility(false)
.valueAccessor(std)
.dimension(returnsDimension)
.group(volGroup);
mylineChart /*dc.compositeChart('#my_line_chart', 'chartGroup')*/
.width(800)
.height(200)
.transitionDuration(1000)
.margins({top: 20, right: 10, bottom: 10, left: 10})
.dimension(moveMonths)
.mouseZoomable(true)
.round(d3.timeMonth.round)
.xUnits(d3.timeMonths)
.renderHorizontalGridLines(true)
.legend(new dc.Legend().x(800).y(10).itemHeight(13).gap(5))
.brushOn(true)
.title( function(d) { return dateFormat(d.key) + ': ' + numberFormat(d.value);
})
.valueAccessor(function (d) { return d.value})
.compose([
dc.lineChart(mylineChart).group(valueGroup , data[0].name)
]);
/****************************
* Step6: Render the Charts *
****************************/
dc.renderAll();
});

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 setting the colour of sub bar chart

I'm trying to set the color of a barchart in a composite chart on click but the renderlet is having problems.
dimensions={
{"Data1": ["line", AppStyles.color.warning],
"Data2": ["line", AppStyles.color.danger],
"Data3": ["bar", AppStyles.color.blue]
}
}
formatData = (data) => {
let formattedData = [];
for(let key in data) {
formattedData.push({
...data[key],
x: this.parseDate.parse(data[key].x)
})
}
return formattedData;
}
componentDidMount(){
let data = this.formatData(this.props.data);
this.ndx = crossfilter.crossfilter(data);
this.chart = dc.compositeChart(this.multiLineChartContainer);
this.dimension = this.ndx.dimension((d) => {
return d.x;
});
let minDate = this.dimension.bottom(1)[0].x;
let maxDate = this.dimension.top(1)[0].x;
let composeGroup = [];
Object.keys(this.props.dimensions).map((dim,i) => {
let grp = this.dimension.group().reduceSum((d) => {
return d[dim];
});
if(this.props.dimensions[dim][0] === "bar"){
composeGroup.push(dc.barChart(this.multiLineChartContainer)
.group(grp, dim)
.colors("blue")
.centerBar(true)
.renderlet((chart) => {
chart.selectAll('rect.bar').on('click', (event)=>{
console.log("clicked rect")
chart.colorAccessor(function(d){
console.log(d.key);
return d.key
}).colors(d3.scale.ordinal().domain(Object.keys(this.props.dimensions))
.range(['blue', '#5C5ED7', 'red', AppStyles.color.warning]))
})
})
)
} else {
composeGroup.push(dc.lineChart(this.multiLineChartContainer)
.group(grp, dim)
.colors(this.props.dimensions[dim][1])
.useRightYAxis(true)
);
}
});
this.chart.width(this.props.width)
.height(this.props.height)
.renderHorizontalGridLines(true)
.x(d3.time.scale().domain([minDate, maxDate]))
.elasticY(true)
.elasticX(true)
.xAxisLabel("Cohort")
.brushOn(false)
.yAxisLabel("Left")
.rightYAxisLabel("Right")
.xUnits(()=>{
return 30;
})
.legend(dc.legend().x(this.chart.width()- 130))
.compose(composeGroup)
this.chart.renderlet((chart) => {
chart.selectAll('circle, rect.bar').on("click", (event) => {
this.props.dataSelect(event);
});
});
this.chart.xAxis().ticks(5)
this.chart.render();
}
I've tried adding a renderlet to the subchart but it never gets invoked. I'm about to rerender the entire chart now and set transitionDuration to 0 just to reassign the colours. Is this really the best way to do this?

Creating table in canvas / Phaser 3 (Priority)

Can any body help with How to create Tables in Phaser-3(Priority) / Canvas.
Table like this.
Without styling is also ok. Just I want to know how we can create table in Phaser-3(Priority) / Canvas.
You can try to Rex UI Plugin
Here you can find a DEMO
Other demos (scrolling, fix-width-sizer and so on..) available HERE.
HTML
<footer><div id=version></div></footer>
CSS
html, body {
height: 100%;
}
body {
margin: 0;
padding: 0;
background: #222;
color: #eee;
font: caption;
}
#version {
position: absolute;
left: 5px;
top: 605px;
}
JS
const Random = Phaser.Math.Between;
const COLOR_PRIMARY = 0x4e342e;
const COLOR_LIGHT = 0x7b5e57;
const COLOR_DARK = 0x260e04;
class Demo extends Phaser.Scene {
constructor() {
super({
key: 'examples'
})
}
preload() {
this.load.scenePlugin({
key: 'rexuiplugin',
url: 'https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/plugins/dist/rexuiplugin.min.js',
sceneKey: 'rexUI'
});
}
create() {
this.print = this.add.text(0, 0, '');
var db = createDataBase(400);
var tabs = this.rexUI.add.tabs({
x: 400,
y: 300,
panel: this.rexUI.add.gridTable({
background: this.rexUI.add.roundRectangle(0, 0, 20, 10, 10, COLOR_PRIMARY),
table: {
width: 250,
height: 400,
cellWidth: 120,
cellHeight: 60,
columns: 2,
mask: {
padding: 2,
},
},
slider: {
track: this.rexUI.add.roundRectangle(0, 0, 20, 10, 10, COLOR_DARK),
thumb: this.rexUI.add.roundRectangle(0, 0, 0, 0, 13, COLOR_LIGHT),
},
// scroller: true,
createCellContainerCallback: function (cell) {
var scene = cell.scene,
width = cell.width,
height = cell.height,
item = cell.item,
index = cell.index;
return scene.rexUI.add.label({
width: width,
height: height,
background: scene.rexUI.add.roundRectangle(0, 0, 20, 20, 0).setStrokeStyle(2, COLOR_DARK),
icon: scene.rexUI.add.roundRectangle(0, 0, 20, 20, 10, item.color),
text: scene.add.text(0, 0, item.id),
space: {
icon: 10,
left: 15
}
});
},
}),
leftButtons: [
createButton(this, 2, 'AA'),
createButton(this, 2, 'BB'),
createButton(this, 2, 'CC'),
createButton(this, 2, 'DD'),
],
rightButtons: [
createButton(this, 0, '+'),
createButton(this, 0, '-'),
],
space: {
leftButtonsOffset: 20,
rightButtonsOffset: 30,
leftButton: 1,
},
})
.layout()
//.drawBounds(this.add.graphics(), 0xff0000);
tabs
.on('button.click', function (button, groupName, index) {
switch (groupName) {
case 'left':
// Highlight button
if (this._prevTypeButton) {
this._prevTypeButton.getElement('background').setFillStyle(COLOR_DARK)
}
button.getElement('background').setFillStyle(COLOR_PRIMARY);
this._prevTypeButton = button;
if (this._prevSortButton === undefined) {
return;
}
break;
case 'right':
// Highlight button
if (this._prevSortButton) {
this._prevSortButton.getElement('background').setFillStyle(COLOR_DARK)
}
button.getElement('background').setFillStyle(COLOR_PRIMARY);
this._prevSortButton = button;
if (this._prevTypeButton === undefined) {
return;
}
break;
}
// Load items into grid table
var items = db
.chain()
.find({
type: this._prevTypeButton.text
})
.simplesort('id', {
desc: (this._prevSortButton.text === '-') // sort descending
})
.data();
this.getElement('panel').setItems(items).scrollToTop();
}, tabs);
// Grid table
tabs.getElement('panel')
.on('cell.click', function (cellContainer, cellIndex) {
this.print.text += cellIndex + ': ' + cellContainer.text + '\n';
}, this)
.on('cell.over', function (cellContainer, cellIndex) {
cellContainer.getElement('background')
.setStrokeStyle(2, COLOR_LIGHT)
.setDepth(1);
}, this)
.on('cell.out', function (cellContainer, cellIndex) {
cellContainer.getElement('background')
.setStrokeStyle(2, COLOR_DARK)
.setDepth(0);
}, this);
tabs.emitButtonClick('left', 0).emitButtonClick('right', 0);
}
update() {}
}
var createDataBase = function (count) {
var TYPE = ['AA', 'BB', 'CC', 'DD'];
// Create the database
var db = new loki();
// Create a collection
var items = db.addCollection('items');
// Insert documents
for (var i = 0; i < count; i++) {
items.insert({
type: TYPE[i % 4],
id: i,
color: Random(0, 0xffffff)
});
}
return items;
};
var createButton = function (scene, direction, text) {
var radius;
switch (direction) {
case 0: // Right
radius = {
tr: 20,
br: 20
}
break;
case 2: // Left
radius = {
tl: 20,
bl: 20
}
break;
}
return scene.rexUI.add.label({
width: 50,
height:40,
background: scene.rexUI.add.roundRectangle(0, 0, 50, 50, radius, COLOR_DARK),
text: scene.add.text(0, 0, text, {
fontSize: '18pt'
}),
space: {
left: 10
}
});
}
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Demo
};
var game = new Phaser.Game(config);

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