D3.js pattern not working with arc or donut graph - javascript

I'm trying to have an image fill my donut chart, then rotate the image 60 degrees from its center.
I've had success filling a simple shape as a pattern with this method, but the pattern image gets all screwy and repeats itself when applied to a donut chart. The image is 300px x 300px - same size as the svg. The final result should look like this.
Here's my fiddle.
imgPath = "http://www.mikeespo.com/statDonkey/inner.png";
w = 300;
h = 300;
passingPercent = 60;
rotateStartPosition = 50;
var myScale = d3.scale.linear().domain([0, 100]).range([0, 2 * Math.PI]);
// MAKES SVG
var svg = d3.select("body")
.append("svg")
.attr("id", "svg_donut")
.attr("width", w)
.attr("height", h);
// MAKE DEFS
var defs = d3.select("#svg_donut")
.append("defs");
// MAKES PATTERN
defs.append('pattern')
.attr('id', 'pic1')
.attr('width', 300)
.attr('height', 300)
.attr('patternUnits', 'userSpaceOnUse')
.append('svg:image')
.attr('xlink:href', imgPath)
.attr("width", 300)
.attr("height", 300)
.attr("transform", "rotate(60 150 150)")
.attr("x", 0)
.attr("y", 0);
// CREATES VARIABLE *VIS* TO SVG
var vis = d3.select("#svg_donut");
// DEFINES DONUT GRAPH
var arc = d3.svg.arc()
.innerRadius(95)
.outerRadius(140)
.startAngle((myScale(0 + rotateStartPosition)))
.endAngle((myScale(passingPercent + rotateStartPosition)));
// APPENDS *VIS* TO SVG
vis.append("path")
.attr("id", "passing")
.attr("d", arc)
.attr("fill", "white")
.attr("transform", "translate(150,150)")
.attr("fill", "url(#pic1)");

I'm not exactly sure why this works to be honest but when I changed the width and height of the pattern element and removed the patternUnits attribute, I was able to achieve the desired look:
defs.append('pattern')
.attr('id', 'pic1')
.attr('width', 1)
.attr('height', 1)
.append('svg:image')
.attr('xlink:href', imgPath)
.attr("width", 300)
.attr("height", 300)
.attr("transform", "rotate(00 150 150)")
.attr("x", 0)
.attr("y", 0);
I don't understand it completely but it has something to do with the coordinate system and the way in which the pattern scales to the object you're applying it to. The width and height aren't defining the size of the image as you might initially think, but the way in which the pattern will map to the new coordinate system of the donut. A width and height of 1 indicates that the pattern will just scale to the width and height of the donut.
Getting my info from here and admittedly not fully grasping it all yet but hopefully this will help: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Patterns

Related

D3 Convention for (re)drawing a single object

I'm using D3 to draw a custom graphs with a border rectangle around it. On window resize, I recalculate the SVG size and want to redraw this border. I'm currently doing this in a drawGraph() function which gets called on resize / new data:
...
svg.selectAll('rect')
.data([true])
.attr('width', w)
.attr('height', h)
.enter()
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', w)
.attr('height', h)
.style('fill', 'none')
...
i.e. - I am binding a single value in an array ([true]) to the selection so that I can either draw the box, or just change its size if it's already been drawn once. It certainly works, but it feels slightly odd to bind data which is guaranteed to be ignored, and possibly a slightly hacky way of doing things.
Is this a well-used convention, or is there another standard way of doing this, such as:
A D3 method which I've not come across
Just using svg.append('rect') and then removing + redrawing it on resize
svg.selectAll('*').remove() and redrawing everything from scratch?
Anything else
Removing and redrawing elements is what I call lazy coding or lazy update in D3.
That being said, why don't you simply assign the rectangle to a variable and change it on resize? Something like this (click "full page" and resize the window):
var div = document.getElementById("div");
var svg = d3.select("svg")
var rect = svg.append("rect")//here you define your rectangle
.attr("width", 300)
.attr("height", 150)
.attr("stroke-width", 3)
.attr("stroke", "black")
.attr("fill", "none");
function resize() {
var width = div.clientWidth;
svg.attr("width", width)
.attr("height", width / 2);
//here you just change its width and height
rect.attr("width", width)
.attr("height", width / 2);
}
window.addEventListener("resize", resize);
svg {
background-color: tan;
}
<script src="//d3js.org/d3.v4.min.js"></script>
<div id="div">
<svg></svg>
</div>

d3 connect arc and point with a ribbon

