DEV Community

Cover image for Geeking-out on SVG Graphics part-four
Tracy Gilmore
Tracy Gilmore

Posted on • Updated on

 

Geeking-out on SVG Graphics part-four

Reprise

My project is to create an SVG-based backdrop in the style of a cutting mat for software engineers (well me, not really anyone else, apologies for being selfish).

As covered in the previous posts, I have a grid drawn in grey (#777) on top of a background colour (#191662) of Engineer's blue. There is also a by-line, a reference to creative-commons licencing and, in the latest update, a rendering of some common screen resolutions.

In this forth post in the series I will be adding some standard angle lines, which is something quite typical of the original physical cutting mats.

I will release the cutting-mat SVG image at the end of each stage via my GitHub repo.

Standard Angles, and reciprocal arcs

The standard angles will radiate out from the bottom-left corner of the cutting mat and extend out to the edge of the grid. At the end of each line there will be a circle containing the degrees the line represents. The standard angles will include 15, 30, 45, 60 and 75 degrees as measured from the horizontal. The reciprocal angle will be represented as an arc transcribed from the horizontal. Intersecting with the bottom of the grid there will be an arc connecting to each line. Where the arc and the line connect there will be a small circle and at the other end of the arc there will be a larger circle containing the Sine of the angle used to calculate the arc.

So, as usual we need to update the CSS, add a containing SVG element to the document and provide a JS function (or two) to inject the drawing instructions.

Updating the CSS

We will be drawing the standard angles in a pale green so will add the following custom property.

  --angle-colour: #cfc;
Enter fullscreen mode Exit fullscreen mode

Using the angles id as a reference point we can style the elements using the following CSS.

#angles circle,
#angles line {
  stroke-width: 1;
  stroke: var(--angle-colour);
  fill: var(--background-colour);
}

#angles path {
  stroke-width: 1;
  stroke: var(--angle-colour);
  stroke-dasharray: 8,8;
  fill: transparent;
}

#angles text {
  font-size: 16px;
  text-anchor: middle; 
  fill: var(--angle-colour);
}

#angles .title {
  font-size: 1.25rem;
  text-anchor: start; 
}
Enter fullscreen mode Exit fullscreen mode

SVG Document

We now need the entry point into the document where the standard angles will be represented. This will be an SVG element with the id of 'angles' containing two unnamed groups, as we only need to reference the first one. The second group will contain a title label of 'Standard Angles' and a circle for the original from which the lines radiate.

<svg x="20" y="20" width="1360" height="880" id="angles" 
    viewBox="-40 -40 1360 880">
  <g></g>
  <g>
    <text x="250" y="-7" class="title">Standard Angles</text>
    <circle cx="0" cy="800" r="10"></circle>
  </g>
</svg>
Enter fullscreen mode Exit fullscreen mode

Notice that although the internal width and height of the SVG element is the same as the external, it is offset by 40px up and left.

Populating the group with drawStandardAngles

True to form we first locate the entry point in the DOM using a querySelector document.querySelector('#angles g'). Next we prepare the data, which is just an array of numbers [75, 60, 45, 30, 15], the order is significant as we use the index of the data to position the arcs.

In this instance we have an internal function called createAngleSvg that takes the angle and an array index as parameters. The function is used by a map method on the array to produce the SVG elements required for rendering a single line, arc, text and pair of circles, ready for injection into the document.

function createAngleSvg(deg, idx) {
  const MINOR_CIRCLE = 3;
  const MAJOR_CIRCLE = 20;
  const TEXT_OFFSET = 6;
  const CANVAS_WIDTH = 1280;
  const CANVAS_HEIGHT = 800;
  const ARC_INTERVAL = 160;

  const deg2Rad = ang => (ang * Math.PI) / 180;
  const sin = ang => Math.sin(deg2Rad(ang)).toFixed(2);

  const arcStartX = (idx + 1) * ARC_INTERVAL;
  const arcEndX = sin(90 - deg) * arcStartX;
  const arcEndY = CANVAS_HEIGHT - sin(deg) * arcStartX;

  const projectedX = CANVAS_HEIGHT * Math.tan(deg2Rad(90 - deg));
  const projectedY = CANVAS_WIDTH * Math.tan(deg2Rad(deg));
  const x = projectedY < CANVAS_HEIGHT 
    ? CANVAS_WIDTH : projectedX;
  const y = projectedY < CANVAS_HEIGHT 
    ? CANVAS_HEIGHT - projectedY : 0;

  return `
  <line x1="0" y1="${CANVAS_HEIGHT}" x2="${x}" y2="${y}"/>
  <circle cx="${x}" cy="${y}" r="${MAJOR_CIRCLE}"></circle>
  <text x="${x}" y="${y + TEXT_OFFSET}">${deg}°</text>
  <path d="M ${arcStartX} ${CANVAS_HEIGHT} A ${arcStartX} ${arcStartX} 0 0 0 ${arcEndX} ${arcEndY}"/>
  <circle cx="${arcEndX}" cy="${arcEndY}" r="${MINOR_CIRCLE}"></circle>
  <circle cx="${arcStartX}" cy="${CANVAS_HEIGHT}" r="${MAJOR_CIRCLE}"></circle>
  <text x="${arcStartX}" y="${CANVAS_HEIGHT + TEXT_OFFSET}">${sin(deg)}</text>
  `;
}
Enter fullscreen mode Exit fullscreen mode

At the bottom of the function we collate a number of SVG fragments to draw the line capped with a circle containing the text label. Next we use the path instruction to draw an arc from the bottom edge of the grid up to the line. We finish with a small circle where the arc and line intersect and a larger circle with a text label on the bottom edge of the grid. The instructions are compiled using a template literal (string) with pre-calculated valued inserted (interpolated) to form a fragment.

At the top of the function we have some constant values defined to set;

  • the radii of the small and large circles
  • the size of the canvas (width and height)
  • the vertical offset required to position the text
  • the interval (incremental radii) at which the arcs are drawn.

In between we have a couple of arrow functions and some calculated values.

  const deg2Rad = ang => (ang * Math.PI) / 180;
  const sin = ang => Math.sin(deg2Rad(ang)).toFixed(2);
Enter fullscreen mode Exit fullscreen mode

The above arrow functions convert an angle in degrees to radians, which is what the Math methods use. The sin function converts the numeric value for the angle in degrees to the Sine value to two decimal places, largely for presentation.

The calculations include the start and end points of the arcs and the projected end of the radial line. The projected end location is refined to 'clip' it so the line remains within the bounds of the grid be it off the top or side.

We are not quite finished yet

The cutting mat is coming along nicely.

Cutting mat with standard angle lines

But there is more to come. In the next post we will be adding some aesthetic ratios such as the Golden Ratio.

Top comments (0)

11 Tips That Make You a Better Typescript Programmer

typescript

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!