how to drag a svg rectangle in d3.js? - svg

Here I am creating a rectangle named resize_icon within another rectangle positioning it at the lower right corner of the big rectangle.I want this resize_icon rectangle to be dragged freely within big rectangle but it should not cross the bigger rectangles top edge and its left edge but can be dragged anywhere else. resize[] is an array which contains id, x and y position of resize_icon rectangle.Please have a look at the following drag code and please tell me why is it not working. Also if anyone can suggest a proper code would be really helpful.
c.append('rect').attr('class','resize_icon').attr('fill','#000')
.attr('id','resize_icon'+i).attr('height',5).attr('width',5)
.attr('x', x+wd+35).attr('y',y+rectHeight-5).on(drag1)
.on('click',function(){
d3.selectAll(".selected").classed("selected",false);
d3.select(this.parentNode).classed("selected",true);
resize_rect(i);
});
var drag1 = d3.behavior.drag().origin(function()
{
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
.on("dragstart", dragstart)
.on("drag", dragon)
.on("dragend", dragstop);
function dragstart() {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("drag", true);
}
function dragon() {
d3.select(this).select('.resize_icon')
.attr("x", d3.event.x)
.attr("y", d3.event.y);
id = d3.select(this).select('.resize_icon').attr("id");
a = id.replace("resize_icon", "");
resize[a].x = d3.event.x;
resize[a].y = d3.event.y;
}
function dragstop() {
d3.select(this).classed("drag", false);
}

Here is my code to drag between a boundary :
function button_drag2(xLower,xHigher,yLower,yHigher){
return d3.behavior.drag()
.origin(function() {
var g = this;
return {x: d3.transform(g.getAttribute("transform")).translate[0],
y: d3.transform(g.getAttribute("transform")).translate[1]};
})
.on("drag", function(d) {
g = this;
translate = d3.transform(g.getAttribute("transform")).translate;
x = d3.event.dx + translate[0],
y = d3.event.dy + translate[1];
if(x<xLower){x=xLower;}
if(x>xHigher){x=xHigher;}
if(y<yLower){y=yLower;}
if(y>yHigher){y=yHigher;}
d3.select(g).attr("transform", "translate(" + x + "," + y + ")");
d3.event.sourceEvent.stopPropagation();
});
}
}
Then call on your selection :
var selection = "#rectangle"; //change this to whatever you want to select
d3.select(selection).call(button_drag2(-100,100,-100,100));
// this lets you drag the rectangle 100px left, right, up and down. Change to what ever you want :)
Hope this helps you out :)

Related

Transform <g> attribute on map of d3.js

I have prepared one example of map in d3.js. I wanted to implement zoom on map with and node(contains circle, smiley and text. as of now i putted circle and smiley) on map shows the city of different countries. When i zoom over map i could not able to transform the tag so smiley got misplace as per my logic. so how to transform only g tags on map. I don't want to transform shape(circle, images) inside tag.
My Jsfiddle link
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll(".node")
.attr("width", function(){
var self = d3.select(this);
var r = 28 / d3.event.scale; // set radius according to scale
self.style("stroke-width", r < 4 ? (r < 2 ? 0.5 : 1) : 2); // scale stroke-width
return r;
});
g.selectAll(".circle")
.attr("r", function(){
var self = d3.select(this);
var r = 8 / d3.event.scale; // set radius according to scale
self.style("stroke-width", r < 4 ? (r < 2 ? 0.5 : 1) : 2); // scale stroke-width
return r;
});
});
Please anybody help me to solve my issue.
To do semantic zooming, one would need to adjust the width and height of the smiley faces as well as adjust the x and y locations since the adjustments will change relative to the width/height:
g.selectAll(".node")
.attr("x", function(d) { return (projection([d.lon, d.lat])[0]) - (8 / d3.event.scale); })
.attr('y', function(d) { return (projection([d.lon, d.lat])[1]) - (8 / d3.event.scale); })
.attr('width', function () { return 20 / d3.event.scale; })
.attr('height', function () { return 20 / d3.event.scale; })
Demo: http://jsfiddle.net/ktee4dLp/

Setting up a click event in d3 that hides all other elements

