Is it possible to have a scrolling div that adjusts to the number of bars in a dc.js rowchart - d3.js

I have several rowcharts that are connected to each other with dc.js.
These rowcharts have the x axis at the top in a different div, as explained here. However these rowcharts also implement filtering and removing, therefore, whenever I filter in one rowchart, the number of bars in the others reduce, but it maintains the size of the scrollable div, even though there are no bars below what is shown. Also, I'm pretty sure it is easy, but I haven't figured out how to put the reset button below the chart, because it shows between the chart div and the axis div, as seen below.
Is there a way to correct these issues?
This is what I have in each rowchart div:
<div id='axis'></div>
<div id="chart" style="overflow-y:auto; height:200px;">
<div>
<span class="reset" style="display: none;">Phylum seleccionado(s):
<span class="filter"></span>
<a class="reset" href="javascript:Chart.filterAll();dc.redrawAll();" style="display">Reset</a>
</span>
</div>
</div>
And this is what a I have in each rowchart in the main.js:
Chart
.fixedBarHeight(20)
.height(nonEmpty.all().length * 20 + 1)
.margins({top: 0, right: 20, bottom: 0, left: 20})
.width(600)
.xAxis(d3.axisTop())
.elasticX(true)
.ordinalColors(['#e41a1c'])
.gap(1)
.dimension(Dim)
.group(nonEmpty) //this removes the ones that don't match the filter of the other rowchart
.on('pretransition', function () {
Chart.select('g.axis').attr('transform', 'translate(0,0)');
Chart.selectAll('line.grid-line').attr('y2', Chart.effectiveHeight());
});

Partial answer here. Well, it answers what you asked but doesn't answer the next question I expect. :(
You are currently sizing the chart based on the number of bars, and you will need to resize the chart when the number of bars change.
This should be done in a preRedraw handler, but unfortunately preRedraw is currently fired after resizing is done. So currently you have to override .redraw():
dc.override(chart, 'redraw', function() {
chart.height(chart.group().all().length * 21);
return chart._redraw();
})
As for putting the info & controls after the chart, there are two issues and I was only able to solve one of them.
dc.js will append an svg element to the chart div. It'd be nice to have more control over this but I'm not sure what to do. For the moment, the easiest workaround is to re-append the info div:
chart.on('postRender', function() {
chart.root().node().appendChild(chart.select('div.info').node())
})
However, what you really want is probably to pull the info div out of the scrolling div, and I can't figure that out at the moment. The problem is that the chart expects the controls/info to be inside the chart div, and that's the same one that needs to scroll.
I tried to mess around with adding another scrolling div inside the chart div, and then moving the svg under it. This works, but it creates some annoying flashing.
Here's what I got so far. I may return to this later if I think of a better solution.
Update: controls outside of the scroller
To solve this right, we need the controls outside of the chart div.
We can port baseMixin.turnOnControls to a filtered event handler, and do the same showing and populating that the chart would do:
function turnOnControls(_chart, controls) {
var attribute = _chart.controlsUseVisibility() ? 'visibility' : 'display';
controls.selectAll('.reset').style(attribute, null);
controls.selectAll('.filter').text(dc.printers.filters(_chart.filters())).style(attribute, null);
}
function turnOffControls(_chart, controls) {
var attribute = _chart.controlsUseVisibility() ? 'visibility' : 'display';
var value = _chart.controlsUseVisibility() ? 'hidden' : 'none';
controls.selectAll('.reset').style(attribute, value);
controls.selectAll('.filter').style(attribute, value).text(_chart.filter());
}
function filter_function(controls) {
return chart => {
chart.hasFilter() ?
turnOnControls(chart, controls) :
turnOffControls(chart, controls);
}
}
chart.on('filtered', filter_function(d3.select('#info')));
Also, controlsUseVisibility and visibility: hidden is better when the controls will affect layout when shown/hidden.
(fiddle)

Related

Showing DC bar chart only after on renderlet function is called, and not on render.

