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;
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;
}
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>
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>
`;
}
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);
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.
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)