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.)
Related
Background
I have created a chart and implemented a click-to-zoom/mouseout-to-reset-zoom feature as follows.
chart.on('pretransition', (c) => {
// click to enable zooming
c.select('svg').on('click.enablemousezoomable', () => {
c.focus();
c.mouseZoomable(true).render();
});
chart.on('postRedraw', (c) => {
// mouseleave to disable zooming
c.select('svg').on('mouseleave.disablemousezoomable', () => {
c.focus();
c.mouseZoomable(false).render();
});
});
Problem
While the feature works as intended, the problem is that after redrawing, page scrolling is impossible if the cursor remains on the chart.
The cursor needs to be moved off of the chart in order to get page scrolling to work.
Question
What might be causing this problem and what is the solution?
Thanks!
I made a block from the dc.js stock example, which uses mouseZoomable in the Monthly Index Abs Move & Volume/500,000 Chart, and applied your changes.
Indeed, it disables zoom but it still leaves the wheel disabled after mouseZoomable has been disabled.
As dc.js#991 discusses, the way dc.js removes zoom is incorrect. According to the d3-zoom documentation, this should be correct:
moveChart._nullZoom = function(sel) {
sel.on('.zoom', null);
};
Indeed, I tried it in this fork of the block, and it seems to work much better. I think it's the correct fix for the linked issue, too.
There are some artifacts due to the redraws for adding and removing zoom, but I think they are out of scope for this question.
series.slices.template.events.on("over", function(ev) {
series.slices.template.tooltipHTML=showHtml(ev, series.name);
}, this);
Tooltip issue in amchart4: not reload on pie chart slice.
I am using custom tooltip like generating custom html on mouse hover. Everything is working but when I move cursor from one slice to another, the popup html is not refreshing. When I mouse out and then again mouse over the same slice, the popup html shows correct data.
I think the previous html data is cached somewhere. Please help me.
It could possibly help to see what showHtml is doing. But we'll make do without that.
There are a few problems with this code.
Code-wise, the most obvious is this line:
series.slices.template.tooltipHTML=showHtml(ev, series.name);
You're resetting the template's tooltipHTML instead of the actual slice's tooltipHTML.The template has applyOnClones as true by default so it will propagate to all the other slices, so if this worked it was by accident. If there's any slice-specific settings in showHtml that don't rely on data placeholders this will break tooltipHTML for the next slice that's hovered over.
The real issue, however, is the approach.
So long as tooltipHTML or tooltipText are set, a tooltip will appear on hover.
It's better to use an adapter for tooltipHTML in this case.
As a quick test to see which wins the race condition, the hover event or the tooltipText/HTML adapter, make a handler for each, and hover over a slice:
series.slices.template.events.on("over", function(){
console.log("hover");
});
// override tooltipText so tooltipHTML is actually used
series.slices.template.tooltipHTML = "something...";
series.slices.template.adapter.add("tooltipHTML", function(tooltipHTML) {
console.log("adapter");
return tooltipHTML;
});
// console:
// "adapter"
// "hover"
You'll find that the adapter triggers first, so by the time you hover, the tooltip is already on its way with its HTML and all.
So use an adapter, just be sure to adjust your showHtml function so its first argument takes target itself instead of event (it may not be necessary to have another argument with the tooltipHTML that's being modified because it will always be sent the original tooltipHTML unformatted string, so that could be referred to manually whether as a string or variable).
The adapter can look like this:
var originalTooltipHTML = "<strong>{country}: </strong>"
series.slices.template.tooltipHTML = originalTooltipHTML;
series.slices.template.adapter.add("tooltipHTML", function(tooltipHTML, target) {
// #2: If we had used an event, here you would work on the target itself, event.target, not the template.
// The showHtml might not even be needed, perhaps whatever it does can go in here instead.
return showHtml(target);
});
Here's a demo with all that thrown together:
https://codepen.io/team/amcharts/pen/a0122e572d27cf513a78384345cad3a6
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 have a dynamically filled GridPane, where I'm animating the first row and column, so if there is a lot of information, the user doesn't get lost, because the row and column jumps to the selected area.
(Animating: I created two GridPanes, one for the first row and the second one for the first column, then I move the labels into the new GridPanes and finally starts the animation.)
It looks like this:
The user can also select some cells and change the content (using KeyEvents).
But there is a problem, when I change the text of a label, the animated row and column jumps back to their first position, but I don't understand why. It happens only, when I change the text, the CSS style for example is not causing the issue.
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.