How to override the dragging events in C3.js - javascript

I am currently trying to implement a zooming feature that is slightly different from the one that already exists.
Actually, I would like that if a user clicks and drags on the graph it zooms on the so defined domain. I would like to do it that way because with the mouse wheel it prevents the user from the page up/down.
As it doesn't seem to be possible with the C3.js API, I tried to implement the drag event by following this little walkthrough on D3.js Drag Behaviour.
However, I didn't understand it well as it is not working when I try it on the graph.
Here's a sample of my code :
function setHandlers() {
/**
* A custom drag event to zoom on the graph
*/
var drag = d3.behavior.drag();
d3.selectAll('.c3-event-rects').on(".drag", null);
drag
.on('dragstart', function (d) {
d3.event.sourceEvent.stopPropagation();
console.log("start");
console.log(d)
})
.on('drag', function (d) {
console.log("on bouge :)")
})
.on('dragend', function (d) {
console.log("end");
console.log(d)
})
}
I call this function whenever I refresh my graph and I have already coded a custom handler for a double click (in the same function but I have it off to be more clear). I would like to know how to successfully trigger a drag event in a C3.js graph, especially the dragstart and dragend events?

c3-event-rects marks the layer that drives the events (tooltips, clicks, etc.). You don't usually want to move this unless you want the event reactions to be offset (like you get the bar 1 tooltips when you hover over someplace else than over bar 1)
Also, the layer has it's opacity set to 0, so you can't actually see it move unless you override it's opacity. Something like
.c3-event-rects {
fill: red;
fill-opacity: 0.2 !important;
}
That said, here is how you do this
var drag = d3.behavior.drag()
.origin(function () {
var t = d3.select(this);
var translate = d3.transform(t.attr('transform')).translate;
return { x: translate[0], y: translate[1]};
})
.on("drag", function () {
d3.select(this)
.attr("transform", "translate(" + d3.event.x + " " + d3.event.y + ")")
})
d3.selectAll('.c3-event-rects').call(drag);
Fiddle - http://jsfiddle.net/d5bhwwLh/
Here's how it looks - the red spot is where I had my mouse over to make the tooltip appear for the first set of bars

Related

How to have an entity trigger a different entities mouseover event (D3)

