How to style SVG path to take an image? - javascript

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.

Related

Add border-radius property to D3js Donut Chart

I have this donut chart currently working in an AngularJS app:
But the design mockup says we would like this, note the border-radius property on the green portion of the arc:
How do I add a border-radius to the SVG that d3js outputs, the code I'm currently using looks like this:
let data = [
{
label: 'Data',
count: scope.data
},
{
label: 'Fill',
count: 100 - scope.data
}
];
let width = 60;
let height = 60;
let radius = Math.min(width, height) / 2;
let color = d3.scale
.ordinal()
.range(['#3CC692', '#F3F3F4']);
let selector = '#donut-asset-' + scope.chartId;
d3
.select(selector)
.selectAll('*')
.remove();
let svg = d3
.selectAll(selector)
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr(
'transform',
'translate(' + width / 2 + ',' + height / 2 + ')'
);
let arc = d3.svg
.arc()
.innerRadius(23)
.outerRadius(radius);
let pie = d3.layout
.pie()
.value(function(d) {
return d.count;
})
.sort(null);
let path = svg
.selectAll('path')
.data(pie(data))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return color(d.data.label);
});
let legend = svg
.selectAll('.legend')
.data(data)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
return 'translate(' + 0 + ',' + 0 + ')';
});
legend
.append('text')
.attr('x', 1)
.attr('y', 1)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'central')
.text(function(d) {
return d.count + '%';
});
};
I know to use cornerRadius but when I do it sets a radius for both arcs, it just needs to exist on the colored one. Any ideas? Thanks in advance for any help!
You can apply a corner radius to a d3 arc which allows rounding on the corners:
let arc = d3.svg
.arc()
.innerRadius(23)
.outerRadius(radius)
.cornerRadius(10);
But, the downside is that all arcs' borders are rounded:
If you apply the cornerRadius to only the darkened arc - the other arc won't fill in the background behind the rounded corners. Instead, we could append a circular arc (full donut) and place the darkened arc on top with rounding (my example doesn't adapt your code, just shows how that it can be done, also with d3v4 which uses d3.arc() rather than d3.svg.arc() ):
var backgroundArc = d3.arc()
.innerRadius(30)
.outerRadius(50)
.startAngle(0)
.endAngle(Math.PI*2);
var mainArc = d3.arc()
.innerRadius(30)
.outerRadius(50)
.cornerRadius(10)
.startAngle(0)
.endAngle(function(d) { return d/100*Math.PI* 2 });
var data = [10,20,30,40,50] // percents.
var svg = d3.select("body").append("svg")
.attr("width", 600)
.attr("height", 200);
var charts = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform",function(d,i) {
return "translate("+(i*100+50)+",100)";
});
charts.append("path")
.attr("d", backgroundArc)
.attr("fill","#ccc")
charts.append("path")
.attr("d", mainArc)
.attr("fill","orange")
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Try playing with stroke attributes like:
stroke
stroke-dasharray
stroke-dashoffset
stroke-linecap
stroke-linejoin
stroke-miterlimit
stroke-opacity
stroke-width
And set width of bar to lower values, or 0.
Reference: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
But the better way is to make charts on canvas, because you can draw everything you want. Or to use an library.

In D3 v4, how do I keep the width of a bar centered around a tick mark?

I am using D3 v4 (most, if not all, of the examples out there are for v3). Back in v3, they had something called rangeBand() which would be able to dynamically position everything neatly on the x-axis for me.
Now, in v4, I am wondering how to do that.
I have a bar chart:
var barEnter = vis.selectAll("g")
.data(rawdata)
.enter()
.append('g')
.append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("width", canvas_width / rawdata.length);
It is the width of this bar this is messing me up. If I set it to canvas_width / rawdata.length, it nicely positions the bars centered around each tick on the x-axis. The problem is that all the bars are pressed together and there is no padding in between.
So, naturally, I tried to do x.paddingInner(.5) which does add some padding but now the bars are not centered around the tick marks. Doing anything with x.paddingOuter() messes things up even more.
After searching around, I found that rangeBand() is what I want but that's only for v3. In the v4 docs, there is nothing that quite looks like it. Is it rangeRound()? Is it align()? I'm not sure. If anyone can point me in the right direction, it'd be greatly appreciated.
Without seeing your code for the axis, I suppose you're using scaleOrdinal(). If that's the case, you can change for scaleBand(), in which it's very easy to center the bar around the tick.
All you need is:
band.paddingInner([padding]): Sets the inner padding of the bars
band.bandwidth(): Gives you the bandwidth of each bar.
Then, you set the x position using the corresponding variable in your data and the width using bandwidth().
This is a small snippet to show you how it works:
var w = 300, h = 100, padding = 20;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var data = [{name: "foo", value:50},
{name: "bar", value:80},
{name: "baz", value: 20}];
var xScale = d3.scaleBand()
.range([0,w])
.domain(data.map(function(d){ return d.name}))
.paddingInner(0.2)
.paddingOuter(0.2);
var xAxis = d3.axisBottom(xScale);
var bars = svg.selectAll(".bars")
.data(data)
.enter()
.append("rect");
bars.attr("x", function(d){ return xScale(d.name)})
.attr("width", xScale.bandwidth())
.attr("y", function(d){ return (h - padding) - d.value})
.attr("height", function(d){ return d.value})
.attr("fill", "teal");
var gX = svg.append("g")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>

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.

D3.js pattern not working with arc or donut graph

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

Drawing states separately with d3 js and country topojson file

I have a topojson which contains state's paths. I want the user to be able to hover over a state and the state to appear in a different svg. So far, I've tried to extract the geometry out of the topojson (d.geometry , d.geometry.coordinates etc) But I'm not able to do it.
Maybe I need to draw a polygon out of that, but some states are of type "Polygon" and some of them are of type "MultiPolgyon".
Any ideas/suggestions?
Edit : Here's my code
var svg = d3.select("#india-map")
.append("svg")
.attr("width",width).attr("preserveAspectRatio", "xMidYMid")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("height", height)
var stateSvg = d3.select("#state-map")
.append("svg")
.append("g")
.attr("height", height)
.attr("width", width);
var g = svg.append("g");
var projection = d3.geo.mercator()
.center([86, 27])
.scale(1200);
var path = d3.geo.path().projection(projection);
var pc_geojson = topojson.feature(pc, pc.objects.india_pc_2014);
var st_geojson = topojson.feature(state_json, state_json.objects.india_state_2014);
g.selectAll(".pc")
.data(pc_geojson.features)
.enter().append("path")
.attr("class", "pc")
.attr("d", path)
.attr("id", function(d){ return d.properties.Constituency;})
.attr("fill", "orange")
.on("click", function(d){
drawCons(d);
});
function drawCons(d){
stateSvg.selectAll(".pc2")
.data(d)
.enter().append("path")
.attr("class","pc2")
.attr("d", path)
}
.data() expects to be given an array of objects to be matched against the selection. You're passing a single object, so it doesn't work. You can either use .datum(d) or .data([d]) to make it work.
Quick and dirty demo here.

Categories