Unfiltered data on/off - dc.js

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

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

Unable to reset the focus ordinal bar chart

I am trying to reset after choosing some of the individual's bar.
index.html: (line no. 62)
<span>
reset
</span>
This seems not to work. I was able to reset all the graphs pie chart, line chart, etc but not this one.
Those two ordinal graphs are created in index.js like this:
var focus = new dc.barChart('#focus');
var range = new dc.barChart('#range');
https://blockbuilder.org/ninjakx/483fd69328694c6b6125bb43b9f7f8a7
Update:
It looks weird now Coz it's showing a single bar and all the bar have got invisible but I want them to be visible (in gray colour) but not clickable.
This example replaces the built-in filtering functionality of the bar chart with its own implementation of ordinal selection, because the chart has a linear scale.
The example uses a global variable focusFilter to store the current selection. We need to empty this out and we also need to update the dimension filter as the original filterAll would do, pulling that code out of the click handler:
focus.applyFilter = function() { // non-standard method
if(focusFilter.length)
this.dimension().filterFunction(function(k) {
return focusFilter.includes(k);
});
else this.dimension().filter(null);
};
focus.filterAll = function() {
focusFilter = [];
this.applyFilter();
};
This will also allow dc.filterAll() to work, for a "reset all" link.
Fork of your block.
For some reason, I could not get the original
reset
links to work at all in this block, so I replaced them with the equivalent D3 click handlers:
d3.select('#reset-focus').on('click', () => {
focus.filterAll();
dc.redrawAll();
})
d3.select('#reset-all').on('click', () => {
dc.filterAll();
dc.redrawAll();
})
I also updated the focus ordinal bar example. Note that automatic hiding/showing of the reset link doesn't work because the chart still has an irrelevant range filter inside of it.

Vue-Chartjs onComplete custom labels - prevent blinking

To calculate custom labels for Vue-Chartjs the only solution I could find was via
animation: { onComplete: function () {
The problem that follows is that these labels are blinking on bar hover. I also saw the same behaviour in many other custom label examples/solutiond. Did anyone manage to solve this?
See example here fiddle
The blinking effect is caused because the animation is only triggered when the bars are hovered. You can use the onHover option to trigger whenever the chart canvas is hovered.
Here's an example logic:
(uses the plugin chartjs-plugin-datalabels to make it easier)
options : {
onHover : function (e) {
const display = e.type === 'mouseout' ? false : true
const labels = this.chart.options.plugins.datalabels
if (display&&labels.display) return //avoid updating if already set
labels.display = display
this.chart.update();
}
}
working example

dc.js - dynamically change valueAccessor of a stacked layer in a lineChart and redraw it

I am trying to realize a dashboard to display basic data.
I am actually completely stuck on an issue. Strangely enough, I couldn't find anything even similar to it online, so I don't have many leads on how to move forward.
I have mainly two charts:
a lineChart called "stackChart" that
displays consumption as a base layer with its valueAccessor function
dispalys production as a stacked layer with its value Accessor function
a barChart called "volumeChart" that is simply the rangeChart for the lineChart
I use radio buttons to select whether to aggregate the grouped data by sum or by average (using the same approach as this example) and then I just use:
stackChart.valueAccessor(/*function with new value (avg or sum)*/);
dc.redrawAll();
to refresh the base layer (consumption).
What I don't manage to do is to refresh the "stacked layer" by updating its valueAccessor! I can't find any way to access its valueAccessor (or, worst case, just completely remove the stacked layer and then add a new refreshed stacked layer using just ".stack(...)").
Here is the respective part of my code where the chart is built:
// Charts customization #js
stackChart
.renderArea(true)
.height(350)
.transitionDuration(1500)
.dimension(dateDim)
.group(powByTime, "Consumption")
// BASE LAYER valueAccessor HERE
.valueAccessor(function(d) { return d.value.conSum; })
.x(d3.time.scale().domain([minDate, maxDate]))
.xUnits(d3.time.days)
.elasticY(true)
.renderHorizontalGridLines(true)
.legend(dc.legend().x(80).y(0).itemHeight(13).gap(5))
.brushOn(false)
// STACKED LAYER HERE
.stack(powByTime, "Production", function(d) { return d.value.prodSum; })
.rangeChart(volumeChart)
.controlsUseVisibility(true)
;
And here is where I look for changes in the radio buttons and re-draw the layers:
// Listen for changes
d3.selectAll('#select-operation input')
.on('click', function() {
var aggrMode = this.value; // fetch "avg" or "sum" from buttons
// UPDATE BASE LAYER HERE:
stackChart.valueAccessor(function(d) { var sel = accessors[aggrMode]['consPow']; return d.value[sel]; });
// ???HOW TO UPDATE STACKED LAYER valueAccessor function???
//stackChart.stack.valueAccessor(function(d) { var sel = accessors[aggrMode]['prodPow']; return d.value[sel]; });
dc.redrawAll();
});
If you need more details on what I am trying to do and full code you can check here.
As a reference, here is what it looks like:
I don't really know dc.js, but it may be possible that you can't change an accessor once it's been set. Try writing a single function for your accessor that will return either the sum or the average, depending on the state of some variable that you can set.
#Ryan's solution will probably work fine (and may be a better design), but here's the lowdown on the dc.js API with respect to stacking, in case you need it.
As described in this issue the group and stack API is pretty weird. It grew organically, in a backward-compatible way, so both the stacks and the value accessors on top of the stacks sort of branch out in a beautiful fractal of... well, no it's pretty messy.
But the issue also suggests the solution for your problem. Since chart.group() resets the set of stacks, just go ahead and build them all from scratch in your event handler:
stackChart.group(powByTime, "Consumption") // this resets the stacks
.valueAccessor(function(d) { var sel = accessors[aggrMode]['consPow']; return d.value[sel]; })
.stack(powByTime, "Production", function(d) { var sel = accessors[aggrMode]['prodPow']; return d.value[sel]; });
Internally it's just emptying an array of layers/stacks and then populating it with some references.
This is quite efficient since dc.js doesn't store your data except where it is bound to the DOM elements. So it is the same amount of work to redraw using the old group and value accessor as it is to redraw using new ones.

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/

Resources