I am modifying the bar size and position using attr.
However the chart attributes are only available after the chart gets rendered.
So I am doing the modification on the fuction
chart.on("renderlet.somename", function (chart) {// modification });
My problem is this looks odd, as the chart gets rendered first then the modifications are applied and it all appears on the page.
I want that the chart should only be visible after the modifications has been applied.
I started to write that you could use the pretransition event, since this fires after everything has been rendered/redrawn, before transitions start.
But you are correct that the bar width is not publicly accessible (it should be!) and you can't read it from the bars until they have transitioned.
So, as you suggested, you could instead hide the whole chart using CSS:
<div id="test" style="visibility: hidden"></div>
And then show it at the start of your renderlet:
chart.on('renderlet', function(chart) {
d3.select('div#test').style('visibility', 'visible');
To eliminate the pause, you could also disable transitions for this chart when you initialize it;
chart
.transitionDuration(0)
And re-enable them in your renderlet:
chart
.on('renderlet', function(chart) {
d3.select('div#test').style('visibility', 'visible');
chart.transitionDuration(750); // default value
Here's a demo, using a fiddle demonstrating error bars (which also need the bar width): http://jsfiddle.net/gordonwoodhull/cw86goxy/32/

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/

Kendo Window positioning

I am using bootstrap template and Kendo Window and so far positioning of modal kendo windows wasn't too hard.
But now as I a use a different layout for a certain area, I find myself having problems with that matter.
following code is expected to create a centered (x-axis) modal kendo window:
#(Html.Kendo().Window()
.Name("Window1")
.Visible(false)
.Position(builder => builder.Top(100))
.Draggable()
.Content(#<div class="kendoWindowContent"><p>Please wait...</p><div class="k-loading-image"></div></div>)
.Width(1000)
.Title("Title1")
.Actions(actions => actions.Close())
.Modal(true)
.Resizable())
..and displaying via:
var wnd = $("#ownerVoucherCreateWindow").data("kendoWindow");
wnd.refresh({
url: '#Url.Action("Voucher_Create", "OwnerVoucher")'
});
wnd.open();
The window is not beeing displayed in the middle of the x axis.
Are there any constraints in order to have the kendo window beeing centered.
Window centering requires the usage of the center() method. Since the Window content is loaded via Ajax, you need to center the widget in its refresh event.
var wnd = $("#ownerVoucherCreateWindow").data("kendoWindow");
wnd.one("refresh", function(e) {
e.sender.center();
});
wnd.refresh({
url: '#Url.Action("Voucher_Create", "OwnerVoucher")'
});
wnd.open();
It is also possible to trigger centering in every refresh event, instead of just once.
Another option is to set explicit width and height. In this case you can center the Window at any time, because the widget dimensions will not change after changing (loading) the content.
ok I guess I was just lucky that all my kendo windows happened to be displayed centered although specifying an explicit offset to top like described.
I assumed, that the window would automatically center on y-axis when having only an x-axis position set.
As it seems this is not the case. I don't really know why this has been working in the past.
Anyway, I figured out a way to center the window depending on the browsers' viewport and window width:
just in case anybodes cares...
function displayWindowCenteredOnYAxis(kendoWindow) {
var windowOptions = kendoWindow.options;
var pos = kendoWindow.wrapper.position();
var viewPortWidth = $(window).width();
var wndWidth = windowOptions.width;
pos.left = viewPortWidth / 2 - wndWidth/2;
kendoWindow.wrapper.css({
left: pos.left
});
kendoWindow.open();
}
Usage:
var wnd = $("#id").data("kendoWindow");
wnd.refresh({
url: '#Url.Action("Action", "Controller")'
});
displayWindowCenteredOnYAxis(wnd);

nvd3 chart error when display:none

I am currently using nvd3 for charting in my application. I have a problem in that if the div is hidden via display:none before the charts are rendered, the charts will throw an error, and upon "un-hiding" the div, I have to click on the charts to get them to render correctly. Is there any way to pre-render the charts even if the div is hidden? I have tried setting the width and height of the parent svg before calling the chart, but to no avail.
nv.addGraph(function () {
//chart setup code
d3.select("#chart svg").attr("width", 300).attr("height", 500);
d3.select("#chart svg").datum(data).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
I figured out how to make a previously hidden chart render properly without needing to statically define the dimensions of the chart area:
NVD3 Charts not rendering correctly in hidden tab
This solution also depends on using JS to display the hidden content and at the same time trigger a resize event which forces NVD3 to resize the now visible chart to fill parent. In my case I didn't care about SEO so I used display:none; but visibility:hidden; would work too.
Just add this JavaScript:
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
window.dispatchEvent(new Event('resize'));
})
hidden.bs.tab is the event that fires after a new tab is shown as per the Bootstrap docs. This code fires a resize event after each tab change.
You can hide a chart – but still render the graph – using a class like this:
.out-of-sight-and-space{
visibility: hidden !important;
position: absolute !important;
top: 0 !important;
}
You should apply this to the parent of the svg, in your case #chart. When you want to show the chart, remove the class.

Randomly placed draggable divs - organize/sort function?

Currently I have a page which on load scatters draggable divs randomly over a page using math.random
Using media queries however the page uses packery to display the same images for browser widths under 769px in a grided fashion.
I had the idea that it could be interesting to create a 'sort/organize' button which would rearrange these divs using packery and remove the draggable class already applied, however i have no idea if this is possible or how to go about it. If there is any method of animating this process that would also be a bonus!
If anyone could at the very least point me in the right direction i would be extremely thankful!!
Hopefully this gives you a bit of a starting point.
I would read up on JQuery as it has some useful helpers for DOM manipulation.
I don't think this is the most efficient way to do it, and I think you will need to rethink your test harness for doing this in the future, but hopefully this gets you started.
Firstly I've added a button to trigger the sort
<div class="rotate" id="contact">Contact</div>
<div id="logo">Andrew Ireland</div>
<button id="sort">sort</button>
Then updated the script to override the css setting to switch between draggable view and item view.
// general wait for jquery syntax
$(function(){
// trigger the layour to sort get the packery container
var container = document.querySelector('#container.packery');
var pckry = new Packery( container );
//button function
$("#sort").click(function(){
//Hide all the dragged divs
//ui-helper-hidden is a jquery ui hider class
if($('.box').css('display') == 'block') {
$('.box').css({'display':'none'});
//Show all the item class's
$('.item').css({'display':'block'});
//show the container
$('#container').css({'display':'block'});
// trigger the layour to sort
pckry.layout();
} else {
//hide all the item class's
$('.item').css({'display':'none'});
//hide the container
$('#container').css({'display':'none'});
//show the draggable box's
$('.box').css({'display':'block'});
}
});
$( ".pstn" ).draggable({ scroll: false });
$(".pstn").each(function(i,el){
var tLeft = Math.floor(Math.random()*1000),
tTop = Math.floor(Math.random()*1000);
$(el).css({position:'absolute', left: tLeft, top: tTop});
});
});
As I said this is more to get started. The packery documentation details how to trigger its layout functions so another approach would be to only have the draggable elements, and put these inside a packery container. Then when you want to sort them you can just trigger that the packery.layout() function.
I hope this is helpful, I am only just getting started on stack overflow so any feedback would be appreciated.

Resources