I am fairly new to using d3, but what I am trying to do is make a chord diagram of some site traffic, and I am trying to make it interactive by changing the color of the paths when a user clicks on the group for a certain site.here is the style and script section of my code:
<style type="text/css">
.group text {
font: 11px sans-serif;
pointer-events: none;
}
#circle circle {
fill: none;
pointer-events: all;
}
.group path {
stroke: #000;
fill-opacity: .5;
}
path.chord {
stroke-width: .75;
fill-opacity: .75;
}
#circle:hover path.fade {
display: none;
}
</style>
</head>
<body>
<script type="text/javascript">
// Chart dimensions.
var w = 600,
h = 700,
r1 = Math.min(w, h) / 2 - 4,
r0 = r1 - 20,
format = d3.format(",.3r");
// Square matrices, asynchronously loaded; credits is the transpose of sitename.
var sitename = [];
// The chord layout, for computing the angles of chords and groups.
var layout = d3.layout.chord()
.sortGroups(d3.descending)
.sortSubgroups(d3.descending)
.sortChords(d3.descending)
.padding(.04);
// The color scale, for different categories of "worrisome" risk.
var fill = d3.scale.ordinal();
// The arc generator, for the groups.
var arc = d3.svg.arc()
.innerRadius(r0)
.outerRadius(r1);
// The chord generator (quadratic Bézier), for the chords.
var chord = d3.svg.chord()
.radius(r0);
// Add an SVG element for each diagram, and translate the origin to the center.
var svg = d3.select("body").selectAll("div")
.data([sitename])
.enter().append("div")
.style("display", "inline-block")
.style("width", w + "px")
.style("height", h + "px")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
// Load our data file…
d3.csv("data2.csv", function(data) {
var uniqueids = {},
array = [],
n = 0;
// Compute a unique id for each site.
data.forEach(function(d) {
d.siteid1 = uniqueIDMaker(d.siteid1);
d.siteid2 = uniqueIDMaker(d.siteid2);
d.valueOf = value; // convert object to number implicitly
});
// Initialize a square matrix of sitename and users
for (var i = 0; i < n; i++) {
sitename[i] = [];
for (var j = 0; j < n; j++) {
sitename[i][j] = 0;
}
}
// Populate the matrices, and stash a map from id to site.
data.forEach(function(d) {
sitename[d.siteid1.id][d.siteid2.id] = d;
array[d.siteid1.id] = d.siteid1;
array[d.siteid2.id] = d.siteid2;
});
// For each diagram…
svg.each(function(matrix, j) {
var svg = d3.select(this);
// Compute the chord layout.
layout.matrix(matrix);
// Add chords.
svg.selectAll(".chord")
.data(layout.chords)
.enter().append("svg:path")
.attr("class", "chord")
.style("fill", function(d) { return fill(d.source.value); })
.style("stroke", function(d) { return d3.rgb(fill(d.source.value)).darker(); })
.attr("d", chord)
.on("dblclick",function(){
d3.select(this)
.style("fill","red")
.style("stroke","yellow")
})
.append("svg:title")
.text(function(d) { return "site " + d.source.value.siteid2.name + " and site " + d.source.value.siteid1.name + " have " + format(d.source.value) + " common users"; })
;
// Add groups.
var g = svg.selectAll("g.group")
.data(layout.groups)
.enter().append("svg:g")
.attr("class", "group");
// Add the group arc.
g.append("svg:path")
.style("fill", function(d) { return fill(array[d.index]); })
.attr("id", function(d, i) { return "group" + d.index + "-" + j; })
.attr("d", arc)
.append("svg:title")
.text(function(d) { return "site " + array[d.index].name + " has " + format(d.value) + "users"; });
g.append("svg:text")
.attr("x", 6)
.attr("dy", 15)
.filter(function(d) { return d.value > 110; })
.append("svg:textPath")
.attr("xlink:href", function(d) { return "#group" + d.index + "-" + j; })
.text(function(d) { return array[d.index].name; });
});
function uniqueIDMaker(d) {
return uniqueids[d] || (uniqueids[d] = {
name: d,
id: n++
});
}
function value() {
return +this.count;
}});
</script>
any help would be greatly appreciated
http://jsfiddle.net/Rw3aK/2/ is a jsFiddle of the script, not sure how to make it read from a file, so here is the contents of data2.csv:
siteid1,siteid2,count,pubid1,pubid2
8,94,11132,57141,57141
201,94,10035,57141,57141
201,8,9873,57141,57141
0,94,8488,45020,57141
0,8,8258,45020,57141
0,201,7644,45020,57141
0,1,6973,45020,45020
94,1,5719,57141,45020
8,1,5670,57141,45020
1,201,5410,57141,45020
I forked your jsfiddle and converted your CSV data to JSON, now in a variable data: http://jsfiddle.net/mdml/K6FHW/.
I also modified your code slightly so that when you click on a group, all outgoing chords are highlighted red. When you click on a group again, the chords change back to their original color. Here're the relevant snippets:
When adding the chords, label each chord with a class according to the chord's source
svg.selectAll(".chord")
.data(layout.chords)
.enter().append("svg:path")
.attr("class", function(d){ return "chord chord-" + d.source.index; })
...
When clicking a group, check if that group's chords are highlighted.
If so, fill the chords with their default color
If not, fill the chords red
Then record whether or not the group's chords are highlighted in a variable d.chordHighlighted
g.append("svg:path")
...
.attr("id", function (d, i) {
return "group" + d.index + "-" + j;
})
...
.on("click", function(d){
if (d.chordHighlighted)
d3.selectAll(".chord-" + d.index)
.style("fill", fill(d.value));
else{
d3.selectAll(".chord-" + d.index)
.style("fill", "red");
}
d.chordHighlighted = d.chordHighlighted ? false : true;
})

