SVG shapes added to Raphael group fire events as if separate - svg

Imagine this:
2 concentric circles, with the smaller one over the larger one so
that both are visible
both are added to a Raphael group (set)
the group has mouseout and mouseover event handlers
Problem:
When the cursor goes from one circle to the other, both event handlers fire, as if they were added separately to each circle.
What I want is for events to be handled for the entire group as if it was a single shape.
How can I achieve that?
Here's the html code:
<!DOCTYPE html>
<html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.0.0/raphael-min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body>
<div id="target"></div>
</body>
</html>
Javascript:
var W=200;
var H=200;
var paper=new Raphael(document.getElementById('target'),W,H);
var c1=paper.circle(W/2,H/2,70).attr({fill:'orange','stroke-width':4,stroke:'red',opacity:0.7});
var c2=paper.circle(W/2,H/2,50).attr({fill:'green','stroke-width':4,stroke:'yellow',opacity:0.7});
var group=paper.set();
group.push(c1,c2);
var count=0;
group.mouseover(function()
{
console.log('IN',++count);
});
group.mouseout(function()
{
console.log('OUT',++count);
});
and the CSS code:
#target{width:200px;}
Run the above code and see the results here: http://jsbin.com/ivules/7.
Console shows IN and OUT logs.
Just move the mouse between the two circles' bounds.

For your mouseout() function, please try:
group.mouseout(function()
{
this.mouseout(function(){
console.log('OUT',++count);
});
});
When you hover over the outer circle, you will get "IN". When you hover over the inner circle, you will get "IN" again. When you leave the circle entirely, you will finally get "OUT".
If that's too many "IN"'s, try creating an invisible circle, place it over top the current 2 circles, and only add the mouseover event to that circle. For instance, try:
c3=paper.circle(W/2,H/2,70).attr({fill:'orange','stroke-width':4,stroke:'red',opacity:0});
c3.mouseover(function()
{
console.log('IN',++count);
});
c3.mouseout(function()
{
console.log('OUT',++count);
});

Related

Touch events are wrong for transformed SVG elements

