Select Menu not updating when line charts are filtered - d3.js

please see this Pen
https://codepen.io/rdavi10471a2/project/editor/XwGGYo
clicking on any of the charts will filter the data but the filters are not reflected in the select menus
using the select menus themselves will filter the charts so it is unclear why the select menu UI does not update on chart filter.
choosing a year in the left most chart will cause the Year dropdown to change but selecting a department from the right most chart does not filter the department dropdown nor does selecting a single branch from the center chart filter the branch drop down.
menus are defined like this
branchdd
.dimension(branchDimension)
.group(branchDimension.group())
.multiple(true)
.numberVisible(5)
.title(kv => kv.key)
.controlsUseVisibility(true)
deptdd
.dimension(deptDimension)
.group(deptDimension.group())
.multiple(true)
.title(kv => kv.key)
.controlsUseVisibility(true)
perioddd
.dimension(dateDimension3)
.group(dateDimension3.group())
.multiple(true)
.title(kv => kv.key)
.controlsUseVisibility(true)
sample chart definition
salesbybranch
.width(300)
.height(400)
// .x(d3.scaleLinear().domain([6,20]))
.elasticX(true)
.dimension(branchDimension)
.group(salesbranch)
.fixedBarHeight(10)
.title(function(d) { return "Value: " +d.key+" " + dollarformat(d.value); })
.renderTitle(true)
salesbybranch.xAxis().ticks(5).tickFormat(d3.formatPrefix(".2", 1e6))
salesbybranch.renderlet(function (chart) {
salesbybranch.xAxis().ticks(5).tickFormat(d3.formatPrefix(".2", 1e6))
})

forgive the forEach on a single element but putting this in document ready after the dc.renderAll() so that it gets executed after the charts are added works (needs better formatting but it shows the user what the filters are.
would be better to move to a collapsible single div somewhere on the page as well
dc.chartRegistry.list().forEach( function(e){
//e.on('filtered', refreshTable);
['filter'].forEach( function(n) {
this.root().append('div')
.classed(n, true)
//.attr('id', `${this.anchorName()}-${n}`)
}, e);
['reset'].forEach( function(n) {
this.root().append('a')
.classed(n, true)
.attr('id', `${this.anchorName()}-${n}`)
.text(n === 'reset' ? 'Clear Filters' : '')
.on('click', function() {
console.log(e)
e.replaceFilter(null);
dc.redrawAll();
console.log(`clicked ${n} div`);
});
}, e);
});
$('.reset').hide();
$('.filter').css("background-color","yellow")
$('.filter').css("overflow","scroll")
$('.filter').css("max-height","50px")
})
as always could use some cleanup..

Related

dc.js table -- Select row(s), highlight onclick and apply filter for Crossfilter [duplicate]

