I'm new to D3 JS.
I spent some time to learn D3 js and build a sort of time table (I'm not sure this is the term) and to animate it.
Now I found a strange behavior on exit transition, when I try to remove the last element in a row the animation goes perfectly but when the element is not the last one, D3 removes it without animation.
It's hard to understand my problem, it's really easier to watch it! :)
I created a working fiddle here: http://jsfiddle.net/fLBq4/5/
Click 'Draw' to build the graph (data are loaded from an external js that create the var demodata)
Then click the second button and watch the last element of Sunday. The transition works correctly.
Then click the last button. You'll see that the first element of Friday is removed without the transition!
Now... I'm really surprised because the code for removing elements it's the same for both executions:
frames.exit()
.transition()
.duration(500)
.attr('width',0)
.remove();
Moreover, I implemented the listeners for 'start' and 'end' transition events (you will not find them in the fiddle). These events are fired correctly, i.e. the 'end' event is timed correctly 500 msec after start.
Why won't D3 animate all the elements in the same way, in my case?
What's happening is that you're getting caught out by D3's data matching. The bar that disappears suddenly isn't in the exit selection, it's in the update selection. The second bar is in the exit selection and disappears gradually, but the first bar is moved to its position instantaneously, so you can't actually see that. I've added a transition to the update selection so you can see what's happening here.
The reason that this is happening is that D3 matches data and DOM elements by index by default. That is, the first data element corresponds to the first DOM element in the selection and so on. In your particular case, you're removing an element from the array, so the last DOM element ends up being not matched and becomes part of the exit selection. The other elements however change their position (as new data is matched to them).
To fix, simply provide a function that tells D3 how to match data and DOM elements, e.g.
var frames = groups.selectAll('.frame')
.data(function(d){return d;}, function(d) { return d.start; });
Complete demo here.
Related
I am trying to figure out how to have custom brush handles highlight data in a range equal to a single value, which seems to result in a null selection.
Here the Plunkr I recreated on one of Mike Bostock's examples. It's in Angular 2/ionic, so if that's an issue, please go here to view his example's plain javascript. The primary thing I edited was commenting out where he hides the custom handles with css when there is a null selection.
if (s == null) {
//handle.attr("display", "none");
//circle.classed("active", false);
}
http://plnkr.co/edit/tRyhlJ
https://bl.ocks.org/mbostock/4349545
If you look in the plunkr example, you can see that if you click and let go the brush is still a perfect sphere, with the line where you clicked. Instead if disappearing, I want the brush handles to stay... and not cause errors when you click to expand the handles.
The error I am currently getting, and can't seem to find a workaround, is this:
How do I gracefully ignore this internal d3 error and continue on letting my selection expand?
I've tried all sorts of things like turning pointer-events off when it's in this state, and manually unhiding/resizing the default handles, to no avail. Every time, when I click the handles I get this error.
As far as I can tell, there's no "clean" way to deal with it. The reason is that a single click defines a range whose size is 0, which the brush considers an empty range (see source) and so it purposely nullifies the selection (see source).
That all means that unless you create your own version of d3-brush to do what you want, there's no way to have an empty selection that's not null nor a way to render a brush for an empty selection.
There's one workaround I can think of: when you detect an empty selection (where s == null) use brush.move to set the selection to something. That something would have to be a range whose size is not 0 (because if you make it 0 then d3, again, would consider that an empty selection and nullify it). To make a non-zero selection that looks like a zero-sized selection (which it has to be, because it's being defined by a single click event) you'd have to make it a tiny selection, eg [123, 123.0001]. Instead of 123, you need to know the mouse position of the click, which you can get using d3.mouse. Putting it all together, it looks like this:
if (s == null) {
var mousex = d3.mouse(this)[0]
gBrush.call(brush.move, [mousex, mousex+.001]);
}
I've got a dc.js rowChart (using the current stable release). It's beautiful. I need it to be more touch-friendly though.
In particular, rows with small values have very tiny bars and are very hard to tap. What I'd like to do it make it such that the user can click anywhere on a row to select/deselect that row rather than having to click directly on the bar.
Is this possible?
As a very hacky solution, I was able to add a bunch of non-breaking spaces after the label and get this effect:
.label(function (d) { return /* your label here */ + new Array(100).join('\xa0'); })
(An invisible element would be better.)
I need to implement a behavior:
when element clicked - one thing happens
but when it's clicked and held for more than one second, something else happens (e.g element becomes draggable) and then the first event never fires
I think I know how to catch click&hold type of events, but how to distinguish between first and second?
Can you show me how to do that using this jsbin. I already made the "click, hold & drag" part, except that it is still firing the 'click' event after dragging the element and it shouldn't.
again: element clicked - one event, click and hold - element is draggable (even after mouse up) and when clicked again it's back to normal (undraggable) state.
I am not looking for a trivial solution, it has to be built using Rx.Observable or at least Bacon's streamEvent object
Thank you
I think you were pretty close with your solution, but probably it is not possible to elegantly achieve what you want while using the browser's built-in click event.
HERE is my attempt to tackle your problem.
The main idea is to define your own click streams like so:
var clicks = downs.flatMapLatest(function(){
return ups.takeUntil(Rx.Observable.timer(250));
});
var longDownsStart = downs.flatMapLatest(function(){
return Rx.Observable.timer(1000).takeUntil(ups);
});
In case of clicks we wait max 250 ms after a mouse down for a mouse-up; in case of the latter we generate the event only if there was no mouse-up within 1000 ms.
There might be some corner cases in which the code does not work as intended.
Here is my proposed solution (with Bacon.js).
I have a stacked bar chart which has time on the x-axis and a count on the y-axis. I have most of the transitions working but there is a scenario where I'm unsure what to do.
If the new data is part of the update selection then I know to grow the bars (as they already exist).
If, if however, the data is part of the enter selection then, depending on where the data is entering, there are two possible transitions:
If the data is newer than the current "end time" then it should slide in from the right, as per this well known tutorial.
If the data is new but older than the current "end time" then a new category should be formed.
The analogous situation occurs in the exit selection.
I am already taking care of #2 above but am having trouble implementing #1. Especially at the same time as #2. It seem to me that I need to inspect the new datum and see where it falls on the chart, probably using the xScale.
How does one run different transitions on the same selection (enter or exit, in this case)?
Here's some pseudo code for what I could imagine for the exit selection:
rects.exit(function () {
if (d.timestamp < that.chartStartTime) {
transitionOffLeftEdge();
} else {
transitionOffBottom();
}
)};
Without getting into unneeded detail, I'll try to explain the basic issue I'm having. I'm evaluating Kendo-UI Web and working with the Grid.
I do some page column re-sizing based upon the grid having detail rows expanded or not.
The behavior that I'm noticing is that the VERY first time "detailExpand" event is triggered per row, you can see the DOM being updated with the new elements BEFORE my code in detailExpand runs. I'm guessing that this is actually just detailInit running since it's the first time the row is expanded. After that however, if I expand the row again, detailExpand event runs first, before the row shows up. (The code appears as though it's simply doing a .show().. This means that I can't properly calculate the change in height.
Same with detailCollapse. The event code fires before the detail row is hidden. So I end up with extra space equal to the size of the detail row. If they were always the same size, this wouldn't be a problem, I could just anticipate the size and reduce or grow based on that. That is not the case.
So to boil it down, if I could simply have an AfterRowExpanded Event, it would solve everything.
I feel I can customize the kendo ui JS easy enough, but then I have to re-minimize the JS and all that.. Any thoughts?
With the latest version to me it seems that the detailExpand is triggered when the detail row has finished expanding, however the detailCollapse is executed before the detail row is hidden.
As a work-around I would suggest you to slow down the execution of your logic with the help of setTimeout without specifying miliseconds (it will be executed just after the row is actually hidden / shown)
e.g.
$('#grid').data().kendoGrid.bind('detailCollapse',function(e){
setTimeout(function(){
//code goes here
})
})