Shapes

Now that text is out of the way, let's look at something much more interesting--shape primitives, which we use to create everything from the bars in a bar chart to complex renders of geographic spaces.
Let's start by talking about things where you need only one point:

svg.append('circle') 
.attr('cx', 350)
.attr('cy', 250)
.attr('r', 100)
.attr('fill', 'green')
.attr('fill-opacity', 0.5)
.attr('stroke', 'steelblue')
.attr('stroke-width', 2);

A circle is defined by a central point (cx, cy) and a radius r. In this instance, we get a green circle with a steel blue border:

Mathematically speaking, a circle is just a special form of ellipse. You can make a less-round circle by setting an ellipse element's rx and ry attributes; it otherwise works the same:

  const ellipses = chart.container.append('ellipse') 
.attr('cx', 350)
.attr('cy', 250)
.attr('rx', 150)
.attr('ry', 70)
.attr('fill', 'green')
.attr('fill-opacity', 0.3)
.attr('stroke', 'steelblue')
.attr('stroke-width', 0.7);

We added an ellipse element and defined some well-known attributes. The ellipse shape needs a central point (cx, cy) and the two radii, rx and ry. Setting a low fill-opacity attribute makes the circle visible under the ellipse:

Let's add another one using the following code:

  chart.container.append('ellipse') 
.attr('cx', 350)
.attr('cy', 250)
.attr('rx', 80)
.attr('ry', 7)

The only trick here is that rx is smaller than ry, creating a vertical ellipse.

Smashing! We've made some kind of weird green eye thing!:

However, we want more. We want a diagonal blue line. Clearly, that's all that's needed to tie this amazing piece of art together.

To draw a straight line, use the following code:

const line = chart.container.append('line') 
.attr('x1', 10)
.attr('y1', 10)
.attr('x2', 100)
.attr('y2', 100)
.attr('stroke', 'blue')
.attr('stroke-width', 3);

As before, we took our chart container element, appended a line, and defined some attributes. We're now diving into creating elements with multiple points--a line is drawn between two, (x1, y1) and (x2, y2). To make the line visible, we have to define the stroke color and stroke-width attributes as well--otherwise it'd simply be a black line with no stroke and no fill, and thus not able to be seen:

Our line points downward, even though y2 is bigger than y1. That's because the origin in most image formats lies in the top-left corner. This means that (x=0, y=0) defines the top-left corner of the image.

Hm. Still missing something... Let's move on to elements with four vertices!

To draw a rectangle, we can use the rect element:

const rect = chart.container.append('rect') 
.attr('x', 200)
.attr('y', 50)
.attr('width', 300)
.attr('height', 400);

We appended a rect element to the container element and defined its core attributes. A rectangle is defined by its upper-left corner (x, y), width, and height.
Our image now looks as follows:

We have a rather-large black rectangle. Shapes will render like this if you don't define the fill and stroke properties--by default, SVG shapes are borderless and with a 100% opacity black fill. Not only that, but it's blocking our weird green eye thing!

SVG stacking is defined in the order an element appears in the markup, and SVG doesn't have anything like z-index in HTML to arbitrarily change this order.

To place the rectangle behind or before the eye, we need to modify the following line:

const rect = chart.container.append('rect')

Modify it and use insert() instead of append():

const rect = chart.container.insert('rect', 'circle')

The first argument is the type of element to be inserted, with the second argument a CSS selector for an item to insert before. In this case, we want to insert behind our earlier circle, so we supply that. Any CSS selector works though--we could have also put :first-child at the top of the stack, so behind everything (including our first text element).

Another way we could accomplish this, which is new in D3 v4, is to use selection.raise() and selection.lower(). selection.raise() will recreate the selection as the last/bottom element in their parent container, and selection.lower() will recreate it at the top of its parent. This is really useful if you want to create a series of elements at the bottom of an SVG group, then move the elements to the top of the group after somebody clicks or taps on them.

Let's set fill and stroke by defining three more properties like this:

  rect.attr('stroke', 'green') 
.attr('stroke-width', 0.5)
.attr('fill', 'white')

Then, mess it up a little bit by rounding the corners an arbitrary amount:

    .attr('rx', 20) 
.attr('ry', 4);

The output will look somewhat like this:

That's kind of interesting, I guess? Our rectangle has a thin, green outline. Rounded corners come from the rx and ry attributes, which define the corner radius along the x and y axes.

The generated SVG is in an XML form, as follows; you can see the same by right-clicking the image and navigating to Inspect Element, which will select the element in Developer Tools:

<svg id="chart" width="801" height="726"> 
<g id="container" transform="translate(10, 10)">
<rect x="200" y="50" width="300" height="400"/>
<text x="50" y="200" text-anchor="start">
Ceci n'est pas un trajet!</text>
<circle cx="350" cy="250" r="100" fill="green"
stroke="steelblue" fill-opacity="0.5" stroke-width="2"/>
<ellipse cx="350" cy="250" rx="150" ry="70" fill="green"
fill-opacity="0.3" stroke="steelblue" stroke-width="0.7">
<ellipse cx="350" cy="250" rx="20" ry="7"/>
</ellipse>
<line x1="10" y1="10" x2="100" y2="100" stroke="blue"
stroke-width="3"/>
</g>
</svg>

Yeah, I wouldn't want to write that by hand either.

However, you can see all the elements and attributes we added before. Being able to look at an image file and understand what's going on is really useful when trying to debug problems. With something like Canvas (which we'll touch upon briefly in Chapter 8, D3 on the server with Canvas, Koa 2 and Node.js), all you see in the rendered output is a single element, which can be output as a base64-encoded string of indecipherable noise. This is both SVG's biggest strength and also its Achilles' heel--while it's incredibly easy to work with and debug, having to keep a complex tree of elements in memory really tends to slow down the browser after a certain point (at present, the general wisdom is that it's time to start thinking about Canvas when your chart renders over a thousand SVG elements).

We've looked at 1, 2, 3, and 4-vertex shapes now (circles, lines, ellipses, and rectangles, respectively). I mentioned earlier that polylines and polygons are also basic SVG elements--however, I will not cover them just yet. Although you can render them using D3 selections like the above, they're far easier to work with if using some of the path generators that come with D3. We'll cover those in just a second.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset