My goal is to add an image into an existing circle with d3. The circle will render and is interactive with mouseover method, but only when I use "fill", "color", and not something more sophisticated like .append("image").
g.append("circle")
.attr("class", "logo")
.attr("cx", 700)
.attr("cy", 300)
.attr("r", 10)
.attr("fill", "black") // this code works OK
.attr("stroke", "white") // displays small black dot
.attr("stroke-width", 0.25)
.on("mouseover", function(){ // when I use .style("fill", "red") here, it works
d3.select(this)
.append("svg:image")
.attr("xlink:href", "/assets/images/logo.jpeg")
.attr("cx", 700)
.attr("cy", 300)
.attr("height", 10)
.attr("width", 10);
});
The image doesn't show after I mouse over. Using Ruby on Rails app, where my image "logo.jpeg" is stored in the assets/images/ directory. Any help for getting my logo to show within the circle? Thanks.
As Lars says you need to use pattern, once you do that it becomes pretty straightforward. Here's a link to a conversation in d3 google groups about this. I've set up a fiddle here using the image of a pint from that conversation and your code above.
To set up the pattern:
<svg id="mySvg" width="80" height="80">
<defs id="mdef">
<pattern id="image" x="0" y="0" height="40" width="40">
<image x="0" y="0" width="40" height="40" xlink:href="http://www.e-pint.com/epint.jpg"></image>
</pattern>
</defs>
</svg>
Then the d3 where we only change the fill:
svg.append("circle")
.attr("class", "logo")
.attr("cx", 225)
.attr("cy", 225)
.attr("r", 20)
.style("fill", "transparent")
.style("stroke", "black")
.style("stroke-width", 0.25)
.on("mouseover", function(){
d3.select(this)
.style("fill", "url(#image)");
})
.on("mouseout", function(){
d3.select(this)
.style("fill", "transparent");
});
nodeEnter.append("svg:image")
.attr('x',-9)
.attr('y',-12)
.attr('width', 20)
.attr('height', 24)
.attr("xlink:href", function(d) {
if(d.type=="root"){
return "resources/images/test.png";}
else if(d.type.toLowerCase()=="A"){
return "resources/icon01/test1.png";}
else if(d.type=="B"){
return "resources/icon01/test2.png";}
})
.append("svg:title")
.text(function(d){
if(d.type=="root"){
return "Root Name:"+d.name;}
else if(d.type=="test"){
return "Name:"+d.name;}
});
Related
I'm trying to drag a group of shapes on a clipped path. For the first time, It works fine, but as soon as I started dragging, clipping does not work at all.
Here is my working code;
var svg = d3.select("svg");
// draw a circle
svg.append("clipPath") // define a clip path
.attr("id", "clip") // give the clipPath an ID
.append("circle") // shape it as an ellipse
.attr("cx", 100) // position the x-centre
.attr("cy", 80) // position the y-centre
.attr("r", 80) // set the x radius
.attr("fill", "red")
var g = svg.append("g")
.datum({x:0, y:0})
.attr("transform", function(d) { return 'translate(' + d.x + ' '+ d.y + ')'; })
.attr("clip-path","url(#clip)")
.call(d3.drag()
.on("start", function(d){
d3.select(this).raise().classed("active", true);
})
.on("drag", function(d){
d3.select(this).attr("transform","translate(" + (d3.event.x) + "," + (d3.event.y) + ")" );
})
.on("end", function(d){
d3.select(this).classed("active", false);
}));
g.append("rect")
.attr("x",100)
.attr("y",80)
.attr("height",100)
.attr("width",200)
g.append("line")
.attr("x1", 100)
.attr("y1", 80)
.attr("x2", 200)
.attr("y2", 80)
.style("stroke", "purple")
.style("stroke-width", 12)
.svgClass{
border:2px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg width="500" height="300" class="svgClass"></svg>
You can see on dragging, first time clipped shape is moving all the way. No further clipping is there.
To make it easy, I redraw the outer circle again. Check this code;
var svg = d3.select("svg");
// draw a circle
svg.append("clipPath") // define a clip path
.attr("id", "clip") // give the clipPath an ID
.append("circle") // shape it as an ellipse
.attr("cx", 100) // position the x-centre
.attr("cy", 80) // position the y-centre
.attr("r", 80) // set the x radius
.attr("fill", "red")
// redraw circle to make it easy
svg.append("circle") // shape it as an ellipse
.attr("cx", 100) // position the x-centre
.attr("cy", 80) // position the y-centre
.attr("r", 80) // set the x radius
.attr("fill", "red")
var g = svg.append("g")
.datum({x:0, y:0})
.attr("transform", function(d) { return 'translate(' + d.x + ' '+ d.y + ')'; })
.attr("clip-path","url(#clip)")
.call(d3.drag()
.on("start", function(d){
d3.select(this).raise().classed("active", true);
})
.on("drag", function(d){
d3.select(this).attr("transform","translate(" + (d3.event.x) + "," + (d3.event.y) + ")" );
})
.on("end", function(d){
d3.select(this).classed("active", false);
}));
g.append("rect")
.attr("x",100)
.attr("y",80)
.attr("height",100)
.attr("width",200)
g.append("line")
.attr("x1", 100)
.attr("y1", 80)
.attr("x2", 200)
.attr("y2", 80)
.style("stroke", "purple")
.style("stroke-width", 12)
.svgClass{
border:2px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg width="500" height="300" class="svgClass"></svg>
Here You can see clipping is not working at all. I want to bound this dragging within circle and if moves out of clipping boundaries, it should clip it accordingly.
Can anyone help me out with this requirement? Or let me know where I'm
doing it wrong.
The drag callback is transforming the same g element that the clip path has been applied to. This means that the g element's clip path is also being transformed, which is why the clipped shape is moving around as you drag your shape.
The snippet below uses a grey rectangle to show the clip path definition, and a pink rectangle to show the area of the transformed g element. The circle is retaining the original clip shape because the g element's clip path is being translated along with the rest of the element.
<svg width="300" height="300">
<clipPath id="cut">
<rect width="100" height="100" x="100" y="50"></rect>
</clipPath>
<rect x="100" y="50" width="100" height="100" fill="#eee"></rect>
<g clip-path="url(#cut)" transform="translate(50, 50)">
<rect x="100" y="50" width="100" height="100" fill="pink"></rect>
<circle
class="consumption"
cx="100"
cy="100"
r="50">
</circle>
</g>
</svg>
In the snippet below, a clip path is applied to an outer g element (which is not translated and has the same co-ordinates as the original clip path definition), while the transformation is applied to an inner g element.
<svg width="300" height="300">
<clipPath id="cut">
<rect width="100" height="100" x="100" y="50"></rect>
</clipPath>
<rect x="100" y="50" width="100" height="100" fill="#eee"></rect>
<g clip-path="url(#cut)">
<rect x="100" y="50" width="100" height="100" fill="pink"></rect>
<g transform="translate(100, 50)">
<circle
class="consumption"
cx="100"
cy="100"
r="50">
</circle>
</g>
</g>
</svg>
So, as shown in the example you should apply the clip path to an outer g element, while transforming an inner g element.
I have tried a code but it dosent work properly. Can you suggest a method to resolve the error. I am new to Visualization and at the beginning stage of d3.js
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"> </script>
</head>
<body>
<div id="viz"></div>
<script type="text/javascript">
var sampleSVG = d3.select("#viz")
.append("svg")
.attr("width", 100)
.attr("height", 100);
sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 50)
.attr("cy", 50)
.on("mouseover", function() {d3.select(this).append("text").attr("fill","blue").text("fill aliceblue");})
</script>
</body>
</html>
Your are trying to append the text element to the circle, which is not possible.
Create a group element and add your circle and text elements to that group.
Here is an example.
var sampleSVG = d3.select("#viz")
.append("svg")
.attr("width", 100)
.attr("height", 100);
var grp = sampleSVG.append("g");
var circle = grp.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 50)
.attr("cy", 50);
circle.on("mouseover", function() {
grp.append("text")
.style("fill", "black")
.attr("x", 32)
.attr("y", 52)
.text("Hello");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="viz"></div>
As suggested here: https://stackoverflow.com/a/16780756/1023562
Create a tooltip div and attach it to mouseover, mousemove and mouseout events.
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip");
sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 50)
.attr("cy", 50)
.on("mouseover", function(){return tooltip.style("visibility", "visible");})
.on("mousemove", function(){return tooltip.style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
Working fiddle: https://jsfiddle.net/vc95wcvm/
Note that in this Fiddle I have loaded http://d3js.org/d3.v2.js in the script panel (because Fiddle refuses to load it over http, it requires https), so the code that interests you is at the bottom of script panel.
I'm working on some D3 visualizations and what I've found is that I'm having to define a lot of styles in my code - that really I'd prefer to have just in my CSS.
The reason for doing this is simply to support transitions. I've found that I can run a transition from a style applied in CSS to an inline one, but then I can't remove that style back to the original. Instead all of them need to be in-line. Like in the following example:
var svg = d3.select("svg");
var c1 = svg.append("circle")
.attr("class", "red")
.attr("r", 25)
.attr("cx", 50)
.attr("cy", 50);
var c2 = svg.append("circle")
.attr("r", 25)
.attr("cx", 250)
.attr("cy", 50)
.style("fill", "red");
svg.selectAll("circle")
.transition()
.delay(2000)
.duration(2000)
.style("fill", "blue");
c1.transition()
.delay(5000)
.duration(2000)
.style("fill", "");
c2.transition()
.delay(5000)
.duration(2000)
.style("fill", "red");
.red {
fill: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="500" height="500">
</svg>
The circle on the left jumps straight back to red, while the one on the right transitions back.
What I'd like to do is transition the left circle back, without having to re-define the original colour I'm using from CSS in my Javascript code.
Does anyone know of an elegant way to achieve this?
So using Gilsha's answer I managed to figure out that you can actually grab the original CSS style later on so you don't need to save it. Seems even when the colour is blue, I can go back and grab the red colour:
c1.transition()
.delay(5000)
.duration(2000)
.style("fill", function(d) {
var selection = d3.select(this);
var currentStyle = selection.style("fill");
var defaultStyle = selection.style("fill", null).style("fill");
selection.style("fill", currentStyle");
return defaultStyle;
});
var svg = d3.select("svg");
var c1 = svg.append("circle")
.attr("class", "red")
.attr("r", 25)
.attr("cx", 50)
.attr("cy", 50);
var c2 = svg.append("circle")
.attr("r", 25)
.attr("cx", 250)
.attr("cy", 50)
.style("fill", "red");
svg.selectAll("circle")
.transition()
.delay(2000)
.duration(2000)
.style("fill", "blue");
c1.transition()
.delay(5000)
.duration(2000)
.style("fill", function(d) {
var selection = d3.select(this);
var currentStyle = selection.style("fill");
var defaultStyle = selection.style("fill", null).style("fill");
selection.style("fill", currentStyle);
return defaultStyle;
});
c2.transition()
.delay(5000)
.duration(2000)
.style("fill", "red");
.red {
fill: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="500" height="500">
</svg>
Try this code.
var color = c1.style("fill");
var svg = d3.select("svg");
var c1 = svg.append("circle")
.attr("class", "red")
.attr("r", 25)
.attr("cx", 50)
.attr("cy", 50);
var c2 = svg.append("circle")
.attr("r", 25)
.attr("cx", 250)
.attr("cy", 50)
.style("fill", "red");
//Get fill color from css
var color = c1.style("fill");
svg.selectAll("circle")
.transition()
.delay(2000)
.duration(2000)
.style("fill","blue");
c1.transition()
.delay(5000)
.duration(2000)
.style("fill", color);
c2.transition()
.delay(5000)
.duration(2000)
.style("fill", "red");
.red {
fill: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="500" height="500">
</svg>
I have some code that create a bunch of circles, updates those circles, removes some of the circles, and lastly I'd like to find out how to use the enter() correctly to add more circles to my current ones.
This is my code for creating circles when there are none:
var circle = SVGbody
.selectAll("circle")
.data(graphDataY/*, function(d){return d;}*/);
circle.enter()
.append("circle");
circle
.attr("cx",xScale(0))
.attr("cy", yScale(minAxisY))
.attr("r",4)
.style('opacity', 0)
.style("fill", colorCircles())
.transition()
.duration(1000)
.attr("cx", function(d, i){ return xScale(graphDataX[i]);})
//.attr("cy", function (d, i){ return yScale(i); })
.style('opacity', 0.8)
.transition()
.duration(1000)
.attr("cy", function(d){return yScale(parseFloat(d));} );
I use the following code to update my circles and remove the ones that aren't being used (sometimes that's the case):
var circle = SVGbody
.selectAll("circle")
.data(graphDataY);
circle
.style("fill", colorCircles())
.transition().duration(1000)
.attr("cx", function(d, i){ return xScale(graphDataX[i]);})
.attr("cy",function(d){return yScale(parseFloat(d));});
circle.exit()
.style("opacity", 1)
.transition()
.duration(300)
.style("opacity", 0)
.remove();
And when lastly, let's assume the last data i used had a length of 100, that means I currently have 100 circles displayed. If I used a new dataset with 120 of length, I use my update code to move the existing circle ( except for the exit().remove() ), and then I try to use the following to "create" the remaining circles out of the remaining unbound data (which in this case would be 20 circles remaining):
circle.enter().append("circle")
.attr("cx", function(d, i){ return xScale(graphDataX[i]);})
.attr("cy",function(d){return yScale(parseFloat(d));})
.style("opacity", 0)
.transition()
.duration(300)
.style("opacity", 1)
I'm putting together a choropleth map in D3 of U.S. States and already have the code to render the map with solid colored backgrounds using fill.
However, I want the fill of the state paths to be an image. I tried setting the path background-image to the img's URL, but that didn't work.
How can I do this? I'd also like to fill each state a different image.
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("class", "background")
.attr("fill","none")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("class", "states");
$(document).ready(function() {
d3.json("us-states.json", function(json) {
var features = json.features;
g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.attr("fill", "purple")
.attr("stroke","white")
.attr("stroke-width", 1);
});
});
As Superboggly points out, first define a pattern, then use it.
Definition example:
<defs>
<pattern id="chloroplethbackground" patternUnits="userSpaceOnUse" width="??" height="??">
<image xlink:href="bg.jpg" x="0" y="0" width="??" height="??" />
</pattern>
</defs>
Then set your fill to url(#chloroplethbackground)
So it will be:
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("class", "background")
.attr("fill","none")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("class", "states");
$(document).ready(function() {
d3.json("us-states.json", function(json) {
var features = json.features;
g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.attr("fill", "url(#chloroplethbackground)")
.attr("stroke","white")
.attr("stroke-width", 1);
});
});