Given a set of arcs that make up a circle and random points generated inside of said circle, what's the best way to generate an area or chord that connects a slice of the array to one of the points and not just the exact center?
I was thinking that a ribbon or chord layout would be helpful here but the chord layout seems specific to connecting arcs (though admittedly I've only spent about two days researching it and am struggling with actual usage)
Right now I have a simple arbitrary arc and circle as such -
var width = 1000;
var height = 600;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
let arc = svg.append("path")
.datum({
id: 1,
startAngle: 0,
endAngle: .50 * (2 * Math.PI)
})
.style("fill", "blue")
.attr("d", d3.arc()
.innerRadius(180)
.outerRadius(200))
let circle = svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("gradientUnits", "objectBoundingBox")
.attr("r", 20);
Simple fiddle - http://jsfiddle.net/968o4s9m/
Seems the best way is to draw a path manually by entering all points using lines and arcs. Ribbons do not appear to be able to connect arbitrary points and arcs from what I can tell.

Added image to DOM element in d3 but now it won't transition

I'm trying to get an element with an appended image to transition using the d3.js library; I had successfully achieved this just using a plain circle that transitioned nicely around the screen but now that I've added a png the transition doesn't happen - the png does appear though when the page is refreshed, it just won't move like it did before! My code is below.. your help is appreciated!
<script>
var data = [60, 120, 40, 710, 560, 850];
var data1 = data[0];
var canvas = d3.select("body").append("svg")
.attr("width", 2000)
.attr("height", 2000);
var imgs = canvas.append("image")
.attr("xlink:href", "AWT-Bus.png")
.attr("x", "60")
.attr("y", "60")
.attr("width", "20")
.attr("height", "20")
.attr("cx", 50)
.attr("cy", 200)
.attr("r", 20)
;
imgs.transition()
.duration(data1*100)
.delay(2000)
.attr("cx", 200)
.transition()
.attr("cx", 50)
.attr("cy", 200)
.transition()
.attr("cx", 150)
.attr("cy", 300)
;
The attributes you are changing in your code (cx and cy) are applicable to circles which are described by the x and y co-ordinates of their center (cx and cy) plus the radius (r). This is why your circle example worked.
But images are described by their width, height and the x and y co-ordinates of the upper-left corner of the box (using x and y attributes as shown below).
Different svg elements have different attributes which describe their dimensions and their location on the page, so you need to be aware of the different attributes that each type of element has, perhaps using a reference such as https://developer.mozilla.org/en-US/docs/Web/SVG/Element. Then you can animate your svg element using transition as you have done in your code and changing the value of the appropriate attribute.
var canvas = d3.select("body").append("svg")
.attr("width", 2000)
.attr("height", 2000);
var imgs = canvas.append("image")
.attr("xlink:href", "AWT-Bus.png")
.attr("x", "60")
.attr("y", "60")
.attr("width", "20")
.attr("height", "20");
imgs.transition()
.duration(2000)
.delay(1000)
.attr("x", 200)
.transition()
.attr("x", 50)
.attr("y", 200)
.transition()
.attr("x", 150)
.attr("y", 300);

How to style SVG path to take an image?

Here is my JSFiddle.
I am simply trying to set up this image in the middle of my arc. My best intuition tells to use .attr("fill","url('somePicture')"), but for the life of me that hasn't been a viable solution.
var width = 700,
height = 600,
tau = 2 * Math.PI
var arc = d3.svg.arc()
.innerRadius(100)
.outerRadius(250)
.startAngle(0)
var arc2 = d3.svg.arc()
.innerRadius(0)
.outerRadius(100)
.startAngle(0)
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")")
//gray null background
var background = svg.append("path")
.datum({endAngle: tau})
.style("fill", "#ddd")
.attr("d", arc)
var center = svg.append("image")
.append("path")
.datum({endAngle: tau})
.attr("d", arc2)
.attr("class","record")
.attr("xlink:href", "http://lorempixel.com/g/400/400/")
You don't need to define a path. If you look into your html, the image is there but it's of size 0x0.
var center = svg.append("image")
.datum({endAngle: tau})
.attr("d", arc2)
.attr("class","record")
.attr("width",400)
.attr("height",400)
.attr("x",-200)
.attr("y",-200)
.attr("xlink:href", "http://cdn.mysitemyway.com/etc-mysitemyway/icons/legacy-previews/icons/glossy-black-icons-symbols-shapes/018712-glossy-black-icon-symbols-shapes-shapes-circle.png")
In your fiddle you attached the wrong image. If you keep your code the same aside from this it should work. Good luck.
If I understand you correctly, you mean that if you want to resize the whole thing, then the image will change with it. Correct? You can do that by making all numbers functions of others.
I start with defining
innerR = 100
for lack of a better name. Then all other non-zero functions are functions of that. There is a fiddle here. Experiment with that parameter and see what happens.

Appending SVG Elements in D3

Going through D3.js tutorials, I see two different conventions, and I am not sure what the difference is because they both product the same code:
1)
var chart = d3.select("body")
.append("svg:svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart.selectAll("rect")
.data(data)
.enter().append("svg:rect")
2)
var chart = d3.select("body")
.append("svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart.selectAll("rect")
.data(data)
.enter().append("rect")
Can someone explain the difference between append("svg") and append("svg:svg") and why I would use one or the other?
The svg: part specifies the name space for the element that comes after it, i.e. an svg element in the svg namespace. It was advisable to specify this in older versions of d3 as it might be interpreted incorrectly otherwise, but it is not necessary to do so in recent versions.

Categories