I love the DC.JS library and have been trying to create a clickable aggregated table in DC.js with partial success. I want to highlight the rows on click event(multiple selections allowed) similar to the row chart or an ordinal bar chart in dc js. Like a row chart, when multiple selections are made, multiple table rows should be highlighted.
I am not able to select the row that I have clicked on, rather, my css selects the first row irrespective of which row I click. I tried to use 'this' keyword to select the current row that was clicked but to no avail.
Here's the js fiddle: https://jsfiddle.net/yashnigam/kvt9xnbs/83/
Here's my code for the click event that makes the css selection:
marketTable.on("renderlet", function(chart){
chart.selectAll('tr.dc-table-row').on('click',function(d){
if(filterKeys.includes(d.key)){
chart.select('tr.dc-table-row').datum(d.key).style('background-color','gray');
}
})
});
Kindly share a way to highlight rows of data table on click, the same way it works on a row chart.
#Hassan has the right idea. I would suggest selecting the trs rather than the tds, and instead of changing the classes on click (which wouldn't survive a redraw), apply the classes also during the pretransition event.
So:
tr.dc-table-row.sel-rows {
background-color: lightblue;
}
marketTable.on('pretransition', function (table) {
table.selectAll('td.dc-table-column')
.on('click', /* ... */)
table.selectAll('tr.dc-table-row')
.classed('sel-rows', d => filterKeys.indexOf(d.key) !== -1)
});
We apply the class based on whether the row's key is in the array. Straightforward and simple!
Fork of your fiddle.
using built-in filters
#vbernal pointed out that the list doesn't get reset when you click the reset link. To better integrate this, we can hook into the built-in filters that the table inherits from the base mixin (but doesn't ordinarily use):
marketTable.on('pretransition', function (table) {
table.selectAll('td.dc-table-column')
.on('click',function(d){
let filters = table.filters().slice();
if(filters.indexOf(d.key)===-1)
filters.push(d.key);
else
filters = filters.filter(k => k != d.key);
table.replaceFilter([filters]);
dc.redrawAll();
});
let filters = table.filters();
table.selectAll('tr.dc-table-row')
.classed('sel-rows', d => filters.indexOf(d.key) !== -1);
});
Instead of setting dimension.filter() ourselves, we fetch the existing table.filters(), toggle as needed, and then set the filters using
table.replaceFilter([filters])
(Note the extra brackets.)
When the reset link is clicked, we reset the filter on the table rather than the crossfilter dimension. (It's always better to manipulate filters through the chart, because charts are not able to read the selection state from the crossfilter dimension.)
$('#resetTable').on('click', function() {
marketTable.filter(null);
dc.redrawAll();
});
New version of fiddle.
In your onclick event, add (toggle) a class similar .sel-rows to clicked item (instead of change back color of it). Now in your css add this:
.sel-rows td{
background-color: gray;
}
Background color for table rows not work in some browsers.
As I said before, the changes that you (#Gordon) indicated worked, when I click the button, the table is redefined without any kind of color.
However, the problem was inversed, now the numbers remain the same.
I mixed it with the code you created and the solution I found was as follows:
marketTable.on('pretransition', function(table) {
table.selectAll('td.dc-table-column')
.on('click', function(d) {
let filters = table.filters().slice();
if (filters.indexOf(d.key) === -1)
filters.push(d.key);
else
filters = filters.filter(k => k != d.key);
if (filters.length === 0)
marketDim.filter(null);
else
marketDim.filterFunction(function(d) {
return filters.indexOf(d) !== -1;
})
table.replaceFilter([filters]);
dc.redrawAll();
});
let filters = table.filters();
table.selectAll('tr.dc-table-row')
.classed('sel-rows', d => filters.indexOf(d.key) !== -1);
});
$('#reset').on('click', function() {
marketTable.filter(null);
marketDim.filter(null)
vendorDim.filter(null)
CategoryDim.filter(null)
dc.redrawAll();
});
$('#resetTable').on('click', function() {
marketTable.filter(null);
marketDim.filter(null)
dc.redrawAll();
});
I don't know if it's the most elegant way to do this, I'm still a beginner in D3, DC and Crossfilter

How to add button inside dc.js table?

How do I add a button with event listener in dc.js data table in a new column.
I basically want to add custom html if possible.
Adding the button is simply a matter of returning the html for the column for
dc.dataTable (dom)
.columns ([function (d) { return "<button>Clickedy click</button>" }])
.on('renderlet', function (graph) {
graph.selectAll('button')
.on('click.clickedy',function(d){
console.log(d);
})
'renderlet' or 'pretransition' for the event, don't think it matters much in this case

Unfiltered data on/off

I am using composite charts to see the unfiltered data, but I want to hide sometimes the unfiltered data and make the 'y' axis elastic. Hiding the unfiltered data wasn't hard, just an event listener on chart, but I can't make possible the elasticity on 'y' axis, when the unfiltered data is hidden. Perhaps it's not even possible in a case like this. Any ideas?
chart.select('.unfiltered_data').on('change', function() {
if(!this.checked) {
console.log("Stop showing unfiltered data!")
chart.select('.sub._0')
.attr('visibility', 'hidden')
// chart.elasticY(true)
chart.redraw()
}
else {
console.log("Show unfiltered data!")
chart.select('.sub._0')
.attr('visibility', 'visible')
// chart.elasticY(false)
chart.redraw()
}
})
There is (almost) always a way to do it in dc.js, because dc.js is a leaky abstraction by design!
First I tried to change which child charts are included in each composite chart, but that wasn't the right approach because a composite chart's children can't be changed on a redraw, only on a render. And we want to animate when switching between showing the unfiltered and not showing.
So instead I thought we could
use your visibility idea
turn off elasticY when the unfiltered is hidden, and
use the filtered child chart's domain instead
So I added a checkbox
<label><input type="checkbox" id="unfiltered" name="unfiltered" checked> Show Unfiltered</label>
and a global variable
var show_unfiltered = true;
The handler looks like this:
function ydomain_from_child1(chart) {
chart.y().domain([0, chart.children()[1].yAxisMax()]);
chart.resizing(true);
}
d3.select('#unfiltered').on('change', function() {
show_unfiltered = this.checked;
charts.forEach(chart => {
chart.select('.sub._0').attr('visibility', show_unfiltered ? 'visible' : 'hidden');
chart.elasticY(show_unfiltered);
if(!show_unfiltered) {
ydomain_from_child1(chart);
chart.children()[1].colors(d3.schemeCategory10);
chart.on('preRedraw.hide-unfiltered', ydomain_from_child1);
}
else {
chart.children()[1].colors('red');
chart.on('preRedraw.hide-unfiltered', null);
}
})
dc.redrawAll();
});
Whenever the checkbox is toggled, we turn on or off elasticY based on the setting. When the unfiltered are not shown, we'll simulate elasticY with a preRedraw handler which determines the domain from the second (filtered) child chart.
Additionally, we turn on/off the red color scheme for the filtered chart based on the checkbox.
I have added this to the compare unfiltered example.
I found I had to make one more change: the filtered chart was hidden when there were no filters. So I had to disable this hiding if the unfiltered was unchecked:
var any_filters = !show_unfiltered || charts.some(chart => chart.filters().length);
chart.select('.sub._1')
.attr('visibility', any_filters ? 'visible' : 'hidden')

How handle style chart click event on nvd3

I want to remove stream style (state ? ("Stream", "Stacked" and "Expanded")) on stacked area chart and use this code :
d3.selectAll("g.nv-series")
.filter(function() {
return d3.select(this).select("text").text() == "Stream";
})
.remove();
But it works only the first time.
I tryed to handle events on chart because i want to refresh rendering of chart but it didn't work for the styles click. It works only for the legend click.
chart.legend.dispatch.on('legendClick', function(e){
console.log('legend was clicked', 'no namespace.');
});
How i can i handle click on style event ?
setter
chart.style('stream');
getter
chart.dispatch.on('stateChange', function(e) {
console.log(e); //e.style holds the current style
});
List of available styles can be found here https://github.com/novus/nvd3/blob/master/src/models/stackedArea.js#L299-L318
I also had hard time with this issue so I hope this would help you.
Cheers!

how to toggle a filter so that it selects only one item at a time?

I have 2 charts. A bar chart and a donut chart (with 3 categories ['A','B','C']).
When I click on a segment of the donut, then I toggle that element's membership in the filter.
e.g. If ['A','B','C'] are selected, and I click on 'A', then I am left with ['B','C']. If I click again on 'A', then I am returned to the full set.
Is it possible to change this behaviour, so that a click, 'de-selects' everything that is not 'A'. So when I click on 'A', I get 'A', and when I click on 'B', I get 'B' etc.
This example achieves this for the rowChart in line 174, but I neither understand the implementation, nor can I reproduce this in my own work.
The relevant code is pasted here:
rowChart.width(340)
.height(850)
.dimension(cityDimension)
.group(cityGroup)
.renderLabel(true)
.colors(["#a60000","#ff0000", "#ff4040","#ff7373","#67e667","#39e639","#00cc00"])
.colorDomain([0, 0])
.renderlet(function (chart) { // <-this bit onwards does the magic (I think)
bubbleChart.filter(chart.filter());
})
.on("filtered", function (chart) {
dc.events.trigger(function () {
bubbleChart.filter(chart.filter());
});
});
In d3 if you need to filter some data out of a bigger dataset you could do like so:
var filter = dataset.filter(function (d) { retrurn d.ValueToFilter == "FilterToApply"; });
For example, if you have a fruit dataset, and you just want orange, you do something like so:
var filter = dataset.filter(function (d) { return d.fruit == "orange"; })
This will return only values that have the "orange" property in d.fruit

Resources