Thursday, 6 February 2014

Styles in d3.js

The following post is a portion of the D3 Tips and Tricks book which is free to download. To use this post in context, consider it with the others in the blog or just download the the book as a pdf / epub or mobi .
----------------------------------------------------------

d3.js Styles

What’s a style?
Believe it or not, that’s as difficult a question to answer as “What’s an attribute?”. I like to think that an element can be selected and arranged on a web page with select and attr, but once it’s there, changes to how it looks are a matter for style. We will cover a range of qualities that neatly fit into this definition in the following section (such as fill, opacity and stroke-width) but there are also a range of unusual style declarations that many may not have come across (I certainly hadn’t before writing this).
The other important thing to mention about setting styles for elements is that there are different ways to accomplish the task. We’ll go through the process of describing different styles as they can be applied to individual elements in isolation, but there is a more powerful way to manage styles across a range of elements via Cascading Style Sheets (CSS) in the <style> section of a web page or even via an external style sheet. We will examine these possibilities at the end of the section.
Full disclosure: I have not figured out how to work some of the styles for d3.js I’m afraid that clip-path andmask have exceeded my skill-set and I will have to leave them for another day :-(. I found that there are several good examples that make use of these styles, but I have struggled (unsuccessfully) to present them in a simple example.

fill

The fill style will fill the element being presented with a specified colour.
By default, most elements will be filled with black (the majority of the examples used in this chapter make nofill declaration).
The following example (which works in conjunction with the HTML file outlined at the start of this chapter) shows the syntax for filling a simple circle with the colour red;
holder.append("circle")        // attach a circle
    .attr("cx", 200)           // position the x-centre
    .attr("cy", 100)           // position the y-centre
    .attr("r", 50)             // set the radius
    .style("fill", "red");     // set the fill colour 
Which results in the following image;
Circle with Red Fill
As we saw with the polyline and polygon examples earlier in the chapter some shapes may need to have theirfill colour turned off in some circumstances and this can be accomplished by declaring the colour to benone (.style("fill", "none");).
There are several different ways to define exactly what colour we want as a fill. The example above uses a ‘named colour code’ to declare the colour as “red” but we could also have defined it as rgb (.style("fill", "rgb(255,0,0)");) or in hexadecimal (.style("fill", "#f00");)

stroke

The stroke style applies a colour to lines.
By default many elements do not have a stroke colour set, so it’s a matter of declaring the colour with either a named colour code (“red”), an rgb value (“rgb(255,0,0)”) or the appropriate hex (“#f00”).
The following example (which works in conjunction with the HTML file outlined at the start of this chapter) shows the syntax for applying the colour red to a simple circle. The fill has been set to none to help the colour stand out.
holder.append("circle")        // attach a circle
    .attr("cx", 200)           // position the x-centre
    .attr("cy", 100)           // position the y-centre
    .attr("r", 50)             // set the radius
    .style("stroke", "red")    // set the line colour
    .style("fill", "none");    // set the fill colour 
Which results in the following image;
Circle with Red Border

opacity

The opacity style has the effect of varying an element’s transparency.
The valid range for opacity is from 0 (completely transparent) to 1 (solid colour). We should make the distinction at this point that opacity affects the entire element, whereas the following fill-opacity andstroke-opacity affects only the fill and stroke respectively.
The following code snippet (which works in conjunction with the HTML file outlined at the start of this chapter) creates a green circle with a red border. The opacity value of .2 creates a degree of transparency which will show the grid lines underneath the element.
holder.append("circle")        // attach a circle
    .attr("cx", 200)           // position the x-centre
    .attr("cy", 100)           // position the y-centre
    .attr("r", 50)             // set the radius
    .style("opacity", .2)      // set the element opacity
    .style("stroke", "red")    // set the line colour
    .style("fill", "green");   // set the fill colour
Which results in the following image;
Circle with opacity

fill-opacity

The fill-opacity style changes the transparency of the fill of an element.
The valid range for fill-opacity is from 0 (completely transparent) to 1 (solid colour). We should make the distinction at this point that fill-opacity affects only the fill of an element, whereas opacity will affect the entire element.
The following code snippet (which works in conjunction with the HTML file outlined at the start of this chapter) creates a green circle with a red border. The opacity value of .2 creates a degree of transparency for the fill which will show the grid lines underneath.
holder.append("circle")        // attach a circle
    .attr("cx", 200)           // position the x-centre
    .attr("cy", 100)           // position the y-centre
    .attr("r", 50)             // set the radius
    .style("fill-opacity", .2) // set the fill opacity
    .style("stroke", "red")    // set the line colour
    .style("fill", "green");   // set the fill colour
Which results in the following image;
Circle with Semi-Transparent Fill
The distinction between this image and the one for the opacity style clearly shows the line around the outside of the object as still a solid (opaque) colour.

stroke-opacity

The stroke-opacity style changes the transparency of the stroke (line) of an element.
The valid range for stroke-opacity is from 0 (completely transparent) to 1 (solid colour). We should make the distinction at this point that stroke-opacity affects only the line or border of an element, whereas opacity will affect the entire element.
The following code snippet (which works in conjunction with the HTML file outlined at the start of this chapter) creates an empty circle with a red border. The opacity value of .2 creates a degree of transparency for the stroke which will show the grid lines underneath (or at least make it appear more ‘muted’).
holder.append("circle")          // attach a circle
    .attr("cx", 200)             // position the x-centre
    .attr("cy", 100)             // position the y-centre
    .attr("r", 50)               // set the radius
    .style("stroke-opacity", .2) // set the stroke opacity
    .style("stroke", "red")      // set the line colour
    .style("fill", "none");      // set the fill colour
Which results in the following image;
Circle with Red Border with opacity
Although it is not necessarily easy to see in this example because the line is quite thin, the lines of the grid behind the circle will be showing through the line of the circle.

stroke-width

The stroke-width style adjusts the width of the line of an element.
The value specified when setting stroke-width is in pixels.
The following code snippet (which works in conjunction with the HTML file outlined at the start of this chapter) creates an empty circle with a red border. The stroke-width is set to 5 which equates to 5 pixels (it can also be specified as “5px”).
holder.append("circle")          // attach a circle
    .attr("cx", 200)             // position the x-centre
    .attr("cy", 100)             // position the y-centre
    .attr("r", 50)               // set the radius
    .style("stroke-width", 5)    // set the stroke width
    .style("stroke", "red")      // set the line colour
    .style("fill", "none");      // set the fill colour
Which results in the following image;
Circle with Thicker Red Border
The width of the line that forms the border of the circle is now 5 pixels wide :-).

stroke-dasharray

The stroke-dasharray style allows us to form element lines with dashes instead of solid lines.
We have covered dashed lines in practical way in a previous section of the book (‘Make a Dashed Line’) but for the sake of completeness I will include dashed lines here as well.
We create a dashed line by specifying the length of a dash and then the length of a space. We can include a long list of dashes and spaces and once complete our line will simply repeat the pattern we have specified.
For example the following code snippet (which works in conjunction with the HTML file outlined at the start of this chapter) creates a line with a dash of 10 pixels followed by a space of 2 pixels;
holder.append("circle")       // attach a circle
    .attr("cx", 200)          // position the x-centre
    .attr("cy", 100)          // position the y-centre
    .attr("r", 50)            // set the radius
    .style("stroke-dasharray", ("10,3")) // make the stroke dashed
    .style("stroke", "red")   // set the line colour
    .style("fill", "none");   // set the fill colour
Which results in the following image;
Circle with Dashed Red Border
More complex combinations of dashes and spaces are possible as are complex animation sequences that leverage the ability to move objects along a path (these are certainly more advanced examples).

stroke-linecap

The stroke-linecap style allows control of the shape of the ends of lines in d3.js.
There are three shape options;
  • butt where the line simply butts up to the starting or ending position and is cut off squarely.
  • round where the line is rounded in proportion to its width.
  • square where the line is squared off but extended in proportion to its width.
The following code snippet (which works in conjunction with the HTML file outlined at the start of this chapter) generates three lines showing each stroke-linecap style option. The top line uses butt. The middle line usesround and the bottom line uses square.
holder.append("line")                 // attach a line
    .style("stroke", "black")         // colour the line
    .style("stroke-width", 20)        // adjust line width
    .style("stroke-linecap", "butt")  // stroke-linecap type
    .attr("x1", 100)     // x position of the first end of the line
    .attr("y1", 50)      // y position of the first end of the line
    .attr("x2", 300)     // x position of the second end of the line
    .attr("y2", 50);     // y position of the second end of the line

holder.append("line")                  // attach a line
    .style("stroke", "black")          // colour the line
    .style("stroke-width", 20)         // adjust line width
    .style("stroke-linecap", "round")  // stroke-linecap type
    .attr("x1", 100)     // x position of the first end of the line
    .attr("y1", 100)     // y position of the first end of the line
    .attr("x2", 300)     // x position of the second end of the line
    .attr("y2", 100);    // y position of the second end of the line

holder.append("line")                   // attach a line
    .style("stroke", "black")           // colour the line
    .style("stroke-width", 20)          // adjust line width
    .style("stroke-linecap", "square")  // stroke-linecap type
    .attr("x1", 100)     // x position of the first end of the line
    .attr("y1", 150)     // y position of the first end of the line
    .attr("x2", 300)     // x position of the second end of the line
    .attr("y2", 150);    // y position of the second end of the line
Which results in the following image;
Three Lines with Different End Shapes
The shapes are quite distinct for each type and it is useful to note the degree to which the lines extend beyond their start and end points.

stroke-linejoin

The stroke-linejoin style specifies the shape of the join of two lines. This would be used on pathpolylineand polygon elements (and possibly more).
There are three line join options;
  • miter where the join is squared off as would be expected at the join of two lines.
  • round where the outside portion of the join is rounded in proportion to its width.
  • bevel where the join has a straight edged outer portion clipped off to provide a slightly more contoured effect while still being angular.
The following code snippet (which works in conjunction with the HTML file outlined at the start of this chapter) generates a poly line where the join has the connection shaped using the stroke-linejoin round style.
holder.append("polyline")       // attach a polyline
    .style("stroke", "black")   // colour the line
    .style("fill", "none")      // remove any fill colour
 .style("stroke-width", 20)  // colour the line
 .style("stroke-linejoin", "round")  // shape the line join
    .attr("points", "100,50, 200,150, 300,50");  // x,y points 
Which results in the following image;
Polyline with Round Join
Note the curve on the outer of the join.
Changing the shape of the line join to bevel produces the following;
Polyline with Bevel Join
Here we can see the clipping of the outer portion of the join.
And using miter produces a standard connection;
Polyline with Miter Join
This is the default setting for line joins and does not need to be added unless the line join type has already been set to a different default.

writing-mode

The writing-mode style changes the orientation of the text so that it prints out top to bottom. It has a single option “tb” that accomplishes this. It is relatively limited in scope compared to the equivalent for CSS, but for the purposes of generating some text it has a definite use.
The following code snippet (hich works in conjunction with the HTML file outlined at the start of this chapter) creates a line of text that is now printed from top to bottom instead of left to right.
holder.append("text")            // append text
    .style("fill", "black")      // make the text black
    .style("writing-mode", "tb") // set the writing mode
    .attr("x", 200)         // set x position of left side of text
    .attr("y", 100)         // set y position of bottom of text
    .text("Hello World");   // define the text to display
Which results in the following image;
Text rotated using writing-mode
It is significant to note that while it looks like the text has been rotated about it’s anchor point, this actually isn’t the case since the anchor point should be at 200,100. Also, the glyph-orientation-vertical style (which follows) will allow the text to be orientated vertically which will be useful.

glyph-orientation-vertical

The glyph-orientation-vertical style changes the rotation of the individual glyphs (characters) in text and if used in conjunction with the writing-mode style (and set to 0) will allow the text to be displayed vertically with the letters orientated vertically as well.
The following code snippet (which works in conjunction with the HTML file outlined at the start of this chapter) creates a line of text that is now printed from top to bottom with letters orientated vertically.
holder.append("text")            // append text
    .style("fill", "black")      // make the text black
    .style("writing-mode", "tb") // set the writing mode
    .style("glyph-orientation-vertical", 0)
    .attr("x", 200)         // set x position of left side of text
    .attr("y", 25)          // set y position of bottom of text
    .text("Hello World");   // define the text to display
Which results in the following image;
Text rotated and orientated
It is worth noting that the text spacing increases dramatically as the spacing for each letter relies on the normal distance between the bottom and top of a line of text.

Using styles in Cascading Style Sheets

Declaring styles on an element by element basis is an OK way to apply styles, but when our visualizations become more complex, this can be an inefficient use of code.
A smarter way to provide a common set of styles to elements is to declare them in the <style> section of our HTML document using Cascading Style Sheets (CSS). These will then be automatically applied to our elements.
We start with an example script that draws our three lines that have different styles of linecaps. Our previous example looked like the following (in conjunction with the HTML file outlined at the start of this chapter)
holder.append("line")                 // attach a line
    .style("stroke", "black")         // colour the line
    .style("stroke-width", 20)        // adjust line width
    .style("stroke-linecap", "butt")  // stroke-linecap type
    .attr("x1", 100)     // x position of the first end of the line
    .attr("y1", 50)      // y position of the first end of the line
    .attr("x2", 300)     // x position of the second end of the line
    .attr("y2", 50);     // y position of the second end of the line

holder.append("line")                  // attach a line
    .style("stroke", "black")          // colour the line
    .style("stroke-width", 20)         // adjust line width
    .style("stroke-linecap", "round")  // stroke-linecap type
    .attr("x1", 100)     // x position of the first end of the line
    .attr("y1", 100)     // y position of the first end of the line
    .attr("x2", 300)     // x position of the second end of the line
    .attr("y2", 100);    // y position of the second end of the line

holder.append("line")                   // attach a line
    .style("stroke", "black")           // colour the line
    .style("stroke-width", 20)          // adjust line width
    .style("stroke-linecap", "square")  // stroke-linecap type
    .attr("x1", 100)     // x position of the first end of the line
    .attr("y1", 150)     // y position of the first end of the line
    .attr("x2", 300)     // x position of the second end of the line
    .attr("y2", 150);    // y position of the second end of the line
Which resulted in the following image;
Three Lines with Different End Shapes
The block of code for each of the three lines contains three separate style declarations. Two of which are identical for all three blocks of code;
    .style("stroke", "black")         // colour the line
    .style("stroke-width", 20)        // adjust line width
To make these styles available from a common point, we declare them in the <style> section of our HTML file as follows;
<style>
line.linecap {
  stroke: black;
  stroke-width: 20;  
}
</style>
The <style> tags simply tell our browser which part of the html file we are using to define our styles.
The line.linecap portion identifies the following styles as belonging to the line elements that are also identified as belonging to the ‘class’ linecap (We have used the linecap name as a convenience only and it could just as easily been foobar.).
The two styles are enclosed within curly braces and are declared in the form <style-name>: <style-value>;. So for our example here, the stroke is black and its width is 20 pixels.
Then our example script can have the two styles removed from each of the blocks that draws the lines and in their place we add a new attribute class that assigns a class to the element (in this case the class linecap). Our new code will look like this;
holder.append("line")           // attach a line
    .style("stroke-linecap", "butt")  // stroke-linecap type
    .attr("class", "linecap")   // inherits styles from CSS
    .attr("x1", 100)     // x position of the first end of the line
    .attr("y1", 50)      // y position of the first end of the line
    .attr("x2", 300)     // x position of the second end of the line
    .attr("y2", 50);     // y position of the second end of the line

holder.append("line")           // attach a line
    .style("stroke-linecap", "round")  // stroke-linecap type
    .attr("class", "linecap")   // inherits styles from CSS
    .attr("x1", 100)     // x position of the first end of the line
    .attr("y1", 100)     // y position of the first end of the line
    .attr("x2", 300)     // x position of the second end of the line
    .attr("y2", 100);    // y position of the second end of the line

holder.append("line")           // attach a line
    .style("stroke-linecap", "square")  // stroke-linecap type
    .attr("class", "linecap")   // inherits styles from CSS
    .attr("x1", 100)     // x position of the first end of the line
    .attr("y1", 150)     // y position of the first end of the line
    .attr("x2", 300)     // x position of the second end of the line
    .attr("y2", 150);    // y position of the second end of the line
While this has only replaced two lines with one in our code, the potential for use in far more complex examples should be obvious. There is significantly more detail that can be gone into with regard to CSS, but that would be beyond my meagre abilities.


The description above (and heaps of other stuff) is in the D3 Tips and Tricks book that can be downloaded for free (or donate if you really want to :-)).

2 comments:

  1. Oh boo ya! THANK YOU. I was trying to get font sizes to work, i had the text classed correctly I just needed to say text.className in my style sheet.

    ReplyDelete