I currently am working on a GeoJSON of the United States with a mouseover event on every state (for example, one thing that occurs is that it changes the border of the states to bright red to show which one is being highlighted).
Some states are fairly small, especially in New England. I was trying to think of a way to get around this and decided to implement additional buttons on the side that would have the same mouseover event tied to specific states that I have deemed were small.
My question is: how would I go about getting something like highlighting the states borders to happen when mousing over the additional buttons.
My current implementation of the original states themselves is below (I haven't begun work on the additional buttons):
.on("mouseover", function(d) {
d3.select(event.target).attr("stroke", "red").attr("stroke-width", "3");
displayData(d);
});
.on("mouseout", function(d) {
d3.select(event.target).attr("stroke", "black").attr("stroke-width", "1");
hideData(d);
});
displayData(d) and hideData(d) are used for a tooltip's display. As you can see, the way the mouseover works right now is by capturing the event.target. How would I replicate this for a separate button? Is it possible to somehow tie that button's event.target to the corresponding state?
Just use selection.dispatch, which:
Dispatches a custom event of the specified type to each selected element, in order.
Here is a basic example, hovering over the circles will call a given function. Click on the button to dispatch a "mouseover" to the second circle.
const svg = d3.select("svg");
const circles = svg.selectAll(null)
.data(["foo", "bar", "baz"])
.enter()
.append("circle")
.attr("id", (_, i) => `id${i}`)
.attr("cy", 70)
.attr("cx", (_, i) => 30 + 100 * i)
.attr("r", 30)
.style("fill", "teal")
.on("mouseover", (event, d) => {
console.log(`My name is ${d}`);
});
d3.select("button").on("click", () => d3.select("#id1").dispatch("mouseover"));
.as-console-wrapper { max-height: 15% !important;}
<script src="https://d3js.org/d3.v7.min.js"></script>
<button>Click me</button>
<svg></svg>

d3 drag behavior: registering, listening, and triggering distinct types in the drag namespace?

Typing d3.behavior.drag() has me confused. I'd like to specify drag types so that different objects trigger different drag events. I'm used to programming with D3 dispatches/namespaces doing the following:
registering them with the D3 dispatch object
then using something like .on('click.typeHere') to listen for the dispatch event
and using dispatch.typeHere(<<data>>) to trigger the event.
I can't figure out how the normal dispatch procedure applies to drag behavior. For example:
var svg = d3.select('svg').append('g')
.attr('transform','translate(0,20)');
svg.append('rect')
.attr('height', 50)
.attr('width', 20)
.style('fill', 'steelblue');
var drag = d3.behavior.drag();
drag.on('drag', function() {
console.log('drag');
}).on('drag.type', function() {
console.log('namespace active');
}).on('drag.type2', function() {
console.log('namespace2 active');
});
svg.append('circle')
.attr('cx',20)
.attr('cy',0)
.attr('r',10)
.style('fill', 'orange')
.call(drag);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<svg></svg>
I want the circle to trigger just drag.type when it is dragged and not the other drags that are registered in the drag behavior namespace. But .call(drag.type) isn't supported. What is the right way to go about this?
I would use different drag behaviours for the different types of objects with the respective drag handlers doing the appropriate thing. This will save you having to mess around with event namespaces:
var drag1 = d3.behavior.drag()
.on('drag', function() {
console.log('drag rect');
});
svg.append('rect').call(drag1);
var drag = d3.behavior.drag()
.on('drag', function() {
console.log('drag circle');
});
svg.append('circle').call(drag);
Complete demo here.

Using d3 drag, why doesn't dragstart have d3.event.x set?

I can access d3.event.x from inside on("drag"...) but x doesn't exist inside on("dragstart"...); why? And how can I get it another way, preferably not very hackish?
Check console output for this example:
d3.select("body").append("svg").append("rect")
.attr("width", 100)
.attr("height", 100)
.style("color", "black")
.call(
d3.behavior.drag()
.on("dragstart", function(){
console.log(d3.event);
})
.on("drag", function(){
console.log(d3.event);
})
.on("dragend", function(){
console.log(d3.event);
})
);
JSFiddle
#Lars explains why, so how do you do it? d3.mouse is pretty slick:
d3.behavior.drag()
.on("dragstart", function(){
console.log('relative to rect: ' + d3.mouse(this));
console.log('relative to body: ' + d3.mouse(d3.select("body").node()));
})
Updated example.
This is the intended behaviour (see the documentation):
Drag events (but not dragstart and dragend events) expose "x" and "y" properties representing the current position of the drag gesture in local coordinates.
The start and end events are fired before and after the actual drag takes place, i.e. there's no coordinate change compared to the first and last drag events.

Adding Object with Transition in D3.js after Zooming

In the d3.js example, the goal is to change the X & Y scales to reveal a newly added circle which is added offscreen.
Currently, if you were to change the zoom scale, then click on the button to add a new circle, the zoom level suddenly resets to 1. Now dragging the view will cause the zoom scale to become correct again, which is the same zoom scale right before adding the circle.
However this problem goes away if you change redrawWithTransition() on line 123 to redraw() which removes the transitions.
Everything works fine if you add the circle without first zooming.
Why do we have to drag the view again to get the correct zoom scale back? How can we avoid having to do this additional drag and still use transitions in redrawWithTransition()? Thank you!!
Jsfiddle: http://jsfiddle.net/NgWmU/
Because panning again after adding the circle solves the problem, I tried calling redraw() at the end but theres no difference! Is the panning/zooming triggering something else in addition to redraw()?
Jsfiddle: http://jsfiddle.net/NgWmU/2/
First the user zooms/pans around and ends up with a view like in figure A. Next the new circle is added and we should get figure C, but instead we get figure B where the user's current translate/zoom is changed significantly.
Another case
If the user pan/zoom to a view similar to the top figure, adding the new circle should reposition the existing circles slightly, but instead the entire view is reset.
Was thinking about something like:
// Copy existing domains
var xMinOld = xMin,
xMaxOld = xMax,
yMinOld = yMin,
yMaxOld = yMax;
// Update domains
var yMin = d3.min(data.map( function(d) { return d; } )),
yMax = d3.max(data.map( function(d) { return d; } )),
xMin = d3.min(data.map( function(d) { return d; } )),
xMax = d3.max(data.map( function(d) { return d; } ));
if(x.domain()[0] > newData[0] || x.domain()[1] < newData[0]) {
if(x.domain()[0] > newData[0]) {
x.domain([xMin, xMaxOld]);
} else {
x.domain([xMinOld, xMax]);
}
zoom.x(x);
}
if(y.domain()[0] > newData[0] || y.domain()[1] < newData[0]) {
if(y.domain()[0] > newData[0]) {
y.domain([yMin, yMaxOld]);
} else {
y.domain([yMinOld, yMax]);
}
zoom.y(y);
}
But eventually the view will still be reset because we pass the updated scales x and y into zoom...

Making charts clickable

I am just learning and trying to code it at the same time, this is the code I have, If I take out the drill method and take out the .click(drill) then everything works so far, it draws some silly bar charts from the data I am sending to it
$( document ).ready(function() {
var dataset = gon.data;
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.click(drill)
.style("height", function(d) {
return d.brand_name + "px";
});
function drill (event) {
var target = event.currentTarget;
var data = $(target).data();
console.log(data);
}
});
But I am not able to add "click" event to those bar charts such that when I click on them I can know which chart I clicked on. The code above is result of my unsuccessful attempt to add "click" event to the charts I have drawn .... What is the correct way?
You can use .on("click", drill) if you want D3 to pass the object and data attached.
function drill(d, i) {
console.log(d); //data object
console.log(i); //array position
console.log(this); //DOM element
}
You can also use .attr("onclick", "drill()") syntax if you want to follow the standard HTML without the D3 wrapper.

Categories

Resources