Saturday, January 4, 2014

D3 map Styling tutorial II: Giving style to the base map


The example La Belle France, or the original La Bella Italia by Gregor Aisch use svg filters to give style to the maps. I haven't found many examples about how to do it, so I will explain here two different styles: a simple shadow under the map, and a style similar to the original map.

As usual, the examples can be seen at my bl.ocks.org page:
Drop shadow
LaBella Italia

SVG filters basics

SVG can style the elements by using css or css-like attributes, like the color, the stroke, and so on. But it's possible to use filters to add efects such as blurring, dilating the shapes, adding lights... This, of course, can be used to change the map polygons too.

A basic usage of an SVG filter is like this:

  
    
      
    
  
  
And the result is:
Note that:

  • The shape (a rectangle defined by the rect tag) is drawn as usual
  • The filter is defined with the filter tag inside the defs section. The filter must have an id to be used in the geometries where it has to be used
  • The filter is applied to the rectangle by using the filter="url(#blur)" attribute, where the id of the filter is added as url(#name_of_the_filter)
This is the easiest filter an SVG can have, but filters can concatenate different effects one after the other or in parallel, as we will see in the following examples. 
A list of all the effects and a tutorial can be found at W3C Schools

Simple shadow

The drop shadow effect gives a quite nice look to the maps and it's fast to process by the browser and easy to code. I took the code from this example.

The map shown in the last post with the effect. The code:






Note that the filter (lines 46 to 66) has several parts:

  • The filter is defined adding a defs tag and appending the filter, with an id and a height tags. The height is added to affect the region outside the geometry. It's not actually necessary in this case, but I kept it to maintain the example code.
  • An feGaussianBlur is then appended (line 50). The in attribute indicates that the input image for the filter is the alpha value of the input image, so if the map is colored in red, the shadow will remain grey. stdDeviation indicates the filter intensity, and output gives an id to use the resulting image, as we will see.
  • Line 55 adds an feOffset filter, that will move the shadow. The dx and dy attributes define how to move the shadow, but the important attribute here is the in, that takes the id defined in the feGaussianBlur tag, so what is moved is the blurred image. Again, an out attribute is defined to use the result image later.
  • Line 61 defines an feMerge tag. This is an interesting property of svg filters that allow to append different filter results one over the other to create more complex outputs. Appended to the tag, an feMergeNode is added at line 63 with the blurred image at the in attribute. At line 65, the original image is appended over the blurred image, with an other feMergeNode tag, with the in attr set to SourceGraphic to indicate the original image.
SVG filters seemed quite dificult to me, but they are actually a kind of instructions one after the other, written in XML.

Styling like La Bella Italia

As it was shown in the post, the filters part of La Bella Italia map is quite complicated. I'll try to show an easy way to get a similar effect. 
Disclaimer: In my old computer, generating these maps is very slow, so maybe it would be better to think about another combination to create cool maps without using the erode and dilate filters.

The map without any effect looks like this:
And the effect has three stacked parts (I'll paste only the part of the code affecting each part, the whole code is here):

  1. A colouring and blurring
    To do it:
    filter.append("feColorMatrix")
        .attr("in","SourceGraphic")
        .attr("type", "matrix")
        .attr("values", "0 0 0 0 0.6 0 0 0 0 0.5333333333333333 0 0 0 0 0.5333333333333333  0 0 0 1 0")
        .attr("result","f1coloredMask");
    filter.append("feGaussianBlur")
      .attr("in", "f1coloredMask")
      .attr("stdDeviation", 15)
      .attr("result", "f1blur");
    So there are two concatenated actions. The first one, feColorMatrix, changes the color of the original image (see how here). The second, blurs it as in the first example.
  2. Eroding, coloring, blurring and composing:
    filter.append("feColorMatrix")
        .attr("in","SourceGraphic")
        .attr("type", "matrix")
        .attr("values", "0 0 0 0 0   0 0 0 0 0   0 0 0 0 0   0 0 0 500 0")
        .attr("result","f2mask");
    filter.append("feMorphology")
        .attr("in","f2mask")
        .attr("radius","1")
        .attr("operator","erode")
        .attr("result","f2r1");
    filter.append("feGaussianBlur")
        .attr("in","f2r1")
        .attr("stdDeviation","4")
        .attr("result","f2r2");
    filter.append("feColorMatrix")
        .attr("in","f2r2")
        .attr("type", "matrix")
        .attr("values", "1 0 0 0 0.5803921568627451 0 1 0 0 0.3607843137254902 0 0 1 0 0.10588235294117647 0 0 0 -1 1")
        .attr("result","f2r3");
    filter.append("feComposite")
        .attr("operator","in")
        .attr("in","f2r3")
        .attr("in2","f2mask")
        .attr("result","f2comp");
    This one is more complicated. 
    1. The first step changes the map color into black, with an alpha value of 0.5. This output will be used in the second and last step.
    2. Then, using the first output, the image is eroded. That is, the land gets smaller by one pixel, using feMorphology with the erode operator. Then, the result is blurred using feGaussianBlur, and coloured as in the first filter.
    3. The resulting image is composited with the original black and white image, using feComposite. The definition of this operator, taken from here, is:
      The result is the part of A that is within the boundaries of B. Don't confuse the name of this attribute value with the in attribute.
  3. The two effects are stacked, and the original map is added at the end:
    var feMerge = filter.append("feMerge");
    
    feMerge.append("feMergeNode")
        .attr("in", "f1blur");
    feMerge.append("feMergeNode")
        .attr("in", "f2comp");
    feMerge.append("feMergeNode")
        .attr("in", "SourceGraphic");
    This part is simple,the feMerge stacks all the outputs indicated in the feMergeNode tags. So in this case, the blurred image goes in the first place, then the eroded one, and finally, the original one. Now, the effect seems quite a lot to the one in La Bella Italia.

Links

Drop shadow map - The first example
La Bella Italia like map - The second example
D3 map Styling tutorial I: Preparing the data - First part of the tutorial
La Bella Italia - Kartograph example by Gregor Aisch
La Belle France - The same example as La Bella Italia, but using D3js
d3.js drop shadow example - SVG drop shadow filter by Charl P. Botha
SVG filters tutorial - W3C Schools tutorial about SVG filters

1 comment:

  1. Nice work! That is a great walk through of the process. There doesn't seem to be a lot of demos of svg manipulation to this level so thanks for sharing.

    ReplyDelete