Raphael JS rotate group/set not individually

I've been toying around with Raphael JS, but failed to find any solution to rotate a group/set of paths as group, and not individually.
A group/set of path:
Each element is rotated. Not the entire group/set:
How I do:
var bobble = map.paper.set();
bobble.push(map.paper.add([{
"fill":fill,
"stroke":"none",
x: 50,
y: 50,
width: 100,
height: 100,
"type":"rect",
"r": 4
},{
"fill":fill,
"stroke":"none",
x: 100,
y: 25,
width: 200,
height: 50,
"type":"rect",
"r": 4
}]));
bobble.rotate(45);
What am I doing wrong?
Here you go DEMO
var paper = Raphael('arrows');
var r1 = paper.rect(100,100,200,10,5).attr({fill:'white'});
var r2 = paper.rect(50,200,100,15,5).attr({fill:'white'});
var st = paper.set(r1,r2);
var l_coord = st.getBBox().x,
r_coord = st.getBBox().x2,
t_coord = st.getBBox().y,
b_coord = st.getBBox().y2;
var cx = (l_coord + r_coord)/2,
cy = (t_coord + b_coord)/2;
st.rotate(54,cx,cy);
Since you need to get your Raphael set's center coordinates, you can use getBBox() function which returns you:
The set of raphaeljs is a list of element. So, when you use tranform method, it is just a transform of unique element of list in the set.
I had created an plugin which supports g tag for my project. But I haven't yet implement the transform method.
const SVG = 'http://www.w3.org/2000/svg';
function TCRaphael(container, width, height) {
var paper = new Raphael(container, width, height);
paper.node = document.getElementById(container).getElementsByTagName("svg")[0];
console.log(paper.node)
paper.group = function (parent) {
return new Group(parent);
};
function Group(parent) {
var me = this;
this.node = document.createElementNS(SVG, "g");
if (typeof parent !== 'undefined') {
if (typeof parent.node != 'undefined')
parent.node.appendChild(me.node);
else{
parent.appendChild(me.node);
}
}
this.append = function (child) {
me.node.appendChild(child.node);
return child;
};
this.appendNode = function (childNode) {
me.node.appendChild(childNode);
};
this.appendTo = function (parent) {
if (typeof parent !== 'undefined') {
if (typeof parent.node != 'undefined')
parent.node.appendChild(me.node);
else{
parent.appendChild(me.node);
}
}
};
this.remove = function(){
me.node.parentNode.remove();
};
this.circle = function(x, y, r){
return me.append(paper.circle(x, y, r));
};
this.ellipse =function(x, y, rx, ry){
return me.append(paper.ellipse(x, y, rx, ry));
};
this.image = function(src, x, y, width, height){
return me.append(paper.image(src, x, y, width, height));
};
this.path = function(pathString){
return me.append(paper.path(pathString));
};
this.rect = function(x, y, width, height, r){
return me.append(paper.rect(x, y, width, height, r));
};
this.text = function(x, y, text){
return me.append(paper.text(x, y, text));
}
}
return paper;
}
You can add more function which you want to TCRaphael.
I think it's a little easier.
myset.transform("R45,20,20")
does exactly the same as
myset.rotate(45,20,20) // deprecated
the whole myset will rotate around the center at 20,20 (maybe the center of the set)
otherwise
myset.transform("R45")
will rotate each element of the set around it's own center

D3js - Circle position vs radius of circle or curved path

