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

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

Related

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

Select Menu not updating when line charts are filtered

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..

Disable brush on range chart before selecting a scale from the dropdown/on page load(dc.js,d3.js)

Following my previous question Disable resize of brush on range chart from focus charts (dc.js, d3.js) - Solved and my previous fiddle,https://jsfiddle.net/dani2011/uzg48yk7/1/, still need to disable brush drawing on the range chart before selecting a scale from the dropdown and/or on page load (!isPostback):
a) When panning /translating the line of the focus charts (bitChart,bitChart2) the brush is displayed on the whole range of the range chart:
b) It is possible to drag the brush on the range chart
Tried to cancel the zoom event using event listeners as followed:
var anotherRoot = d3.select("div#bitrate-timeSlider-chart.dc-chart").select(".chart-body");
anotherRoot.on("mousedown", null)
anotherRoot.on("mousemove.zoom", null)
anotherRoot.on("dblclick", null)
anotherRoot.on("touchstart", null)
anotherRoot.on("wheel", null)
anotherRoot.on("mousewheel.zoom", null)
anotherRoot.on("MozMousePixelScroll.zoom", null)
Tried to use different SVG scopes instead of anotherRoot such as:
//option 1
var rootSvg = d3.select("#bitrate-timeSlider-chart svg brush")
//option 2
var brushSVG = d3.select("#bitrate-timeSlider-chart").select("g.brush").select("*");
//option 3
d3.select("#bitrate-timeSlider-chart").on("touchstart.zoom", null);
d3.select("#bitrate-timeSlider-chart").on("mouse.zoom",
null);
Tried to cancel the event listeners:
1) Directly in my js file
2) Within the range chart (timeSlider)
3) Within the range chart events such as .on(render...) , .on(postRedraw...)
4) Tried to remove the brush within .on(postRedraw...) and within (!isPostBack) using:
//JS file
function isPostBack() { //function to check if page is a postback-ed one
return document.getElementById('_ispostback').value == 'True';
}
//HTML file
....
</script>
<input type="hidden" id="_ispostback" value="<%=Page.IsPostBack.ToString()%>" />
</body>
</html>
d3.select("#bitrate-timeSlider-chart").selectAll("g.brush").selectAll("*").data(data[0]).exit().remove();
Any help would be appreciated.
Okay, the answer I provided to the previous question for fixing the brush size was broken by these lines:
document.getElementById("alert").style.display = "inline";
There's no #alert element, so it crashes every time. I've restored that to the way I wrote it and it's a little bit messy when you drag, but at least it locks the brush size.
As for the other parts, now we're (finally) getting into documented behavior. Yay!
It's not perfect, but you can enable the brush only when there is a scale selection. Just disable it at first:
timeSlider
.brushOn(false)
and then enable it with a render when a scale has been selected:
function addHours(amountHours) {
var showBrush = +amountHours !== 0;
if(timeSlider.brushOn() !== showBrush)
timeSlider.brushOn(showBrush)
.render();
The render is not great, we'd rather do a redraw, but apparently the chart will only look at .brushOn() on render. Something to look into in the future.
We can also disable the styles which make it look like it has a ordinal brush and wants to be clicked on, like this:
.dc-chart rect.bar {
cursor: default;
}
.dc-chart rect.bar:hover {
fill-opacity: 1;
}
As for preventing zoom on the focus charts, you just need to set .zoomScale():
bitChartGeneral
.zoomScale([1,1]);
This sets d3.zoom.scaleExtent, locking the zoom.
Here's the updated fiddle: https://jsfiddle.net/gordonwoodhull/dsfqeut8/5/

SlickGrid CSS styles wrong on filtered view

I have a SlickGrid with dataview working pretty good, grid and dataview are synced up for modify and delete selections using syncGridSelection, however an interesting problem occurs on the changed CSS styles. The changed rows CSS stlye are being applied to the same "visible" row number in the grid when I choose a filter set that does not include the actual changed rows. The sort works fine, but I noticed that the filter is not working. Does anyone have a fix for this? Can you paste as much info and code for me as possible because I'm new to SlickGrid. I pasted code that loads up the grid.
function LoadGridData() {
$.getJSON('#Url.Action("GetConfigurations")', function (rows) {
if (rows.length > 0) {
if (rows[0].id = 'undefined') {
$(rows).each(function (index) {
rows[index].newAttribute = "id"
rows[index]["id"] = index;
});
}
};
data = rows;
dataView.beginUpdate();
dataView.setItems(data);
dataView.setFilter(filter);
dataView.endUpdate();
// Refresh the data render
grid.invalidate();
grid.render();
grid.setSelectedRows([]);
dataView.syncGridSelection(grid, true);
});
}
After debugging I found that I had used an older example of marking css changed in function getItemMetadata. The correct code is below. Previously I was referencing data[row]. When Syncing DataView to Grid, the getItem() method returns the correct row. In this case my DataState is my own changed indicator on the view model.
dataView.getItemMetadata = function (row) {
var item = this.getItem(row);
if (item && item.DataState == 2) {
return {
"cssClasses":
"changed"
};
}

Resources