Can an SVG pattern be implemented in D3? - d3.js

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);

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!

Drawing Labels using TextPath inside a donut chart

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!.

Why do GeoJSON features appear like a negative photo of the features themselves?

I have a pretty standard code thats reads a GeoJSON file and renders its features using D3.js. It works fairly well except with this file: https://github.com/regiskuckaertz/d3/blob/master/circonscriptions.json
The file doesn't look weird or anything, in fact you can preview it on GitHub or geojsonlint.com. However, D3 draws paths that look like the features were used as a clipping mask, i.e. all the shapes are negatives of the features themselves. The code is pretty standard though:
var proj = d3.geo.mercator()
.scale(25000)
.center([6.08642578125,49.777716951563754])
.rotate([-.6, -.2, 0]);
var path = d3.geo.path().projection(proj);
function ready(error, luxembourg) {
svg
.selectAll("path")
.data(luxembourg.features)
.enter().append("path")
.attr("d", path)
.attr("class", function(d) { return quantize(rateById.get(d.properties.name)); })
}
You can have a look here: http://jsfiddle.net/QWZXd/
The same code works with another file, which comes from the same source.
For some reason, the points in these polygons are in reverse order - they ought to be clockwise, but are defined as counterclockwise, and d3 follows the right-hand rule for polygon interpretation.
To fix, reverse the points, either in the file or in JS:
luxembourg.features.forEach(function(feature) {
feature.geometry.coordinates[0].reverse();
});
Fixed fiddle: http://jsfiddle.net/nrabinowitz/QWZXd/1/

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

replacing d3.js diagram instead of creating a new one every time

I`m using d3.js combined with GWT and every time the javascript code for creating a pie chart is called it appends a new one rather than replacing the old one with new values.
How can I change this code so that the old one is removed before appending the new one?
var svg = d3.select(".body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
Rather then changing your code i would suggest creating it once, then build your diagram using your data and taking a look at the .enter() and .exit() methods of d3.js - so if your data changes the library would add/remove some elements.
Cheers
Just add
d3.select(".body").selectAll("svg").remove();
before your code to remove all SVG elements.

Resources