Our project is an online DIY album. Users fill in photos in the frame. Konva is required to achieve a mask function similar to Photoshop, similar to the effect of photos pressed under the frame. There may be many picture frames in my canvas, and only one canvas can not achieve this function. Therefore, I need to use a new canvas to draw this, and then draw the contents of this mask canvas onto Konva's canvas.
Based on this idea, I realized the basic needs. Our requirement is that users can drag, rotate or scale photos. Therefore, I drew a transparent element on Konva's main canvas (Canvas 1) with the same size and position as the photo in the mask canvas (Canvas 2), and then bound it with transformer.With this transformer, I can change the position, size and rotation angle of the transparent elements, and then transfer the matrix of the transparent elements to the photos in the mask canvas through getTransform and setTransform, so that the photos in the mask can rotate, scale or translate with the transparent elements.
When the width and height of the mask canvas (Canvas 2) are the same as those of the elements finally drawn on the main canvas (Canvas 1), everything is OK. However, in order to ensure a clearer final effect, we set the width and height of the mask canvas (Canvas 2) to a higher value. Then there is a problem. When I drag or rotate the transparent elements, the position of the photos in the mask does not completely follow the transparent elements, but there is a deviation.
I think this is because the size of the transparent element is different from the size of the photo in the mask canvas (Canvas 2), so it is not possible to directly set the matrix obtained by getTransform to the photo. In this process, a transformation is required. It should be OK to set the transformed matrix to the photo in the mask. The problem is that I don't know how to carry out such a transformation.
Here's the code:
//canvas 1
let container = document.getElementById("container");
let stage = new Konva.Stage({
container: container,
width: 800,
height: 800
});
layer = new Konva.Layer();
stage.add(layer);
let group = new Konva.Group({
x: 100,
y: 100,
width: 200,
height: 200,
clipX: 0,
clipY: 0,
clipWidth: 200,
clipHeight: 200
});
let shape = await drawMaskPhoto();
group.add(shape);
let transparentRect = new Konva.Rect({
width: 200,
height: 200,
x:0,
y:0,
type:"transparentRect"
});
group.add(transparentRect);
transparentRect.on('transform',async function (e) {
let transform = e.target.getTransform().m;
shape = await drawMaskPhoto(transform);
});
function newImg(src) {
return new Promise((resolve, reject) => {
let img = new Image();
img.src = src;
img.onload = () => {
resolve(img)
}
})
}
async function drawMaskPhoto(transform){
//canvas 2
let maskCanvas = document.createElement("canvas");
let mask = await newImg(mask.imgPath);
let photo = await newImg(photo.imgPath);
//To ensure clarity, I set the new canvas size to be 5 times the size of the elements finally drawn on the main canvas (1000*1000)
maskCanvas.width = 1000;
maskCanvas.height = 1000;
let ctx = maskCanvas.getContext("2d");
ctx.drawImage(mask, 0, 0, 1000, 1000);
ctx.globalCompositeOperation = 'source-atop';
if(transform){
ctx.setTransform(...transform);
}
ctx.drawImage(photo, 0, 0, 1000, 1000);
let shape = new Konva.Image({
image:maskCanvas,
width:200,
height:200
})
return shape
}
The effect picture is like this
The effect picture drawn
Effect picture after rotation
let traceEle = $('#trace');
/*
* SyncHandler is a DIY object for synchronising canvases. In this case we have one leader and one follower.
* An object is used to scope the code. Create new instaces via
* let myStage = new Konva.Stage(..all usual Konva stage stuff...)
* let myHandler = new syncHandler(myStage, 'h4'); // where h4 = a useful trace prefix so we can see which handler is fired.
*/
function syncHandler(stage, handlerName){
let followers = [], // In this demo we have only one listening canvas but we will assume there could be many.
shapes = {}; // Each shape created is placed in this object thus forming a quick lookup list based on id.
// We need all shapes to have a unique ID so we use this func to make them.
// These are not entirely compliant GUID's but meet our needs.
function getGuid(){
let fC=function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1).toUpperCase();
}
return (fC() + fC() + "-" + fC() + "-" + fC() + "-" + fC() + "-" + fC() + fC() + fC());
}
// Fires during init stage - set the stage id if not set already.
stage.id(getGuid());
// Add a follower to the list of followers.
this.addFollower = function(newFollower){
followers.push(newFollower);
}
// Func to make a proxy for the shape's attrs arry.
function makeProxy(target){
let prxy = new Proxy(target, {
// this code fires when the shape has a change to its attrs array. It allows us
// to intervene with the prop change, send the change to the listerners
// target is the shape's attrs array which will shortly receive the new value.
set(array, prop, value, target) {
changeAttr(prop, value, target); // Invoke the function to communicate the change to any follower[]
// finally let the target - the shape's attr array get the change.
return Reflect.set(array, prop, value, target);
},
deleteProperty(array, prop, target) {
// Included in case needed in future.
let msg = {type: 'attrdel', id: target["id"], name: prop};
sendMessage(msg);
// finally let the target - the shape's attr array get the change.
return Reflect.deleteProperty(array, prop);
}
})
return prxy;
}
// This func is a wrapper for the 'new Konver.<ShapeName>()' process. It is required to:
// - ensure that the new shape will have an id;
// - hide the wiring up of the shape.attrs prox;
// - add the shape into our shapes list, keyed on the assigned id;
// - send the message about the new object to the follower[] canvases.
this.makeShape = function(typeName, attrs){
let shape = null;
attrs = (typeof attrs == 'undefined' ? {} : attrs); // if attrs is not supplied then make an empty array
attrs.id = (typeof attrs.id === 'undefined' ? getGuid() : attrs.id); // ensure there is an ID.
shape = new Konva[typeName](); // Make the actual Konva shape in this canvas.
shape.setAttrs(attrs); // Set the attrs for the new shape.
shape.attrs = makeProxy(shape.attrs);
if (typeof shapes[attrs.id] === 'undefined'){
shapes[attrs.id] = shape;
}
// Send the message about the new shape to any follower[]
let msg = {type: 'makeShape', name: typeName, attrs: attrs };
sendMessage(msg);
return shape; // Hand back the shape to the code that created it.
}
// This func is a wrapper for Konva.container.add() or move(). It takes
// as args the shape being moved and the container that is to be the new parent.
this.changeParent = function(shapeObj, newParentObj){
newParentObj.add(shapeObj); // add shape to new parent.
// Send the message about the new shape to any follower[]
let msg = {type: 'changeParent', id: shapeObj.id(), parentId: newParentObj.id(), parentType: newParentObj.getType()};
sendMessage(msg);
}
/* this func is a wrapper for the Konva.shape.setAttr() method.
* Network comms are costly - we do not want to send messages about propos that either have not changed or
* where the change to a numeric property is insignificant.
*/
function changeAttr(prop, value, target){
let currentVal = target[prop],
sendIt = true; // flag to indicate change is to be sent - overridden below for numeric types if needed.
if (currentVal !== value){
if ( typeof(value) === "number" ){
if (Math.abs(currentVal - value) < 0.01){ // adjust or remove this tollerence as needed.
sendIt = false;
}
}
if (sendIt){
// make the message
let msg = {type: 'changeAttr', id: target["id"], name: prop, value: value };
sendMessage(msg);
}
}
return true;
}
// Func to convert the given message to a JSON string and send it to any followers.
function sendMessage(msg){
if (followers.length === 0){ // no send if no listening followers.
return;
}
let jsonMsg = JSON.stringify(msg);
for (let i = 0; i < followers.length; i++){
followers[i].processMessage(jsonMsg);
}
}
/* In this func we process a received change message. In this demo this is simply one object calling a func in another
* but in the final version this will be talking via peer-to-peer between browsers. We receive a message in JSON format
* containing the change information. The 'type' value gives either 'makeShape', 'changeParent', or 'changeAttr'.
*
* Note that when this runs it is within the context of a 'following' syncHandler instance, not the sending instance!
*
*/
this.processMessage = function(changeInfo){
let change = JSON.parse(changeInfo), // parse the JSON into a JS object. Note you will want a try-catch here !
shape = null;
switch (change.type){
case "makeShape": // a new shape message.
shape = this.makeShape(change.name, change.attrs); // make the shape in the follower syncHandler - this works
// in this demo because the follwower has no followers of its own.
// If it _did_ have followers a deadlock would occur !
trace(handlerName + ".makeShape: " + change.name + ' ' + shape.id()); // record a trace of what is going on
shapes[shape.id()] = shape; // note the shape in our shape list.
break;
case "changeParent": // an existing shape is changing parent container - like from layer A to layer B.
trace(handlerName + '.changeParent: id=' + change["id"])
shape = shapes[change.id]; // get the Konva shape instance that is moving parent
// Special case for adding to stage
if (change.parentType === "Stage"){
stage.add(shape)
}
else {
let parentContainer = shapes[change.parentId]; // get the Kona shape that is to be the new container.
parentContainer.add(shape); // execute the Konva command to switch parents.
}
break;
case "changeAttr": // an attribute of a shape has changed - mirror the change in this follower.
trace(handlerName + '.changeAttr: id=' + change["id"] + ' - ' + change.name + ' = ' + change.value);
shape = shapes[change.id];
shape.setAttr(change.name, change.value);
break;
}
}
} // end of the syncHandlerobject declaration.
// a simple trace output function so we can see some of what is happening - better than console.log!
function trace(msg){
traceEle.prepend('<pre>' + msg + ' </pre>');
}
/* from here onwards is Konva canvas admin */
// Making the stage is standard Konva API code.
let stage1 = new Konva.Stage({container: "container1", width: $('#container1').width(), height: $('#container1').height()});
// And now we create the handler object.
let handler1 = new syncHandler(stage1, 'sh1');
// Making the stage is standard Konva API code.
let stage2 = new Konva.Stage({container: "container2", width: $('#container2').width(), height: $('#container2').height()});
let handler2 = new syncHandler(stage2, 'sh2');
// Very importantly - we inform handler1 than handler2 is listening and wants to know about changes.
handler1.addFollower(handler2);
// The stage object was made via standard Konva API but for all other containers and shapes we use the handler
// function which adds id and wires up listener on attrs list.
let layer1 = handler1.makeShape("Layer");
// Add the layer to the stage.
// Adding a shape is done via syncHandler so that we can capture and broadcast the change
handler1.changeParent(layer1, stage1);
// Make a rect.
let rect1 = handler1.makeShape("Rect", {x: 20, y: 10, width: 100, height: 80, fill: 'cyan', draggable: true});
// Add rect1 to layer1 via syncHandler
handler1.changeParent(rect1, layer1);
// Make a circle
let circle1 = handler1.makeShape("Circle", {x: 140, y: 100, radius: 40, fill: 'magenta'});
// Add circle1 to layer1 via syncHandler
handler1.changeParent(circle1, layer1);
// Make a pentagon
let radialGradPentagon1 = handler1.makeShape("RegularPolygon", {
x: 500,
y: stage1.height() / 2,
sides: 5,
radius: 70,
fillRadialGradientStartPoint: { x: 0, y: 0 },
fillRadialGradientStartRadius: 0,
fillRadialGradientEndPoint: { x: 0, y: 0 },
fillRadialGradientEndRadius: 70,
fillRadialGradientColorStops: [0, 'red', 0.5, 'yellow', 1, 'blue'],
stroke: 'black',
strokeWidth: 4,
draggable: true,
});
// Add radialGradPentagon1 to layer1 via syncHandler
handler1.changeParent(radialGradPentagon1, layer1);
//
// Now we carry out a handful of attribute changes on the shapes to confirm it works !
//
// Move the rect to x = 101
rect1.x(101)
// Fill rect with red and rotate 45 degrees.
rect1
.fill('red')
.rotation(45);
rect1.on('mousedown', function(e){
e.cancelBubble = true;
})
// make circle draggable
circle1.draggable(true);
// Change the pentagon gradient
radialGradPentagon1
.fillRadialGradientEndRadius(60)
.fillRadialGradientColorStops([0, 'red', 0.5, 'yellow', 1, 'blue']);
// We will also now make a transformer on Stage 1 to experiment with dynamic attr changes.
var tr = handler1.makeShape("Transformer", {
anchorStroke: 'red',
anchorFill: 'yellow',
anchorSize: 20,
borderStroke: 'green',
borderDash: [3, 3],
nodes: [],
});
handler1.changeParent(tr, layer1);
// attach the transformer to the rect
tr.nodes([rect1])
layer1.on('mousedown', function(){
traceEle.html('');
})
// add a new rect to be used as a mouse-selection rectangle via click & drag on stage1.
var selectionRectangle = handler1.makeShape("Rect", {
name: 'selectionRect',
fill: 'rgba(0,0,255,0.5)',
visible: false,
});
// Following copied from https://konvajs.org/docs/select_and_transform/Basic_demo.html
// Add selectionRectangle to layer1 via syncHandler
handler1.changeParent(selectionRectangle, layer1);
let x1, y1, x2, y2;
stage1.on('mousedown touchstart', (e) => {
// do nothing if we mousedown on any shape
if (e.target !== stage1) {
return;
}
x1 = stage1.getPointerPosition().x;
y1 = stage1.getPointerPosition().y;
x2 = stage1.getPointerPosition().x;
y2 = stage1.getPointerPosition().y;
selectionRectangle.visible(true);
selectionRectangle.width(0);
selectionRectangle.height(0);
});
stage1.on('mousemove touchmove', () => {
// do nothing if we didn't start selection
if (!selectionRectangle.visible() ) {
return;
}
x2 = stage1.getPointerPosition().x;
y2 = stage1.getPointerPosition().y;
selectionRectangle.setAttrs({
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.abs(x2 - x1),
height: Math.abs(y2 - y1),
});
});
stage1.on('mouseup touchend', () => {
// no nothing if we didn't start selection
if (!selectionRectangle.visible()) {
return;
}
// update visibility in timeout, so we can check it in click event
setTimeout(() => {
selectionRectangle.visible(false);
});
var shapes = stage1.find();
var shapes = layer1.getChildren(function(node){
return node.name() !== 'selectionRect' && node.getClassName() != "Transformer";
});
var box = selectionRectangle.getClientRect();
var selected = shapes.filter((shape) =>
Konva.Util.haveIntersection(box, shape.getClientRect())
);
tr.nodes(selected);
});
.container {
width: 800px;
height: 300px;
background-color: silver;
margin: 5px;
}
#trace {
max-height: 200px;
overflow-y: scroll;
font-family: 'Courier'
}
pre {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://unpkg.com/konva#8/konva.min.js"></script>
<div id='container1' class='container'></div>
<div id='container2' class='container'></div>
<p id='trace'></p>
I am facing one issue while setting event on Pushpin which was created by canvas. To set the environment please goto Bing Map - Pushpin Event Example. And copy below code in Javascript tab and run the example.
var map = new Microsoft.Maps.Map(document.getElementById('myMap'), {});
var pushpin = new Microsoft.Maps.Pushpin(map.getCenter(), {
icon: createRedArrow(110),
anchor: new Microsoft.Maps.Point(12, 12)
});
map.entities.push(pushpin);
function createRedArrow(heading) {
var c = document.createElement('canvas');
c.width = 24;
c.height = 24;
var ctx = c.getContext('2d');
// Offset the canvas such that we will rotate around the center of our arrow
ctx.translate(c.width * 0.5, c.height * 0.5);
// Rotate the canvas by the desired heading
ctx.rotate(heading * Math.PI / 180);
//Return the canvas offset back to it's original position
ctx.translate(-c.width * 0.5, -c.height * 0.5);
ctx.fillStyle = '#f00';
// Draw a path in the shape of an arrow.
ctx.beginPath();
ctx.moveTo(12, 0);
ctx.lineTo(5, 20);
ctx.lineTo(12, 15);
ctx.lineTo(19, 20);
ctx.lineTo(12, 0);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Generate the base64 image URL from the canvas.
return c.toDataURL();
}
// Binding the events
Microsoft.Maps.Events.addHandler(pushpin, 'click', function () { highlight('pushpinClick'); });
Microsoft.Maps.Events.addHandler(pushpin, 'dblclick', function () { highlight('pushpinDblclick'); });
Microsoft.Maps.Events.addHandler(pushpin, 'mousedown', function () { highlight('pushpinMousedown'); });
Microsoft.Maps.Events.addHandler(pushpin, 'mouseout', function () { highlight('pushpinMouseout'); });
Microsoft.Maps.Events.addHandler(pushpin, 'mouseover', function () { highlight('pushpinMouseover'); });
Microsoft.Maps.Events.addHandler(pushpin, 'mouseup', function () { highlight('pushpinMouseup'); });
// Setting up the printout panel
document.getElementById('printoutPanel').innerHTML =
'<div id="pushpinClick">click</div><div id="pushpinDblclick">dblclick</div><div id="pushpinMousedown">mousedown</div><div id="pushpinMouseout">mouseout</div><div id="pushpinMouseover">mouseover</div><div id="pushpinMouseup">mouseup</div>';
function highlight(id) {
document.getElementById(id).style.background = 'LightGreen';
setTimeout(function () { document.getElementById(id).style.background = 'white'; }, 1000);
}
You can notice that all pushpin events are working on canvas's width/height (24/24) area as shown in below image
And as per my requirement events should only work on drawn part of canvas and also canvas w/h will be like 100. So, how do I achieve that?
Also, if two or more pushpins are nearer (see below image) and if both canvas are overlap to each other then how we can identify both pushpins are different for pushpin event?
Here, I have provided my requirement with Bing Map's exisiting example. But here is actual pushpin secnario.
Unfortunately this is a limitation in how pushpins are rendered in Bing Maps. There is an option to modify the click area to be round rather than square which will help a bit here. See the roundClickableArea pushpin option: https://learn.microsoft.com/en-us/bingmaps/v8-web-control/map-control-api/pushpinoptions-object
I’d like to rebuild this animation http://imgur.com/l5Vhswe in paper.js.
I already tried SVG animations (http://codepen.io/magglomag/pen/jrVwzy) but despite from the fact that they’ll be deprecated soon I was not able to move the two points asynchronously.
What I have so far is the shape and I know that I can animate with the onFrame event handler. But I have no clue how to say that the point should animate between the coordinates [43,168.7] and [43,35.3].
http://codepen.io/magglomag/pen/yaVXrr
var firstSegment = new Segment({
point: [109,3.7]
});
var secondSegment = new Segment({
point: [43,168.7]
});
var thirdSegment = new Segment({
point: [109,202.2]
});
var path = new Path({
segments: [firstSegment, secondSegment, thirdSegment],
fillColor: '#2dfd9a',
closed: true
});
secondSegment.onFrame = function(event) {
this.point = [43,35.3]
}
The error you are making is that you are trying to bind an handler to segment.onFrame event.
But only item.onFrame and view.onFrame are available.
In PaperScript context, you can even use a global onframe named function as a convenient way to animate things.
Here is a simple example demonstrating how a path segment can be animated.
// create a triangle
var triangle = new Path.RegularPolygon({
center: view.center,
sides: 3,
radius: 50,
fillColor: 'orange'
});
// store initial first point position
var initialPoint = triangle.firstSegment.point.clone();
// on frame
function onFrame(event) {
// use sine function as a convenient way to demonstrate animation
var newPoint = initialPoint + Math.sin(event.count * 0.05) * 30;
// update first point
triangle.firstSegment.point = newPoint;
}
This question already has an answer here:
NVD3 - How to refresh the data function to product new data on click
(1 answer)
Closed 7 years ago.
I try to create a living line chart. I always show a fixed number of points adding a new one means removing an old one. To do this I use an interval timer to redraw the chart.
This works quite nice until I run the profiler and have a look at the memory consumption. This chart consumes a lot of memory and more and more for every step. I cannot see an obvious reason because the data is shift() out of the array after a new value is push() in.
var data = [{
"key" : "Long",
"values" : getData()
}];
var chart;
function redraw() {
nv.addGraph(function() {
var chart = nv.models.lineChart().margin({
left : 100
})
//Adjust chart margins to give the x-axis some breathing room.
.useInteractiveGuideline(true) //We want nice looking tooltips and a guideline!
//.transitionDuration(350) //how fast do you want the lines to transition?
.showLegend(true) //Show the legend, allowing users to turn on/off line series.
.showYAxis(true) //Show the y-axis
.showXAxis(true);
//Show the x-axis
chart.xAxis.tickFormat(function(d) {
return d3.time.format('%x')(new Date(d))
});
chart.yAxis.tickFormat(d3.format(',.1%'));
d3.select('#chart svg').datum(data)
//.transition().duration(500)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
}
function getData() {
var arr = [];
var theDate = new Date(2012, 01, 01, 0, 0, 0, 0);
for (var x = 0; x < 30; x++) {
arr.push({
x : new Date(theDate.getTime()),
y : Math.random() * 100
});
theDate.setDate(theDate.getDate() + 1);
}
return arr;
}
setInterval(function() {
var long = data[0].values;
var next = new Date(long[long.length - 1].x);
next.setDate(next.getDate() + 1)
long.shift();
long.push({
x : next.getTime(),
y : Math.random() * 100
});
redraw();
}, 1500);
What's wrong?
Thanks to #shabeer90 hint I found the solution. I just had to call the following method after the chart has been constructed.
function update() {
var data = getData();
// Update the SVG with the new data and call chart
chartData.datum(data).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
};
And that's it!
Observed Behavior
I'm using d3.js, and I'm in a situation where I'd like to update some data based on a drag event, and redraw everything after the dragend event. The draggable items also have some click behavior.
Draggable items can only move along the x-axis. When an item is dragged, and the cursor is directly above the draggable item on dragend/mouseup, the item must be clicked twice after it is re-drawn for the click event to fire. When an item is dragged, but dragend/mouseup does not occur directly above the item, the click event fires as expected (on the first try) after the redraw.
Desired Behavior
I'd like the click event to always fire on the first click after dragging, regardless of where the cursor is.
If I replace the click event on the draggable items with a mouseup event, everything works as expected, but click is the event I'd really like to handle.
A Demonstration
Here is a self-contained example: http://jsfiddle.net/RRCyq/2/
And here is the relevant javascript code:
var data, click_count,did_drag;
// this is the data I'd like to render
data = [
{x : 100, y : 150},
{x : 200, y : 250}
];
// these are some elements I'm using for debugging
click_count = d3.select('#click-count');
did_drag = d3.select('#did-drag');
function draw() {
var drag_behavior,dragged = false;
// clear all circles from the svg element
d3.select('#test').selectAll('circle')
.remove();
drag_behavior = d3.behavior.drag()
.origin(Object)
.on("drag", function(d) {
// indicate that dragging has occurred
dragged = true;
// update the data
d.x = d3.event.x;
// update the display
d3.select(this).attr('cx',d.x);
}).on('dragend',function() {
// data has been updated. redraw.
if(dragged) { draw(); }
});
d3.select('#test').selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',function(d) { return d.x; })
.attr('cy',function(d) { return d.y; })
.attr('r',20)
.on('click',function() {
did_drag.text(dragged.toString());
if(!dragged) {
// increment the click counter
click_count.text(parseInt(click_count.text()) + 1);
}
}).call(drag_behavior);
}
draw();
A little late to the party, buuuut...
The documentations suggests that you use d3.event.defaultPrevented in your click event to know whether or not the element was just dragged. If you combine that with your drag and dragend events, a much cleaner approach is to call the exact function you want when necessary (see when and how flashRect is called):
http://jsfiddle.net/langdonx/fE5gN/
var container,
rect,
dragBehavior,
wasDragged = false;
container = d3.select('svg')
.append('g');
rect = container.append('rect')
.attr('width', 100)
.attr('height', 100);
dragBehavior = d3.behavior.drag()
.on('dragstart', onDragStart)
.on('drag', onDrag)
.on('dragend', onDragEnd);
container
.call(dragBehavior)
.on('click', onClick);
function flashRect() {
rect.attr('fill', 'red').transition().attr('fill', 'black');
}
function onDragStart() {
console.log('onDragStart');
}
function onDrag() {
console.log('onDrag');
var x = (d3.event.sourceEvent.pageX - 50);
container.attr('transform', 'translate(' + x + ')');
wasDragged = true;
}
function onDragEnd() {
if (wasDragged === true) {
console.log('onDragEnd');
// always do this on drag end
flashRect();
}
wasDragged = false;
}
function onClick(d) {
if (d3.event.defaultPrevented === false) {
console.log('onClick');
// only do this on click if we didn't just finish dragging
flashRect();
}
}
I didn't like the global variable, so I made a revision to use data: http://jsfiddle.net/langdonx/fE5gN/1/
After observing that the click required before my svg circles would start responding to click events again could happen anywhere in the document, I settled on a hack whereby I simulate a click event on the document (thanks to https://stackoverflow.com/a/2706236/1015178) after the drag ends. It's ugly, but it works.
Here's the function to simulate an event (again, thanks to https://stackoverflow.com/a/2706236/1015178)
function eventFire(el, etype){
if (el.fireEvent) {
(el.fireEvent('on' + etype));
} else {
var evObj = document.createEvent('Events');
evObj.initEvent(etype, true, false);
el.dispatchEvent(evObj);
}
}
And here's the updated drag behavior:
drag_behavior = d3.behavior.drag()
.origin(Object)
.on("drag", function(d) {
// indicate that dragging has occurred
dragged = true;
// update the data
d.x = d3.event.x;
// update the display
d3.select(this).attr('cx',d.x);
}).on('dragend',function() {
// data has been updated. redraw.
if(dragged) { draw(); }
// simulate a click anywhere, so the svg circles
// will start responding to click events again
eventFire(document,'click');
});
Here's the full working example of my hackish "fix":
http://jsfiddle.net/RRCyq/3/