I created a data visualization of a solar system using D3.js.
In doing so, I noticed a weird inconsistency when setting the x,y position of a circle element and the radius of a circle element or curved path element.
To place the planets down, I do:
planetEnter.append("circle")
.attr("r", function (d) {
return planetScale(d.radius); })
.attr("class", "body")
.attr("fill", "url(#gradePlanet)")
.attr("filter", "url(#glowPlanet)")
.attr("transform", function (d) {
// Position of planet in relation to the sun at (0,0)
// x and y are linear scales
return "translate(" + x(d.orbital_radius) + ", " + y(0) + "), scale(.05)"; });
Now to create the orbital lines, I do:
var orbital_arc = d3.svg.arc()
.startAngle(0)
.endAngle(6.28318531) // 360 degrees
.innerRadius(function (d) {
return x(d.orbital_radius); })
.outerRadius(function (d) {
return x(d.orbital_radius); });
Now you would think that this would work and the radius of the arc would match the position of the planet, but it does not. The radius ends up MUCH bigger. To compensate, I found this magic number through trial and error:
var orbital_arc = d3.svg.arc()
.startAngle(0)
.endAngle(6.28318531) // 360 degrees
.innerRadius(function (d) {
return x(d.orbital_radius) - 470; }) // Magic number.
.outerRadius(function (d) {
return x(d.orbital_radius) - 470; }); // Magic number.
That number consistently works for every orbital line and I cannot figure why.
And it's not just the path element, the radius of a circle ends up much bigger too:
planetEnter.append("circle")
.attr("r", function (d) {
return x(d.orbital_radius); })
.attr("class", "body")
.attr("transform", function (d) {
return "translate(" + x(0) + ", " + y(0) + ")"; });
Here are the jsfiddles demostrating this (pan and zoom if you need a better view):
Solar System with Magic Number
Solar System without Magic Number
So why do I need this magic number?
Angles in D3 are set in radians, so you can have a function that does...
function degreesToRadians(degrees) {
return degrees * (Math.PI/180);
}
But you're always using circles, so this is more done more elegantly simply by...
var tau = Math.PI * 2; //this is your first magic number
var orbital_arc = d3.svg.arc()
.startAngle(0)
.endAngle(tau)
As for the second magic number (470) it's half of your width, so putting it all together you can do...
var tau = Math.PI * 2; //this is your first magic number
var orbital_arc = d3.svg.arc()
.startAngle(0)
.endAngle(tau)
.innerRadius(function (d) { return x(d.orbital_radius) - width/2; })
.outerRadius(function (d) { return x(d.orbital_radius) - width/2; });

selecting multiple svg elements and dragging them in Raphael.js

Does anyone know how I can achieve this?
I basically have an svg document with multiple shapes,lines,text,etc... and I am trying to implement a selection tool which helps me select multiple elements, group them and drag them.
There is a feature in raphäel called set: http://raphaeljs.com/reference.html#set
You can add all the elements you want to drag around to a new set and then apply the dragging mechanism to the set.
I made you this: http://jsfiddle.net/Wrajf/
It's not perfect. I would add the mousemove event to the document, but for that you need a library like jQuery. Otherwise if you move the mouse to fast you have a fall out.
I did this (example here) :
EDIT : something cleaner
Create methods to set and retrieve the group of an element :
Raphael.el.setGroup = function (group) {
this.group = group;
};
Raphael.el.getGroup = function () {
return this.group;
};
Create the method of your grouped elements :
Raphael.fn.superRect = function (x, y, w, h, text) {
var background = this.rect(x, y, w, h).attr({
fill: "#FFF",
stroke: "#000",
"stroke-width": 1
});
var label = this.text(x+w/2,y+h/2, text);
var layer = this.rect(x, y, w, h).attr({
fill: "#FFF",
"fill-opacity": 0,
"stroke-opacity": 0,
cursor: "move"
});
var group = this.set();
group.push(background, label, layer);
layer.setGroup(group);
return layer;
};
create functions to drag a grouped elements :
var dragger = function () {
this.group = this.getGroup();
this.previousDx = 0;
this.previousDy = 0;
},
move = function (dx, dy) {
var txGroup = dx-this.previousDx;
var tyGroup = dy-this.previousDy;
this.group.translate(txGroup,tyGroup);
this.previousDx = dx;
this.previousDy = dy;
},
up = function () {};
Init SVG paper and create your elements (the order of element is important)::
window.onload = function() {
var paper = Raphael(0, 0,"100%", "100%");
var x=50, y=50, w=30, h=20;
paper.superRect(x, y, w, h, "abc").drag(move, dragger, up);
x +=100;
paper.superRect(x, y, w, h, "def").drag(move, dragger, up);
};

Resources