Creating and Editing SVG Graphics

  • 7/15/2012

Case Study: Designing a Reusable Pattern

The example in this section gives you a closer look at how to write SVG code that generates a pattern composed of both vector and bitmap graphics.

Adding Basic Shapes

Building upon your knowledge up to this point, you’ll walk through each step of the design and creation process.

  1. Create and save a file named tile.svg that contains the following lines of code:

    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
      version="1.1"
      width="800" height="600"
      viewBox="0 0 400 300" preserveAspectRatio="none">
      <g id="layer1"></g>
    </svg>
  2. With this framework in place, you can start adding some basic shapes. The next example shows a simple pattern design. Tile patterns are known mathematically as tessellations of the plane.

    httpatomoreillycomsourcemspimages1261217.png

    To create this pattern in SVG code, first create the following line:

    <line stroke="#000000" stroke-width="1" stroke-linecap="round"
              stroke-linejoin="round" stroke-miterlimit="4" stroke-opacity="0.4"
              stroke-dasharray="1, 6" stroke-dashoffset="0"
              x1="90" y1="10" x2="10" y2="90"
              id="patternLine1" />

    Now, as mentioned earlier, you can reuse the line. By changing the x and y values you can effectively rotate the line by 90 degrees. Also, to mix the pattern up a bit, you can override stroke-opacity and other style attributes that would otherwise be inherited from the referenced element:

    <use stroke-opacity="1"
           transform="rotate(90, 50, 50)"
             xlink:href="#patternLine1"
             id="patternLine2" />
  3. Next, draw the rest of the elements that you want to include in your pattern—for example:

    <ellipse fill="#a1d9ad" fill-opacity="0.7" fill-rule="nonzero"
                  stroke="#32287d" stroke-width="1" stroke-opacity="0.5"
                  id="path3389" cx="50" cy="50" rx="30" ry="20" />
    
    <!-- Draw the upper-right rectangle. -->
    <rect fill="#ada1d9" fill-opacity="1" fill-rule="nonzero"
           stroke="#32287d" stroke-width="10" stroke-linecap="butt"
           stroke-linejoin="bevel" stroke-miterlimit="4" stroke-opacity="0.4"
           id="patternRect-upperRight"
           width="20" height="20" x="90" y="-10" />
    
    <!-- Reuse the first rectangle element and rotate it 90 degrees each time. -->
    <use transform="rotate(90, 50, 50)"
             xlink:href="#patternRect-upperRight"
             id="patternRect-lowerRight" />
    <use transform="rotate(180, 50, 50)"
             xlink:href="#patternRect-upperRight"
             id="patternRect-lowerLeft" />
    <use transform="rotate(270, 50, 50)"
             xlink:href="#patternRect-upperRight"
             id="patternRect-upperLeft" />
    
    <!-- Draw the circle. -->
    <circle fill="#d9d2a1" fill-opacity="1" fill-rule="nonzero"
                stroke="#32287d" stroke-width="1" stroke-linecap="butt"
                stroke-linejoin="bevel" stroke-miterlimit="4"
                id="path3395" cx="50" cy="50" r="10" />
    
    <!-- Draw the path using "relative"coordinates via lowercase path commands.
         Note that we can easily switch to using the Polyline element by changing
         the "d" attribute to "points". -->
    <path fill="none" stroke="#000000" strGoke-width="1px" stroke-linecap="butt"
              stroke-linejoin="miter" stroke-opacity="1"
              d="m 0,50 10,0 0,20 20,20 0,0 0,0 20,0 0,10"
              id="patternPath-lowerLeft" />
    
    <!-- Reuse the first path, rotate it 90 more degrees for each of the four corners. -->
    <use transform="rotate(90, 50, 50)"
             xlink:href="#patternPath-lowerLeft"
             id="patternPath-upperLeft" />
    <use transform="rotate(180, 50, 50)"
             xlink:href="#patternPath-lowerLeft"
             id="patternPath-upperRight" />
    <use transform="rotate(270, 50, 50)"
             xlink:href="#patternPath-lowerLeft"
             id="patternPath-lowerRight" />

    These SVG elements form the basis for the pattern that you will create in the next step. You may have noticed the use of the transform attribute. You can see how the referenced rectangle and path shapes were moved into a different position via a rotate command. The next chapter will cover the usefulness of transformations in greater detail.

  4. To create a more interesting pattern design, rather than using simple MoveTo (M) path commands, simply alter the <path> element’s values to use a relatively positioned smooth quadratic Bézier curve using the s command, and an absolutely positioned cubic Bézier curve using C. So, the path’s data becomes the following:

    d="M 0,50 s 10,0 0,20 C 20,20 0,0 0,0"
  5. Add a reference to a bitmap image of the planet Jupiter and position the image at the center. Also, move the graphics to the origin of the coordinate system, which equals the x,y value of (0,0), to complete your initial tile design. Now the tile looks like this:

    httpatomoreillycomsourcemspimages1261219.png
  6. Finally, add the <pattern> element inside of a <defs> element and move the tile design graphics inside of the <pattern>.

    <defs>
      <pattern id="gridPatternWithTessellation"
                     x="20" y="20" width="100" height="100
                     patternUnits="userSpaceOnUse">
          <!--Insert the tile elements here.  -->
      </pattern>
    </defs>

    Below the <defs>, you then simply create a rectangle, path, or any other SVG shape and set its fill value to be the pattern, as shown at the end of the full code listing below.

    <svg
       xmlns="http://www.w3.org/2000/svg"
       xmlns:xlink="http://www.w3.org/1999/xlink"
       id="chapter2-ShapesPatternsGroupsUse"
       version="1.1"
       width="800" height="600"
       viewBox="0 0 400 300" preserveAspectRatio="none"
    >
      <defs>
        <!-- Begin Example -->
        <pattern id="gridPatternWithTessellation" x="20" y="20" width="100" height="100"
          patternUnits="userSpaceOnUse">
          <!-- Draw the lines. -->
          <line stroke="black" stroke-width="1" stroke-linecap="round" stroke-
    linejoin="round"
             stroke-miterlimit="4" stroke-opacity="0.4" stroke-dasharray="1, 6"
             stroke-dashoffset="0"
             x1="90" y1="10" x2="10" y2="90"
             id="patternLine1" />
          <!-- Reuse the first line, rotate it 90 degrees, and update the style attributes.
    -->
          <!-- For appendix or wiki - note that currently most browsers do not support
    styling
          of Use elements using either CSS or SVG attributes -->
          <use stroke-opacity="1"
            transform="rotate(90, 50, 50)"
             xlink:href="#patternLine1"
             id="patternLine2" />
          <!-- Draw the upper-right rectangle. -->
          <rect fill="#ada1d9" fill-opacity="1" fill-rule="nonzero" stroke="#32287d"
             stroke-width="10" stroke-linecap="butt" stroke-linejoin="bevel"
             stroke-miterlimit="4" stroke-opacity="0.4"
             id="patternRect-upperRight"
             width="20"
             height="20"
             x="90"
             y="-10" />
          <!-- Reuse the first rectangle element and rotate it 90 degrees each time. -->
          <use transform="rotate(90, 50, 50)"
             xlink:href="#patternRect-upperRight"
             id="patternRect-lowerRight" />
          <use transform="rotate(180, 50, 50)"
             xlink:href="#patternRect-upperRight"
             id="patternRect-lowerLeft" />
          <use transform="rotate(270, 50, 50)"
             xlink:href="#patternRect-upperRight"
             id="patternRect-upperLeft" />
          <!-- Group containing the eye. -->
          <g id="eye">
            <!-- Draw the ellipse. -->
            <ellipse fill="#a1d9ad" fill-opacity="0.7" fill-rule="nonzero"
              stroke="#32287d" stroke-width="1" stroke-opacity="0.5"
              cx="50" cy="50" rx="22" ry="14" />
            <!-- Group containing the eye's iris. -->
            <g id="iris">
               id="path3389"
               cx="50" cy="50" rx="20" ry="14" />
    
              <!-- Draw the circle. -->
              <circle fill="black" fill-opacity="1" fill-rule="nonzero" stroke="#32287d"
                 stroke-width="1" stroke-linecap="butt" stroke-linejoin="bevel"
                 stroke-miterlimit="4"
                 id="path3395"
                 cx="50" cy="50" r="10" />
              <!-- Reference the bitmap image (PNG) -->
              <image id="bitmapCentralBall"
                 width="5.5%" height="5.5%"
                 x="39px" y="42px"
                 xlink:href="iris-small.png"
                 alt="NASA Photo of Jupiter" />
            </g>
          </g>
          <!-- Draw the path using "relative" coordinates via lowercase path commands.
          Note that we can easily switch to using the Polyline element by changing
         the "d"
          attribute to "points". -->
          <path fill="none" stroke="black" stroke-width="1px" stroke-linecap="butt"
             stroke-linejoin="miter" stroke-opacity="1"
             d="M 0,50 s 10,0 0,20 C 20,20 0,0 0,0"
             id="patternPath-lowerLeft" />
             <!-- Other interesting paths
                  MoveTo Polyline-like d="m 0,50 10,0 0,20 20,20 0,0 0,0 20,0 0,10"
                  Quadratic d="M 0,50 Q 10,0 0,20 S 20,20 0,0"
                  Smooth Quadratic d="M 0,50 S 10,0 0,20 Q 20,20 0,0"
                  Cubic d="M 0,50 C 10,0 0,20 20,20 S 0,0 0,0"
                  Smooth Quadratic & Cubic d="M 0,50 s 10,0 0,20 C 20,20 0,0 0,0" -->
    
                  -->
          <!-- Reuse the first path, rotate it 90 more degrees for each of the
          four corners. -->
          <use
            transform="rotate(90, 50, 50)"
             xlink:href="#patternPath-lowerLeft"
             id="patternPath-upperLeft" />
          <use
            transform="rotate(180, 50, 50)"
             xlink:href="#patternPath-lowerLeft"
             id="patternPath-upperRight" />
          <use
            transform="rotate(270, 50, 50)"
             xlink:href="#patternPath-lowerLeft"
             id="patternPath-lowerRight" />
        </pattern>
        </g>
        <pattern id="gridPattern" width="10" height="10" patternUnits="userSpaceOnUse">
           <path d="M10 0 L0 0 L0 10" fill='none' stroke='gray' stroke-width='0.25'/>
        </pattern>
      </defs>
      <g id="layer1">
        <!-- background grid -->
        <rect id="grid" width="100%" height="100%" x="0" y="0"
           stroke='gray' stroke-width='0.25' fill='url(#gridPattern)'/>
        <!-- grid illustrations -->
        <use xlink:href="#coords"/>
        <text x="3" y="9" font-size='8'>(0,0)</text>
        <!-- Begin Example -->
        <rect id="gridWithTessellation" width="300" height="300" x="20" y="20"
           fill='url(#gridPatternWithTessellation)' />
      </g>
      <rect id="gridWithTessellation"
              x="20" y="20" width="300" height="300"
              fill='url(#gridPatternWithTessellation)' />
    </svg>

With just these lines of code, you have created an interesting work of art and a useful tiling pattern that has all the benefits of SVG. To meet the needs of your company, group, or imagination, you only need to edit the base tile to create an entirely different design for your application.

Because there is a bitmap image within the pattern, if end users zoom in they will see a slightly pixelated graphic surrounded by the smoother, unpixelated vector graphics. You should consider bitmap pixelation when your project requires high-fidelity printouts.