Drawing Labels using TextPath inside a donut chart - d3.js

I am looking for a jsfiddle sample for a donut chart in D3, where
labels are inside the arcs
labels are centerd inside each arc
they are bended over the arc (like the figure below)
Update: Here is my code so far http://jsfiddle.net/gs5qp5sx/1/
svg.selectAll("text").data(dataset.apples).enter()
.append("text")
.style("font-size",15)
.style("fill","#F8F8F8")
.attr("dy",0)
.append("textPath")
.attr("xlink:href",function(d,i){return "#s"+i;})
.attr("startOffset","50%")
.style("text-anchor","middle")
.text(function(d,i){return "label"+i;})
Do you know any sample that does this ?

http://bl.ocks.org/mbostock/3887193
http://www.kevlindev.com/tutorials/basics/text/svg/index.htm
hope this samples help you!.

Related

Rendering in the background of a dc.js chart with renderlet

I use dc.js for showing the results of multiple classification algorithms. More specifically, I want to show a precision recall chart (each point corresponds to a result of a classification system).
I already used a dc.js scatter chart for this which works fine.
Additionally I would like to have a d3 contour in the background of the chart which shows the F-measure.
This is already implemented. The only issue is that the contour part is in the foreground and not in the background of the chart.
Please have a look at the jsfiddle for a full example.
Two questions are still open for me because I'm not a dc.js or d3 expert:
Is there a way to put the contour in the background or the symbols(cycles) of the scatter chart in the foreground (I already tried it with the help of this stackoverflow question but with no success)
I used the 'g.brush' selector to get the area of the inner chart. This works fine as long as the brushing is turned on. Is the selector a good way to go or are there better alternatives (which may also work if brushing is switched off).
In my example I put the contour part in the upper left corner to see if it works but I also provide the code (currently uncommented) to increase the width and height of the contour to the correct size.
chart
.on('renderlet', function (chart) {
var innerChart = chart.select('g.brush');
var width = 300, height=300;
//getting the correct width, height
//var innerChartBoundingRect = innerChart.node().getBoundingClientRect();
//var width = innerChartBoundingRect.width, height=innerChartBoundingRect.height;
[contours, color] = generateFmeasureContours(width,height, 1);
innerChart
.selectAll("path")
.data(contours)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", d => color(d.value));
var symbols = chart.chartBodyG().selectAll('path.symbol');
symbols.moveToFront();
});
jsfiddle
Putting something in the background is a general purpose SVG skill.
SVG renders everything in the order it is declared, from back to front, so the key is to put your content syntactically before everything else in the chart.
I recommend encapsulating it in an svg <g> element, and to get the order right you can use d3-selection's insert method and the :first-child CSS selector instead of append:
.on('pretransition', function (chart) {
// add contour layer to back (beginning of svg) only if it doesn't exist
var contourLayer = chart.g().selectAll('g.contour-layer').data([0]);
contourLayer = contourLayer
.enter().insert('g', ':first-child')
.attr('class', 'contour-layer')
.attr('transform', 'translate(' + [chart.margins().left,chart.margins().top].join(',') + ')')
.merge(contourLayer);
A few more points on this implementation:
use dc's pretransition event because it happens immediately after rendering and redrawing (whereas renderlet waits for transitions to complete)
the pattern .data([0]).enter() adds the element only if it doesn't exist. (It binds a 1-element array; it doesn't matter what that element is.) This matters because the event handler will get called on every redraw and we don't want to keep adding layers.
we give our layer the distinct class name contour-layer so that we can identify it, and so the add-once pattern works
contourLayer = contourLayer.enter().insert(...)...merge(contourLayer) is another common D3 pattern to insert stuff and merge it back into the selection so that we treat insertion and modification the same later on. This would probably be simpler with the newer selection.join method but tbh I haven't tried that yet.
(I think there may also have been some improvements in ordering that might be easier than insert, but again, I'm going with what I know works.)
finally, we fetch the upper-left offset from the margin mixin
Next, we can retrieve the width and height of the actual chart body using
(sigh, undocumented) methods from dc.marginMixin:
var width = chart.effectiveWidth(), height = chart.effectiveHeight();
And we don't need to move dots to front or any of that; the rest of your code is as before except we use this new layer instead of drawing to the brushing layer:
contourLayer
.selectAll("path")
.data(contours)
.enter()
.append("path")
.attr("d", d3.geoPath())
.attr("fill", d => color(d.value));
Fork of your fiddle.
Again, if you'd like to collaborate on getting a contour example into dc.js, that would be awesome!

D3 - Select element in this svg?

I have a codepen here - https://codepen.io/anon/pen/yvgJKB
I have two d3 charts with different data.
I want to have tooltips on the bars so when you rollover them it shows the value.
I want the tooltip to be separate html in the svg cos the actual charts are responsive and I dont want the tooltips to scale with the svg.
The tooltip works as expected in the top chart.
In the bottom chart the tooltip appears in the top chart.
I think this is becasue the let tooltip = d3.select('.tooltip').append("div") is selecting the first tooltip which is in the first chart.
The charts and tooltip is dynamically created.
This there a way in the D3 to say d3.select('.tooltip') in this svg something like d3.select(this)('.tooltip')
let tooltip = d3.select('.tooltip').append("div")
.attr("class", "tip")
.style("display", "none")
.attr("fill", "grey");
There is a lot of unnecessary repetition in your code.
Create the tooltip div only once. Also, since you are creating the div in your D3 code, you don't need any div with the tooltip class in your HTML. Remove both.
Besides that, use d3.event.pageX and d3.event.pageY to position your tooltip div anywhere in the page. A single tooltip div can be used to show the tooltip to any number of SVGs you have on that page.
Here is your updated CodePen: https://codepen.io/anon/pen/KQaBvE?editors=0010
Tooltips should be created separately by selecting different charts (or some other unique marker) and given unique variable names within the same scope:
let tooltip1 = d3.select('.chart1').append("div")
.attr("class", "tip")
.style("display", "none")
.attr("fill", "grey");
let tooltip2 = d3.select('.chart2').append("div")
.attr("class", "tip")
.style("display", "none")
.attr("fill", "grey");
https://codepen.io/mortonanalytics/pen/BYpwaz

d3.geo.circle angle does not work?

So, I've tried following Jason Davies' answer to the Circle clip and projection with D3 orthographic thread...
I've tried 3 different possibilities :
svg.append("g").attr("class","points")
.selectAll("path")
.data(places.features)
.enter()
.append("path")
//THIS COMMENTED PART DOES NOT WORK
//though the console spits out an array
/*.datum(function(d){
console.log(d.geometry.coordinates)
return d3.geo.circle()
.origin(d.geometry.coordinates)
.angle(2)
})*/
//THIS UNCOMMENTED PART WORKS
//and places the circles accordingly with a fixed size radius
.datum(d3.geo.circle()
.origin(function(d){return d.geometry.coordinates})
.angle(2)
)
//THIS COMMENTED PART DOES NOT WORK
//which is a shame, as my goal is to change the angle of each circle
/*.datum(d3.geo.circle()
.origin(function(d){return d.geometry.coordinates})
.angle(function(d){return 2})
)*/
.attr("class", "point")
.attr("d", path)
How do I get the last commented part to work so that I can change the radius of my circles? Thanx a lot.
After updating to d3 version "5.7.0" I had same issue.
Fixed by using geoCircle.center(center).radius(angle) instead of geo.circle().angle(angle).
Here you can find how it could work.

Drawing map with d3js from openstreetmap geojson

Hy
I'm trying to draw an svg with d3.v3.js from geojson. I fetch the geojson from openstreetmap(my test data: http://pastebin.com/4GQne42i) and try to render it to svg.
My JS code:
var path, vis, xy, jdata;
xy = d3.geo.mercator().translate([0, 0]).scale(200);
path = d3.geo.path().projection(xy);
vis = d3.select("body").append("svg").attr("width", 960).attr("height", 600);
//22.json is the name of the file which contains the geojson data
d3.json("22.json", function(error, json) {
jdata = json;
if(error!=null)
console.log(error);
return vis.append("svg:g")
.selectAll("path")
.data(json.coordinates)
.enter().append("path")
.attr("d", path);
});
And somehow my svg result is this:
<svg width="960" height="600">
<g>
<path></path>
</g>
</svg>
I know the projection is not good, but I think the svg should have nodes.
What is the problem with my code? Would you post a correct solution?
The first problem is with your data join:
vis.append("g")
.selectAll("path")
.data(json.coordinates)
.enter().append("path")
.attr("d", path);
This would mean you want one path element for each element in the json.coordinates array. Since your test data is a polygon, that would mean one path element for the exterior ring, and then perhaps multiple other path elements for any interior holes, if your data has them. I expect you just want a single path element for the entire polygon.
The second problem is that you’re not passing valid GeoJSON to the d3.geo.path instance. Because the data in your data join is json.coordinates, you’re just passing an array of coordinates directly to path, when you need to pass a GeoJSON geometry object or a feature. (See the GeoJSON specification.)
Fortunately both of these problems are easy to fix by eliminating the data join and rendering the full polygon. To add just one path element, just call selection.append:
vis.append("path")
.datum(json)
.attr("d", path);
Your projection will probably need adjusting (translate and scale), too. You might find the project to bounding box example useful here.
Do you really need to do it with D3?
I would suggest to go with more map oriented libraries like:
polymaps
Leaflet
Leaflet vector layer has support for GeoJSON and its size is quite compact.
Open Layers is also an option but it's size is quite big.
Here is an example how I have used Leaflet + GeoJSON to display suburb shape http://www.geolocation.ws/s/6798/en

Can an SVG pattern be implemented in D3?

I tried:
var pattern = d3.select("#container").append("svg:pattern");
pattern.append("svg:defs") ... //and so on
but ran into errors on append("svg:pattern") so I assume that is not implemented in d3 yet.
My solution was to simply run the SVG code outside of d3 which worked fine since I did not need the power of d3 for this piece. (I realized I could have attached a .data(dataset) to an SVG object and looped through it to create it, but that would have been a lot of d3 effort for no reason.)
This is a sanity-check question: Did I miss something in trying to implement an svg pattern in d3, or was my solution the correct approach?
(Thank you Mike Bostock for such an incredible library.)
In this case all the magic it's in svg rather than d3. Take a look at the pocket guide to writing SVG to understand the basics of svg.
And here is my solution:
var svg = d3.select("body").append("svg");
svg.append("defs").append("pattern")
.attr('id','myPattern')
.attr("width", 40)
.attr("height", 25)
.attr('patternUnits',"userSpaceOnUse")
.append('path')
.attr('fill','none')
.attr('stroke','#335553')
.attr('stroke-width','3')
.attr('d','M0,0 Q10,20 20,10 T 40,0' );
svg.append('rect').attr('x',10)
.attr('y',0)
.attr('width',500)
.attr('height',200)
.attr('fill','url(#myPattern)');
View this example working on codepen
D3 is more or less agnostic to what you're dong with it -- you can generate SVG, HTML, whatever. There's no reason why SVG patterns wouldn't work. From the little code you've posted it looks like you might be trying to append the pattern to an HTML element. You'll have to create the SVG and the defs element before starting to define the pattern.
I confirm, you first have to add the defs element to your SVG and then add your pattern to it.
//first create you SVG or select it
var svg = d3.select("#container").append("svg");
//then append the defs and the pattern
svg.append("defs").append("pattern")
.attr("width", 5)
.attr("height", 5);

Resources