In the process of experimenting with scaling/panning an inline SVG image by applying a matrix transform I have discovered a rather peculiar thing. After loading the image I am attaching a touchstart event listener to some of the elements in the SVG image and the event fires right away when the object is touched. However, after applying a transform
document.getElementById('mysvg').setAttribute('transform''matrix(a b c d e)')
which has the effect of scaling and/or translating the entire SVG image touching the same object no longer triggered the expected touch event. After some experiment I found that the event could still be triggered by the touch location on screen had no bearing to the actual new placement of the object on the screen. I then proceeded to first removeEventListener followed by addEventListener for the object after issuing the matrix transform and lo & behold the touch event handling was back to normal.
Quite apart from the fact that I would like to avoid the rather expensive operations of removing & then reassigning the same event listener after each pan/zoom I would like to understand just why this is happening. It is like the browser is locating the pixel location of the object at the addEventListener stage and then holds on to that somewhere in its memory blissfully ignorant of any object displacements that might have occurred later.
Can anyone here tell me what is going on here and how I can go about retaining the utility of the touch event after pan & zoom in a more efficient manner?
I've set up a similar issue:
There is a <circle> element, with a transform attribute inside an <svg>.
The 'touchstart' event fires only at the first tap on the <circle>. After that it doesn't trigger the 'touchstart' event anymore.
I have found a strange workaround: Add a 'touchstart' eventListener to the <svg> element with a noop handler:
document.querySelector('svg').addEventListener('touchstart', () => {});
After this the <circle> triggers the 'touchstart' events perfectly.
You can test it with the folllowing snipet:
let debugLines = [];
let lines = 0;
function writeDebug(...t) {
let d = document.getElementById('debug');
debugLines.unshift(`${lines++}: ${t.join(' ')}`);
debugLines.splice(5);
d.innerHTML = debugLines.join('<br />');
}
document.querySelectorAll('circle')[0].addEventListener('touchstart', writeDebug);
/* remove comment from the line below to test workaround */
// document.querySelector('svg').addEventListener('touchstart', () => {});
<!doctype html>
<html>
<head>
<style>
svg { background: #f0f0f0; width: 200px; float: left; }
</style>
</head>
<body>
<div id="wrap">
<svg viewBox="-50, -50, 100, 100" class="b-circular-slider-svg">
<circle cx="0" cy="0" r="8"
stroke="#ccc" fill="#fafafa"
transform="translate(0, -10)"></circle>
</svg>
</div>
<strong>debug:</strong>
<div id="debug"></div>
</body>
</html>

Do not render chart when no data is found or display a message or something similar

I'm using the drilldown pie chart to drill down into children of a node, etc. The problem is that at some point, the children do not contain any data. Is there a way to display a message or something similar inside the chart instead of a white area (because of no series data) that informs the user that there is no more data to display?
Very good question! Inside of your JSON configuration you can define the nodata attribute.
var myConfig = {
type: "bar",
noData:{
text:"Empty Series",
backgroundColor: "#20b2db"
},
series:[
{
values:[]
}
]
};
zingchart.render({
id : 'myChart',
data : myConfig,
height: 400,
width: 600
});
<!DOCTYPE html>
<html>
<head>
<script src= "https://cdn.zingchart.com/zingchart.min.js"></script>
</head>
<body>
<div id='myChart'></div>
</body>
</html>
You can even use background image like a loading screen. In the following example I'm displaying spongebob while I'm waiting for chart data to come in. So I initially render a chart with no series values and nodata defined. The image is displayed while the Ajax call happens asynchronously.
demo

Hammerjs events order for nested elements

Consider two nested divs with "click" event handlers:
var parent = document.getElementById("parent");
parent.addEventListener("click", function() {
console.log("parent click");
});
var child = document.getElementById("child");
child.addEventListener("click", function() {
console.log("child click");
});
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="parent" style="width:150px;height:100px;border: 1px solid black">
<div id="child" style="width:75px;height:50px;border: 1px solid black"></div>
</div>
</body>
</html>
When one clicks on the nested element the "click" event "bubbles", so the output in the console looks like this:
child click
parent click
Now consider similar example with Hammerjs involved:
var parent = document.getElementById("parent");
var hammer1 = new Hammer(parent).on("tap", function() {
console.log("parent click");
});
var child = document.getElementById("child");
var hammer2 = new Hammer(child).on("tap", function() {
console.log("child click");
});
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.js"></script>
</head>
<body>
<div id="parent" style="width:150px;height:100px;border: 1px solid black">
<div id="child" style="width:75px;height:50px;border: 1px solid black">
</div>
</div>
</body>
</html>
When one clicks on the nested element the console output is the opposite (parent's event handled first):
parent click
child click
To get the "bubble" event order one has to register child event handler before the parent's one.
Is there any way to achieve the same effect without messing with the order of event handler registrations?
Using jQuery, here's one trick I used successfully:
Normally you would do something like this:
$("some_selector").hammer().on('press', handlePress);
But to get the order right with Hammer events, this works:
$($("some_selector").get().reverse()).hammer().on('press', handlePress);
It's just a trick to get jQuery to assign the events in the reverse order (from the leaf children up the tree to the parents and parents parents etc.)
I looked into this because I ran into the very same issue.
To understand the problem you need to know how hammerjs recognizes gestures (consisting of several events); in this case the "tap"-gesture.
Hammer installs so-called recognizers that get activated when certain events travel (bubble) along the DOM.
The "tap"-event gets triggered when the "pointerup"-event bubbles up to the window.
When the "pointerup"-event reaches the window (event.currentTarget is window) all registered recognizers get actived on that node's event and that is in the order of installation.
To respect the propagation line the "tap"-recognizer would have to fire on their respective element's "pointerup" event rather than on window.
Hope that is a somewhat consistent explanation.

Is it possible to change clipart color programmatically?

I would like to know if it is possible to change clipart color in html5 canvas. I couldn't find any information about it, but I have seen a designer's software that is able to implement this function. Thank you in advance!
Yes, you can use html canvas to change selected colors on an image.
Here's how:
You can use canvas's getImageData to read the RGBA value of any pixel(s) on the canvas:
// get the pixel at the click position
var imgData=ctx.getImageData(mouseX,mouseY,1,1);
var data=imgData.data;
// the R,G,B of the clicked color are in sequential elements of the data[] array
var Red=data[0];
var Green=data[1];
var Blue=data[2];
Then to replace a color, you can loop through the canvas’s entire pixel array and replace the clicked color with a new color of your choice:
// test
// replace the clicked color with Gold
var newR=255;
var newG=215;
var newB=0;
// get the pixel array for the whole canvas
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
// loop through all pixels on the canvas
for(var i=0;i<data.length;i+=4) {
// if this pixel matches the old color, replace it
if(data[i]==oldR && data[i+1]==oldG && data[i+2]==oldB){
data[i]= newR;
data[i+1]= newG;
data[i+2]= newB;
}
}
And finally, when you’ve replaced all the colors, use ctx.putImageData to draw the modified pixels back on the canvas.
// put the recolored image back on the canvas
ctx.putImageData(imgData,0,0);
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/LZUfB/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var img=new Image();
img.onload=function(){
canvas.width=img.width;
canvas.height=img.height;
ctx.drawImage(img,0,0);
}
// make sure to use crossOrigin="anonymous" to avoid CORS errors
// the image must be hosted on a CORS enabled site
img.crossOrigin="anonymous";
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/colorhouse.png";
// when the user clicks, change the clicked color to Gold
$("#canvas").click(function(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// get the pixel at the click position
var imgData=ctx.getImageData(mouseX,mouseY,1,1);
var data=imgData.data;
// if the clicked color is transparent, no work to do
if(data[3]<10){return;}
// save the R,G,B of the clicked color
var oldR=data[0];
var oldG=data[1];
var oldB=data[2];
// test
// replace the clicked color with Gold
var newR=255;
var newG=215;
var newB=0;
// get the pixel array for the whole canvas
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
// loop through all pixels on the canvas
for(var i=0;i<data.length;i+=4) {
// if this pixel matches the old color, replace it
if(data[i]==oldR && data[i+1]==oldG && data[i+2]==oldB){
data[i]= newR;
data[i+1]= newG;
data[i+2]= newB;
}
}
// put the recolored image back on the canvas
ctx.putImageData(imgData,0,0);
});
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

Plot dimple(d3.js) chart in deck.js section

I would like to insert a dimple plot into a deck.js presentation. The code below online puts the plot in the body at the background. But I would like to have the plot displayed in the section class. I think I have to change something in var svg = dimple.newSvg("body", 800, 600). Because of my very limited javascript skills I have no idea what to change exactly. Any help would be very much appreciated.
<section class="slide" id="test-section">
<h2>test section</h2>
<script type="text/javascript">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://dimplejs.org/dist/dimple.v1.min.js"></script>
var svg = dimple.newSvg("body", 800, 600);
var data = [
{ "Word":"Hello", "Awesomeness":2000 },
{ "Word":"World", "Awesomeness":3000 }
];
var chart = new dimple.chart(svg, data);
chart.addCategoryAxis("x", "Word");
chart.addMeasureAxis("y", "Awesomeness");
chart.addSeries(null, dimple.plot.bar);
chart.draw();
</script>
</section>
If only the included the specific section class code in my question. If needed the complete code can be found here. The index page in the is located in the introduction folder.
A couple things need fixing:
First, you can't put a script tag inside of another script tag. You should move the code that loads d3 and dimple to the head of the document:
...
<script src="../modernizr.custom.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://dimplejs.org/dist/dimple.v1.min.js"></script>
</head>
Second, as you suspected and John points out, something with dimple.newSvg is wrong. You probably want var svg = dimple.newSvg("#test-section", 800, 600); so the graph is only added to the test-selection slide, not all of the slides.
I would actually go one step farther and change the html a little bit so you can control precisely where the graph appears:
<h2>Graph Title</h2>
<div id = "graphHere"></div>
<h3>Some more text about the graph below the graph</h3>
To make the graph appear between the text, just change the selection passed to dimple to the id of the div we've created:
var svg = dimple.newSvg("#graphHere", 800, 600);
Finally, chart.js is doing some weird resizing the graph since it is too big to fit on the slide. Without digging through the source of chart.js, we can fix the problem by creating a smaller graph:
var svg = dimple.newSvg("#graphHere", 400, 200);
I like the look of deck.js so I just pulled it down and had a play. I then came back and found Adam had basically explained everything I just found out. You need to put a div within the slide and add the svg to that, otherwise the deck scaling code duplicates the chart.
First add a div to the relevant slide:
<section class="slide">
<div id="myChartDiv"></div>
</section>
Then add the references to the set at the bottom (or the header if you like):
<!-- Required JS files. -->
<script src="jquery-1.7.2.min.js"></script>
<script src="core/deck.core.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://dimplejs.org/dist/dimple.v1.min.js"></script>
then the dimple code below that:
<script type="text/javascript">
var svg = dimple.newSvg("#myChartDiv", 800, 600);
var data = [
{ "Word":"Hello", "Awesomeness":2000 },
{ "Word":"World", "Awesomeness":3000 }
];
var chart = new dimple.chart(svg, data);
chart.addCategoryAxis("x", "Word");
chart.addMeasureAxis("y", "Awesomeness");
chart.addSeries(null, dimple.plot.bar);
chart.draw();
</script>
I hope that hopes
John
I've never used deck.js but have you tried:
var svg = dimple.newSvg(".slide", 800, 600);
or
var svg = dimple.newSvg("#test-section", 800, 600);
Let me know if that works. If not I'll take a look at your code.
I know that this thread is from a long time ago, but let me add one thing in addition to Adam's answer.
At least on dimple v2.1.2 + deck.js v1.1.0 + Firefox 34.0, the graph is corrupted in Adam's example.
It seems that the size of the div tag must be explicitly set:
<div id="graphHere" style="width:400px;height:300px;"></div>
...
<script>
var svg = dimple.newSvg("#graphHere", 400, 300);
// plotting function goes here
</script>

Resources