duplicating the whole svg element using d3.js - javascript

I am creating a rectangle using d3.js, inside that rectangle i am creating 10 smaller rectangles`.
i want to replicate whole thing into another svg element on mouse click.
jsfiddle link of the code : http://jsfiddle.net/nikunj2512/XK585/
Here is the code:
var svgContainer = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200);
//Draw the Rectangle
var rectangle = svgContainer.append("rect")
.attr("x", 10)
.attr("y", 10)
.attr("fill", "red")
.attr("width", 200)
.attr("height", 200);
var bigRectContainer = d3.select('#bigRectContainer').append('svg')
.attr('width', 200)
.attr('height', 200);
var dim = 20;
var x = 0;
var y = 0;
for (i = 1; i < 11; i++) {
x = 10 + (i-1)*dim;
//alert(x);
y = 10;
svgContainer.append("rect")
.attr("x", x)
.attr("y", y)
.attr("width", 20)
.attr("height", 20)
.style("fill", "black");
}
var bigRectContainer = svgContainer.append("g");
svgContainer.selectAll("rect").on("click", function () {
var littleRect = d3.select(this);
console.log(littleRect)
var bigRect = bigRectContainer.selectAll("rect")
.data(littleRect)
.enter()
.append("rect");
});
Please tell me where i made the mistake...

I'm not entirely certain what you're trying to do with the code you've posted, but I thought that duplicating an entire SVG node was interesting. It turns out it's quite easy to do with selection#html - this doesn't work on the SVG node, but it does work on its container, so it's easy to clone the whole node:
function addAnother() {
var content = d3.select(this.parentNode).html();
var div = d3.select('body').append('div')
.html(content);
div.selectAll('svg').on('click', addAnother);
}
svg.on('click', addAnother);
See working fiddle here. Note that this only works if the SVG node is the only child of its parent - otherwise, you might need to wrap it somehow.

D3 doesn't provide cloning functionality, probably because of the native cloneNode method that already exists on DOM elements, including SVG nodes.
This method includes a boolean parameter to deep copy (i.e. copy all descendants) instead of just cloning the node it is called on. You would probably want to do something like bigRectContainer.node().cloneNode(true) to copy the entire DOM branch of rectangles.

Related

Creating a d3.js scale with svg elements as range

I am trying to create a scale which contains the svg elements as range. E.g. a linear continous scale with a range containing circles with radius ranging from 0 to 100 and which can also be queried for those circles. The purpose of this is to pass the scale to a legend maker which uses the nice properties of d3 scales to construct a legend.
I am able to create circles that I see in the browser's page inspector but they are not displayed where it matters. Why is that? If append() accepts a dom element why isn't it displayed?
let canvas = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 200);
let domFun = function(scale, range) {
scale.range(range);
return function(d) {
let template = document.createElement("template");
template.innerHTML = scale(d);
let dom = template.content.childNodes[0];
return dom;
}
};
let cScale = domFun(d3.scaleLinear(), ["<circle r = 0>", "<circle r = 100>"]);
let data = [0.2, 0.3, 0.6, 1];
canvas.selectAll("circle").data(data).enter()
.append(d => cScale(d))
.attr("cy", 100)
.attr("cx", (d, i) => (i + 0.5) * 200)
.attr("fill", "red");
Grateful for any help/input here.
While the other answer is the typical D3 approach to this sort of task, you can make this work with one modification.
You need to specify an SVG namespace when creating the elements. This might look like:
return function(d) {
let template = document.createElementNS(d3.namespaces.svg, "svg")
template.innerHTML = scale(d);
let dom = template.firstChild;
return dom;
}
Altogether that looks something like:
let svg = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 200);
let domFun = function(scale, range) {
scale.range(range);
return function(d) {
let template = document.createElementNS(d3.namespaces.svg, "svg")
template.innerHTML = scale(d);
let dom = template.firstChild;
return dom;
}
};
let cScale = domFun(d3.scaleLinear(), ["<circle r = 0>", "<circle r = 100>"]);
let data = [0.2, 0.3, 0.6, 1];
svg.selectAll("circle").data(data).enter()
.append(d => cScale(d))
.attr("cy", 100)
.attr("cx", (d, i) => (i + 0.5) * 200)
.attr("fill", "red");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Why use the scale to construct some tags and use a function to extract it from inside another tag?
Just use the scale to calculate the radius and the circles are visible
let canvas = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 200);
let cScale = d3.scaleLinear().range([0, 100]);
let data = [0.2, 0.3, 0.6, 1];
canvas.selectAll("circle").data(data).enter()
.append("circle")
.attr("r", d => cScale(d))
.attr("cy", 100)
.attr("cx", (d, i) => (i + 0.5) * 200)
.attr("fill", "red");
Edit
Instead of using the string interpolator and constructing a DOM element it can be done with the Object interpolator. This allows you to also interpolate colors.
let canvas = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 200);
let icScale = d3.scaleLinear().range([{r:10, fill:"red", cx:100}, {r:100, fill:"yellow", cx:700, cy:100, shape:"circle"}]);
let data = icScale.ticks(5);
let shape = icScale(data[0]).shape;
canvas.selectAll(shape).data(data).enter()
.append(shape)
.attrs(d => icScale(d));
you need to add
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
this to your HTML page to use the .attrs().
Only the second object needs to have the shape attribute, and also because here cy does not change, it only needs to be in the second object.
If you need to keep the object returned by the scale be aware that you have to make a copy.

Object Oriented D3 JS - How to select object?

Setting up my D3 project, which will have a number of shapes on the screen being animated (Org class). I am trying to maintain an object-oriented paradigm, and take advantage of the D3 animations (https://jrue.github.io/coding/2014/lesson07/).
Consider the code below:
function test() {
class Org {
constructor(_width, _height) {
this.width = _width;
this.height = _height;
}
}
var orgs = [];
var canvas = d3.select('body')
.append('svg')
.attr('width', screen.width)
.attr('height', screen.height);
for (var x = 0; x < 100; x++) {
var circle = new Org(Math.random()*screen.width, Math.random()*screen.height);
orgs.push(circle);
canvas.append('circle')
.attr('cx', circle.width)
.attr('cy', circle.height)
.attr('r', 5)
.attr('fill', 'pink');
}
for (var b = 0; b < orgs.length; b++) {
circle.transition().attr('cx', 0); //DOES NOT WORK
}
}
Obviously, the commented line throws an error because transition() belongs to D3, not my class. How can I select these objects and animate them?
Your code is not very D3-ish, which makes it cumbersome to achieve your goal. Keep in mind, that D3 by its nature is about data-driven documents and, thus, data binding is at its very core. It is essential to understand this concept to get the most out of this library. When refactoring your code accordingly, the solution becomes almost obvious.
That said, it always looks suspicious using for-loops when dealing with D3. Only rarely is there a need to put those loops to use as this is taken care of by D3's internal workings. Without breaking your OO-approach you can bind your orgs array to a selection and take advantage of D3 doing its magic:
var circles = canvas.selectAll("circle")
.data(orgs)
.enter().append('circle')
.attr('cx', d => d.width )
.attr('cy', d => d.height )
.attr('r', 5)
.attr('fill', 'pink');
This will append circles to your selection corresponding to all Org instances in your orgs array which was bound to the selection using .data(orgs). The above statement also keeps a reference to the selection containing all newly appended circles in the circles variable, which you can use for later manipulation.
This reference comes in handy, when doing the transition:
circles
.transition()
.attr('cx', 0);
Have a look at the following snippet which is equivalent to your approach, but does it the D3 way.
class Org {
constructor(_width, _height) {
this.width = _width;
this.height = _height;
}
}
var orgs = d3.range(100).map(function() {
return new Org(Math.random() * screen.width, Math.random() * screen.height);
});
var canvas = d3.select('body')
.append('svg')
.attr('width', screen.width)
.attr('height', screen.height);
var circles = canvas.selectAll("circle")
.data(orgs)
.enter().append('circle')
.attr('cx', d => d.width )
.attr('cy', d => d.height )
.attr('r', 5)
.attr('fill', 'pink');
circles
.transition()
.attr('cx', 0);
<script src="https://d3js.org/d3.v4.js"></script>
You might want to have a look at some tutorials about this concept for a more in-depth introduction:
Three Little Circles
Let’s Make a Bar Chart, Parts I, II & III
Thinking with Joins
How Selections Work (advanced)
Perhaps give them an id and select them with d3?
First give the circles an id:
for (var x = 0; x < 100; x++) {
var circle = new Org(Math.random()*screen.width, Math.random()*screen.height);
orgs.push(circle);
canvas.append('circle')
.attr('id', "myCircle_" + x)
.attr('cx', circle.width)
.attr('cy', circle.height)
.attr('r', 5)
.attr('fill', 'pink');
}
Then select them by id:
for (var b = 0; b < orgs.length; b++) {
d3.select('#myCircle_'+b).transition().attr('cx', 0);
}

How can I assign every pixel in an svg a color based on some function?

What I'd like to do is color each pixel based on a function of its x and y coordinates, based on relations to other objects on the screen. I'm using d3 to do all of my other svg work but I can't figure out how to bind each pixel as the data then work off of that. Not posting code because I am looking for a very general way to do this: for instance, color each point such that (x+y)%73==0 green.
For those interested: looking back I'd recommend canvas over SVG for a task like this, but here's a kind of hacked-together solution using d3:
WIDTH = $(window).width();
HEIGHT = $(window).height();
$(document).ready( function() {
var svg = d3.select("#svg")
.attr("width", WIDTH)
.attr("height", HEIGHT);
points = []
for (x=0;x<WIDTH/10;x++) {
for (y=0;y<HEIGHT/10;y++) {
points.push([x,y]);
}
}
svg.selectAll("rect")
.data(points)
.enter().append("rect")
.attr("x", function(d) {return d[0];})
.attr("y", function(d) {return d[1];})
.attr("width",1)
.attr("height",1)
.style("fill",function(d) {
if ((d[0] + d[1])%73 == 0)
return "green";
return "black";
});
}
This assumes you have an svg element with id=svg in your html body. I've only gone through a 1/10 of the way in each dimensino for testing purposes, full size would load 100 times slower, which is pretty slow considering you really shouldn't be using an SVG for this.

Accessing Nested Array in a D3 variable

This one has got to be easy but I can't for the life of me figure out why it's not working.
I've got some D3 code to plot some circles. I've nested arrays of six numbers inside a single variable (called 'dataset'). I'm trying to access those values to use a a y-value for the circle.
var width = 600;
var height = 400;
var dataset = [[16.58, 17.90, 17.11, 17.37, 13.68, 13.95], [20.26,1 3.40, 18.63, 19.28, 20,92, 18.95], [16.32, 23.16, 21.05, 28.16, 23.68, 23.42], [31.32, 30.80, 29.37, 28.16, 32.37, 27.63], [41.32, 39.74, 29.37, 35.00, 35.53, 30.00], [25.83, 38.27, 43.33, 45.83, 44.17, 41.25]];
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d, i) {
return i * 100 + 50;
})
.attr("cy", function(d, i) {
for (var j = 0; j<6; j++){
//console.log(i,j);
return d[i][j]; //THIS IS WHERE I'M FAILING
}
})
.attr("r", 15);
So the x values are just 100 px intervals (I basically want each set in its own column). The y value of each circle should then be the j'th term in the i'th array. So why doesn't d[i][j] return that?
I've got a console.log statement commented out. If I un-commennt that everything logs just as I would expect, but the double bracket notation is clearly not accessing the numbers. If I go straight to the console in the browser and type dataset[0][1], it returns '17.90', so why doesn't it work in this implementation?
So confused.
Thanks

d3.js circles are not appearing

I'm working through this tutorial
http://flowingdata.com/2012/08/02/how-to-make-an-interactive-network-visualization/
I'm trying to just get update nodes working(my data is slightly different).
var updateNodes = function(nodes){
console.log("updateNodes Called");
console.log(nodes);
var node = nodesG.selectAll("circle.node").data(nodes,function(d){
return d.id;
});
node.enter().append("circle").attr("class","node")
.attr("cx",x)
.attr("cy",y)
.attr("r",1000)
.style("fill","steelblue")
.style("stroke","black")
.style("stroke-width",1.0);
node.exit().remove();
}
This does not make any circles appear on the DOM.
nodesG is defined as :
var nodesG = d3.selectAll("g");
and this function is called from
var update = function(){
force.nodes(data.nodes);
updateNodes(data.nodes);
//force.links(curLinksData);
//updateLinks();
//force.start();
}
Why is nothing appearing?
Thanks,
The first thing that is not working in your code is that you don't create a svg element in which you will draw your circle.
So you have to replace
var nodesG = d3.selectAll("g");
by
var nodesG = d3.select('body').append('svg');
Then, you don't define well the attributes x and y of your circles. These attributes, I guess, depend on the property x and y of each node. Thus you have to replace the following:
.attr("cx", x)
.attr("cy", y)
by:
.attr("cx", function(d){return d.x})
.attr("cy", function(d){return d.y})
Finally, you have to call update() at the end of your script
Here is a working jsFiddle: http://jsfiddle.net/chrisJamesC/S6rgv/

Categories

Resources