Get click event from SVG bounding box using d3js - javascript

I am trying to add a mouse click event listener to click events on the bounding box of an SVG element (in this case all elements of the class "measure").
What I thought should work is this:
var vrvToolkit = new verovio.toolkit(); // www.verovio.org
jQuery.ajax({
url: "http://www.verovio.org/examples/downloads/Schubert_Lindenbaum.mei",
success: function (data) {
var svg = vrvToolkit.renderData(data + "\n", "");
jQuery("#svg_output").html(svg);
},
async: false
});
d3.select("#svg_output svg").selectAll(".measure")
.on("click", function () {
jQuery("#log").text(d3.mouse(this));
});
see JSFiddle
Unfortunately only mouse clicks on actually drawn elements are registered, the white space is ignored.
Is there a way to recognize mouse clicks inside a specific bounding box?

You cannot detect clicks on none existing elements in d3.
If you want to detect your bounding box, you need to create a new element with the same shape (for example: <polygon>), make it invisible and add it to the group you want to listen to.
Usually people create a big transparent rectangle.

All you need to do is add the click handler on the entire SVG and not specific elements:
d3.select("#svg_output svg")
.on("click", function () {
jQuery("#log").text(d3.mouse(this));
});
Complete demo here. To get the element that is being clicked on, you can use d3.event.target (https://jsfiddle.net/78hLhugh/3/).

Related

Pixi js - need to make clickable background

I need to be able to click on background, and get the position of the click.
I tried adding an event listener to stage like this
app.stage.interactive = true;
app.stage.on('click', function(){
console.log('hello');
})
but it works only if i click on element inside the stage, not the background itself.
Do i need to make a sprite as a background, if so, how do i set its background color and make sure it stays under all the other elements?
The stage is a PIXI.Container, which means it's basically an empty node that can hold children. It doesn't have dimensions of its own, and so when the interaction manager goes to hit-test your click, it isn't detected.
Your suggestion of adding a background sprite is probably the simplest solution. You can add one like so:
// Create the background sprite with a basic white texture
let bg = new PIXI.Sprite(PIXI.Texture.WHITE);
// Set it to fill the screen
bg.width = app.screen.width;
bg.height = app.screen.height;
// Tint it to whatever color you want, here red
bg.tint = 0xff0000;
// Add a click handler
bg.interactive = true;
bg.on('click', function(){
console.log('hello');
});
// Add it to the stage as the first object
app.stage.addChild(bg);
// Now add anything else you want on your stage
...
PixiJS renders objects in order, so if you add your background sprite as the first child of the app's stage, it will be rendered behind all other content. When hit-testing a click, it will be the last object tested, and so will catch a click on the background of the stage. Note that to "block" clicks, other objects will need to be set to interactive = true, even if they don't have a click handler attached!
A possible solution would be to add an event on app's view.
app.renderer.view.addEventListener('click', function(e) {
console.log(e);
});

d3.js: click equivalent of mouseout

What I mean by a click equivalent of mouseout is that I'd like a way to click on an element to change some attribute then have it change back when I click on anything but that element. Toggling this attribute change with hovering is easy because you change things based on mouseover and mouseout, but I'm unsure of how to do the same based on the click event.
So what I have is an svg element with circles on it, which display a red outline when they're clicked on. I know enough to be able to make only one circle appear selected at a time, but I don't know how to deselect all nodes when I click on a part of the svg that isn't a circle. If this isn't clear enough, I can create a jsfiddle to demonstrate what I have so far.
I have a working knowledge of selections from reading many examples, but can't seem to figure out what approach I should take to achieve this.
You could use d3.dispatch to set up some custom event handling. Sometimes separating out distinct behaviors from the rest of your layout code helps to keep things organized.
You might want one function to unhighlight all of the clickable circles, and another to toggle a single circle. Then when the svg is clicked, you can decide whether to unhighlight all based on whether a circle was clicked or not.
In other words...
When a circle is clicked, toggle it.
When the svg document is clicked, and the click is not on a circle, unhighlight all circles.
Then you can set up separate dispatch events for the two processes. This is nice because then these become reusable behaviors. If for example, you later want to add a button to unhighlight all circles, or want to highlight a circle when it's moused over, you can call the same dispatch functions.
var dispatch = d3.dispatch('unhighlightAll','toggleSingle')
// remove the `highlighted` class on all circles
.on('unhighlightAll', function() {
d3.selectAll('.clickable-circle').classed('highlighted', false);
})
// toggle the `highlighted` class on element `el`
.on('toggleSingle', function(el) {
d3.select(el).classed('highlighted', function() {
return !d3.select(el).classed('highlighted');
});
});
Finally, you call the dispatch functions from your click handlers:
svg.on('click', function() {
// do nothing if a clickable circle is clicked
if (d3.select(d3.event.target).classed('clickable-circle')) {
return;
} else {
// otherwise unhighlight all circles
dispatch.unhighlightAll();
}
});
circles.on('click', function() {
dispatch.toggleSingle(this);
});
Then all that's left is to decide how to display the highlighted class, and handle that in your css.
Here's a demo JSBin
--EDIT--
I just realized that since you're trying to mimic mouseout, you probably don't want multi-select. You'd just need to change the toggleSingle function a bit:
dispatch.on('toggleSingle', function(el) {
// store state of current element
var highlighted = d3.select(el).classed('highlighted');
// unhighlight all
dispatch.unhighlightAll();
// set opposite of stored state
d3.select(el).classed('highlighted', !highlighted);
});
And here's the updated JSBin.
Add a click handler on the SVG. In that click handler, first deselect all circles. Then, check the event target via d3.event; if it is a circle, select it. Pseudocode-ish description:
svg.on('click', function() {
circles.classed('selected', false);
var target = /* get event target */;
if (/* target is circle */) {
target.classed('selected', true);
}
});
Jshanley's answer is great and is probably better in most cases, but I ended up modifying it a bit:
svg.selectAll("dot").data(datasource).enter() //you probably have your own thing here
.on("mousedown", function(d) {
d3.selectAll("circle") //this selects all of the elements you want to deselect by html tag (here its "circle")
.style("fill", "black"); //default color of unselected elements, here its black
d3.select(this) //select the element that's just been clicked
.style("fill", "orange"); //orange is the color of currently selected element
});
This works as long as the default style on all elements is applied before the style of the selected element.

How can I make the flot library hovering circle effect stay upon clicking the plot?

As of right now, I have a graph for which I would like to calculate an average slope. I would like to do this by having the user select a left point and a right point on a line. I know how to record where a person clicked, but I would also like to display the same circular hover effect where they clicked until they click on it again to turn it off. Is there a special flot method that would allow me to do this, or will I have to manipulate the flot documentation (how would I go about doing that if I need to)?
You need to make the chart clickable (I guess you already have that) by setting the clickable option:
grid: {
clickable: true
}
Than bind an event handler to the plotclick event which calls the highlight method, when clicking on a data point:
$("#placeholder").bind("plotclick", function (event, pos, item) {
if (item) {
plot.highlight(item.series, item.datapoint);
}
});
(Snippets taken from the example page)

jsPlumb: Can't click() on a div inside a div that acts as a jsPlumb source/target

I'm using jsPlumb. My current functionality lets me create a .project div that can then have .task divs inside it. The .project div has 3 clickable buttons which all work using jQuery and the .tasks inside the .project have a single close button which also works.
As can we seen here: http://jsfiddle.net/9yej6/3/
(click the add project button then click on the green project and try to click on the X near some task - an alert should pop up)
However, whenever I try to make the .tasks a makeTarget/makeSource using jsPlumb it surpasses (probably not the best word) any other event done by jQuery. That is when I click on the X icon of the .task it instead acts as if I click on the .task itself and tries to create jsPlumb's bond.
As can be seen here: http://jsfiddle.net/9yej6/4/
So the following line no longer works (note I'm using the on() function since the .project/.task divs are dynamically created):
$("#container").on('click','.task .close',function(e) {
alert('a task`s add was clicked');
});
Initially the addTask() function was (which worked, but you can't add jsPlumb bonds):
function addTask(parentId, index) {
var newState = $('<div>').attr('id', 'state' + index).addClass('task');
var close = $('<div>').addClass('close');
newState.append(close);
var title = $('<div>').addClass('title').text('task ' + index);;
newState.append(title);
$(parentId).append(newState);
}
But when I add the makeTarget()/makeSource() calls to it, it seems to surpass any other jQuery event handling. Where my new addTask() function becomes:
function addTask(parentId, index) {
var newState = $('<div>').attr('id', 'state' + index).addClass('task');
var close = $('<div>').addClass('close');
newState.append(close);
var title = $('<div>').addClass('title').text('task ' + index);;
newState.append(title);
$(parentId).append(newState);
jsPlumb.makeTarget(newState, {
anchor: 'Continuous'
});
jsPlumb.makeSource(newState, {
anchor: 'Continuous'
});
}
You can also use the filter parameter to specify what element to be included for the object drag.
See my complete answer here.
http://jsplumbtoolkit.com/doc/connections.html#sourcefilter
jsPlumb.makeSource("foo", {
filter:":not(a)"
});
Above means, don't interfere with operations related to a (anchor tag).
As mentioned,
$("#container").on('click','.task .close',function(e) {
alert('a task`s add was clicked');
});
This code doesn't work becasue you have made the '.task' element as either target or source part of jsPlumb hence the mouse events will be handled by jsPlumb which prevents the default event handling(jQuery or pure JS) of those elements.
In such case you need to create a small rectangle DIV(refer image) from where the user can drag the connection instead of an entire DIV.

jVectorMap generates a 100x100 when display object is initially hidden

So I'm having a problem generating my jVectorMap.
The map itself sits inside a very custom drop down menu that I have created and this is where I suspect the problem is.
When I mouseover my menu item to open up the drop down which contains the map the actual svg starts out with a forced dimension of 100px x 100px.
What I have tried to do a number of workarounds wher I call the "map.setSize()" either on the mouseclick event of the dropdown as well as the mouseover event of the container itself. The problem here is my dropdown is not subject to a click event but shows on the mouseover event. However, at the point of the mouseover event the actual container for the map hasn't loaded so I'm still stuck with a 100px x 100px svg.
To get around this I've put an event on the mouseover event of the container itself but this isn't great either as it then requires the user to move his mouse over the container before it actually shows the map, something I don't want to happen.
Is there a way of getting the map built inside a div which is invisible before my menu event occurs?
For an example of my problem I've created this at jsfiddle http://jsfiddle.net/AEup9/
You will notice that when you hover over the "Show Map" menu item (the only item) the drop down is blank except for the topic headers until you move the mouse over the actual drop down itself which then reloads the map. I then keep the map there by using my "loaded" variable created before my mouseover event and a force map.setSize() inside the same event:
var loaded = false;
$('#aamap').mouseover(function () {
if (!loaded) {
(function () {
map = new jvm.WorldMap({
map: 'za_mill_en',
container: $('#southafrica-map'),
backgroundColor: '#cbd9f5',
initial: {
fill: 'white'
},
series: {
regions: [{
attribute: 'stroke'
}]
}
});
loaded = true;
})();
}
map.setSize();
});
This is my rough work around but not what I really want as I want the map to show up first time.
Can anyone help me here?
Edit: I finally decided to NOT go ahead with using jvectormap due to this issue. Instead I opted to use jqvmap which is to some degree a fork of jvectormap, however the issues experienced with jvectormap were no longer a problem.
I met this issue as well.
To solve that problem we need to run an updateSize method on a map object when container of our map becomes visible. First, to get the map object we need to use this command:
$('#world-map').vectorMap('get', 'mapObject') and when execute updateSize on it, like:
var map = $('#world-map').vectorMap('get', 'mapObject');
map.updateSize();
or in a shorter form:
$('#world-map').vectorMap('get', 'mapObject').updateSize();

Categories

Resources