How to draw gradient arc using d3.js? - javascript

I have started using d3.js. I have following requirement
Requirement:
What I have tried?
fiddle
Question?
How to achieve gradient as same as above image.
Any suggestion or idea will be grateful.
Note
I am just started d3.js.

Edit - changed data structure and fiddle link to represent unfilled chunk at the beginning.
I would use the pie function in d3 to create a pie chart.
The image above is basically a pie with two different gradient styles applied to the pie chunks.
A red linear gradient and a black/white radial gradient.
I created a fiddle linked below to show you an example.
The key here is that you need to structure your data to also include the percentage that should not have the red-gradient applied.
Using the example above, we have three chunks with red and the rest as unfilled.
Imagine the data set like so:
var data = [{
percent: 10,
pie: 0
}, {
percent: 13,
pie: 1
}, {
percent: 13,
pie: 1
}, {
percent: 6,
pie: 1
}, {
percent: 56,
pie: 0
}];
So we have the percent and we also flag which chunks should be red and which chunk should be the unfilled section using the pie attribute.
You can use whatever data set you wish but I'm just using this as an example.
So next thing is to create your SVG element:
var width = 400;
var height = 400;
var radius = Math.min(width, height) / 2;
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(((radius - 10) / 5) * 4);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.percent });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
and after this we will create the two gradients to style the pie chunks.
So first one is the linear red gradient:
// append a defs tag to SVG, This holds all our gradients and can be used
//by any element within the SVG we append it to
var defs = svg.append("svg:defs")
//next we append a linear gradient
var red_gradient = defs.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "0%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
//first dark red color
red_gradient.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "rgb(221,48,2)")
.attr("stop-opacity", 1);
//second light red color
red_gradient.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "rgb(247, 78, 1)")
.attr("stop-opacity", 1);
Then we append the radial gradient for the unfilled part. This one is a little tricker because we need to move the gradient with a transform to get the right radial center. If you translate it half the width and height I think it should work out.
var radial_gradient = defs.append("radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("cx", '50%')
.attr("cy", '50%')
.attr("r", "75%")
.attr("fx", '50%')
.attr("fy", '50%')
.attr('gradientTransform', "translate(-200,-200)")
.attr("id", 'gradient2');
radial_gradient.append("stop").attr("offset", "0%").style("stop-color", "black");
radial_gradient.append("stop").attr("offset", "55%").style("stop-color", "white");
radial_gradient.append("stop").attr("offset", "95%").style("stop-color", "black");
Once we have set up the gradients, we can add the pie:
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
// we create a function to append the different chucks of the pie.
// we check the pie attribute from the data and apply the correct gradient.
g.append("path")
.attr("d", arc)
.style("fill", function (d) {
if (d.data.pie === 1) {
console.log('true' + d.data.pie);
return "url(#gradient)"
}
else {
console.log('false' + d.data.pie);
return "url(#gradient2)"
}
})
JSFiddle: http://jsfiddle.net/staceyburnsy/afo292ty/2/

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.

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.

How to add background shading to D3 line chart

I would like to add shading to the background of a D3 line graph. There would be different shades for different parts of the line. Here is an example
My approach is the add rectangle svg to the chart, but that doesn't seem to be working because I don't know how to make the width correspond with the data.
here is a jsfiddle
Here is an example of the rectangle creation:
svg.append("rect")
.attr("class", "shading")
.attr("x", d[1].date)
.attr("y", 80)
.attr("width", 20)
.attr("height", 20)
.attr("fill", "blue");
Am I on the right track? How do I find the width so that it corresponds with the data?
UPDATE: There will be multiple square of different widths, so I can't just grab the width of the entire svg.
You can do it like this:
//get all the ticks in x axis
//make a pair of it refer: d3.pair
var data = d3.pairs(svg.selectAll(".x .tick").data());
//make a color category
var c10 = d3.scale.category10();
//to svg append rectangles
svg.selectAll("rect")
.data(data)//for the tick pair
.enter()
.append("rect")
.attr("class", "shading")
.attr("x", function(d){return x(d[0])})//x will be the 1st tick
.attr("y", 0)
.attr("width", function(d){return (x(d[1]) - x(d[0]));})//width will be the diff of 1st and 2nd tick
.attr("height", height)
.attr("opacity", 0.2)
.attr("fill", function(d,i){return c10(i)});//use color category to color the rects.
working code here
fill the svg first with the data, then after that get the width property, it should automatically calculate it

How to do stroke-opacity:0 with d3

Here is my code. My target - do intervals between slices in pie chart.
chart.svg.selectAll('path')
.style('stroke-opacity','0.0')
.style('stroke-width','10');
I think if stroke opacity will be 0 on piechart slices on web page it will be similar to interval between slices.
Problem: if stroke opacity equals to zero that doesn't work. If equals to number from 0.1 to 1.0 - all works. But I have another color from background.
Please give a hand to beginner! Thanks for attention and have a nice day.
I believe the problem comes from the misconception that, when you set stroke-opacity to 0, the stroke will be transparent and reveal the background colour, and the fill of the element will end at the internal limits of the stroke. But, in fact, if you set the stroke-opacity to 0, you'll reveal the fill of the element (and the background colour, once the stroke goes inwards and outwards in the default stroke-alignment).
Look, for instance, at this example:
var svg = d3.select("body")
.append("svg")
.attr("width", 300)
.attr("height", 300);
var color = d3.scale.category10();
data = [10, 20];
var rects = svg.selectAll(".rect")
.data(data)
.enter()
.append("rect");
rects.attr("x", function(d){ return d*10})
.attr("y", 0)
.attr("width", 100)
.attr("height", 80)
.attr("fill", function(d){ return color(d)})
.attr("stroke-width", 10)
.attr("stroke", "white")
.attr("stroke-opacity", 0);
var rects2 = svg.selectAll(".rect2")
.data(data)
.enter()
.append("rect");
rects2.attr("x", function(d){ return d*10})
.attr("y", 100)
.attr("width", 100)
.attr("height", 80)
.attr("fill", function(d){ return color(d)})
.attr("stroke-width", 10)
.attr("stroke", "white");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
Both the pairs of rectangles are absolutely equal:
rects.attr("x", function(d){ return d*10})
.attr("y", 0)
.attr("width", 100)
.attr("height", 80)
.attr("fill", function(d){ return color(d)})
.attr("stroke-width", 10)
.attr("stroke", "white")
The only difference is that, in the upper pair, I add:
.attr("stroke-opacity", 0);
And that is the same of having no stroke.
You can see that, independent of the stroke alignment, the area and the size of the element is the same. Check the default stroke:
The rect element, outlined by a black line, remains the same.
To finish, I just found this fiddle (I don't know who's the author), and I set the stroke to white and stroke-width to 10: this is what you want, imitating a real padding. But you'll not get this result setting the stroke opacity to 0: https://jsfiddle.net/j1769sx2/

d3 path gradient stroke

Hello I am working with d3 diagonal diagram and would like to add a gradient to path which links my circles...
I am generating my tree with:
var width = 800,
height = 700;
element.html('');
var color = d3.interpolateLab("#008000", "#c83a22");
var scale = d3.scale.linear().domain([0, 100]).range(["red", "green"]);
var cluster = d3.layout.cluster()
.size([height, width - 160]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select('#tab-manageAccess').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(40,0)');
/*svg.append("linearGradient")
.attr("id", "line-gradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0).attr("y1", y(0))
.attr("x2", 0).attr("y2", y(1000))
.selectAll("stop")
.data([
{offset: "0%", color: "red"},
{offset: "40%", color: "red"},
{offset: "40%", color: "black"},
{offset: "62%", color: "black"},
{offset: "62%", color: "lawngreen"},
{offset: "100%", color: "lawngreen"}
])
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });*/
var nodes = cluster.nodes(scope.accessTree),
links = cluster.links(nodes);
var link = svg.selectAll('.link')
.data(links)
.enter().append('path')
.attr('class', 'link')
.attr('d', diagonal);
var node = svg.selectAll('.node')
.data(nodes)
.enter().append('g')
.attr('class', 'node')
.attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; });
node.append('circle')
.attr('r', 4.5);
node.append('text')
.attr('dx', function(d) { return d.children ? -8 : 8; })
.attr('dy', 3)
.style('text-anchor', function(d) { return d.children ? 'end' : 'start'; })
.style('font-weight', 'bold')
.attr('fill', function (d) {
var color = '#4D7B88';
if (d.depth === 0) {
color = '#7F3762';
} else if(d.depth === 1) {
color = '#83913D';
}
return color;
})
.text(function(d) { return d.name; });
d3.select(self.frameElement).style('height', height + 'px');
I found this example: https://gist.github.com/mbostock/4163057 co I created variable color with d3.interpolateLab("#008000", "#c83a22"); and then added .style("fill", function(d) { return color(d.t); })
.style("stroke", function(d) { return color(d.t); }) to path element but it doesn't work :( can anyone help me?
The aspect of Mike Bostock's code that you're missing is where he divides the path up into hundreds of different sub-paths and sets the color on each one separately. Go to the live version at http://bl.ocks.org/mbostock/4163057 and check the DOM to see what's really going on.
Why does he do that? Because, while you can set the stroke of an SVG line or path to a gradient, you can't tell it to make the gradient follow the slope or curve of that line. The angle of the gradient is defined when the gradient is created, based on either:
the rectangular bounding box for the element that uses it
(if gradientUnits is set to ObjectBoundingBox), or
the user coordinate system where the object is drawn
(if gradientUnits is set to userSpaceOnUse).
The way you have it set up (in your commented out code) basically creates a hidden gradient background over the entire image, and then lets it show through wherever you draw your lines. Clearly not what you wanted.
Hence, Mike's complex function and the hundreds of sub-paths it creates. Probably not what you want, either, especially if you want the graph to be interactive.
For simple lines, there is another way to get gradients to line up correctly from start to finish of your line.
I've got a very simple example with plain SVG (no D3) up here: http://codepen.io/AmeliaBR/pen/rFtGs
In short, you have to define your line to go in the direction that matches up with the gradient, and then use transforms (scale/rotate/translate) to actually position the line where you want it.
How tricky that would be to implement in D3 depends on how complex your layout is. If you were just using simple lines, I think this would work:
calculate the length of the line and its slope using simple geometry from the (x1,y1) and (x2,y2) values,
draw the line from (0,0) to (0,length) (assuming a vertical gradient),
add a transform attribute of translate(x1,y1) rotate(slope)
With paths, you'd need to know what type of path you're dealing with and use regular expressions to parse and edit the path's d attribute. Very messy.
Maybe just try line markers for start and end?

Categories