Paul Cowan

Nomadic cattle rustler and inventor of the electric lasso

Animated 3D Pyramid With CSS3 and SASS

Below is the end result of this post or you can see a live demo here. This will not work for IE11 or before.

The sass/css of this post can be found on github here.

In the dark ages of at least 4 years ago, jQuery used to be the defacto way of creating smooth transitions between elements but now css animations and their promise of hardware accelaration are becoming a standard in all modern browsers. Vendor prefixed css rules are now disappearing into the ether with the usual exception of internet explorer as the standard becomes ratified.

I’ve only just recently discovered the 3D qualities of css3 and I found it pretty easy to cobble together this rotating cube. You can find the css/sass here.

I then created a hexagon which was pretty similar. Here is the sass.

I then tried to create the pyramid in the screenshot above which took me considerably longer to acheive the end result.

CSS Coordinate System

Before diving into the code, it is important to realise that the coordinate system used by css transforms to position elements is slightly different than the 3D coordinate system you may have previously learned in geometry. In the css coordinate system, the y-axis and the z-axis are the positioned the other way round from what I learned in maths with the y-axis acting as the vertical axis and the z-axis acting as the guage to slide elements forwards or backwards from the user.

Pyramid Container

The pyramid will be constructed of five divs with four divs making up the triangular faces and a rectangular div for the base.

Below is the markup of the pyramid structure:

pyramid.html
1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="row">
  <div class="row">
    <div class="pyramid-container">
      <div id="pyramid">
        <div class="base"></div>
        <div class="front"></div>
        <div class="back"></div>
        <div class="right"></div>
        <div class="left"></div>
      </div>
    </div>
  </div>
</div>

Line 6 contains the opening tag for the pyramid-parent div that will act as a container for the pyramid structure.

The css for the pyramid-parent is below:

parent.css
1
2
3
.pyramid-parent {
  perspective: 800px;
}

The perspective rule on line 2 defines how the depth of the 3D scene is rendered. Think of perspective as a distance from the viewer to the object. If you apply 3D transforms without setting the perspective, elements appear flattened.

The pyramid-parent element encloses a further div with an id of pyramid that has the following css rules assigned to it:

pyramid.css
1
2
3
#pyramid {
  transform-style: preserve-3d;
}

The transofrm-style rule on line 6 specifies how the children of an element are positioned in 3D space or are simply flattened. The default is flat and a value of preserve-3d instructs the browser to position the elements in 3D-space. Without this property set, the pyramid would appear as a 2d triangle. The screenshot below shows how the pyramid looks without the preserve-3d value set:

CSS Triangles

The first challenge was how to create triangles using only css. Some slight of hand and a bit of css skullduggery is required to create an equilateral triangle like below:


Below is the css that creates the effect:

skullduggery.css
1
2
3
4
5
6
7
8
9
.skullduggery {
  width: 0;
  height: 0;
  border-left: 200px solid transparent;  /* left arrow slant */
  border-right: 200px solid transparent; /* right arrow slant */
  border-bottom: 200px solid #2f2f2f; /* bottom, add background color here */
  font-size: 0;
  line-height: 0;
}

The secret to these triangles is creating giant borders to the two perpendicular sides to the direction you would like the triangle to point. Make the opposite side’s border the same size and background colour. The larger the border, the larger the triangle.

Pyramid Maths

Unsurprisingly, positioning elements in 3D is considerably more difficult than in 2D and thankfully, the trigonomic ratios came to the rescue to correctly judge both the length of the elements and the angles of the pyramid.

Below is an image that labels the important parts of the pyramid:

All positioning takes place around the yellow right angle triangle in the above diagram. I first of all determined that I would like an angle of 60° for the slant angle of the triangle or the angle between the apothem and the base. The apothem is the slant height of a lateral face of the pyramid. With this angle and assigining a width to the base of the triangle, I could work out both the height of pyramid and the apothem height.

Once I know this, I can determine the lengths:

  • apothem = (½ Base) / cos(α)
  • height = (½ Base) * tan(α)
  • apex angle = 180 - 90 - α Where α = 60° and I took the Base = 270px.

One of the nice features of sass is that we can use variables like you would in a normal programming language to stop repeating the same values in css and also mean I can calculate other values from existing variables, something very lacking in current css.

variables.scss
1
2
3
4
5
$base: 270px;
$half-base: ($base / 2);
$apothem: 270px;  //(1/2 base) / cos(α)
$apex-angle: 30deg; // apex angle = 180 - 90(right-angle) - α
$base-move: 0 - ($apothem - $half-base);

I am using node-sass and I could not find a way of using the trig functions in the sass. This is possible with compass in ruby sass but I don’t know of a way in node-sass of achieving this so I had to calculate the value of the apothem height in a calculator first.

What I wanted to achieve with these vaiables was to be able to only set the $base width variable and every other value would be derived from that. Sadly as I cannot reference the trigonomic cosine trig function from the sass, I had to manually set the $apothem or slant height variable.

The $apex-angle on line 4 is the angle each triangle will be rotated along the x axis in order to tilt each triangle into the centre or apex.

Constructing the pyramid

I will now break down the steps I took to arrange the base and four sides of the pyramid. I will omit the many wrong turns I took in getting here.

Below is another view of the markup that makes up the pyramid:

hyramid.html
1
2
3
4
5
6
7
8
9
<div class="pyramid-container">
  <div id="pyramid">
    <div class="base"></div>
    <div class="front"></div>
    <div class="back"></div>
    <div class="right"></div>
    <div class="left"></div>
  </div>
</div>

Below are the css rules that are assigned to the base div or rectangular base of the pyramid:

base.css
1
2
3
4
5
6
7
8
9
10
11
12
13
.base {
  position: absolute;
  width: $base;
  height: $base;
  background-color: rgba(147,81,166,0.9);
  transform: rotateX(90deg) translate3d(0px, 0px, $base-move);
  opacity: .5;

  &:after {
    content: "5";
    left: 112px !important;
    top: 93px !important;
  }}

The width and height of the div are set on lines 3 and 4 and the transform property on line 6 is arguably the most important css3 property when it comes to positioning elements in the 3D space. This property allows you to rotate or move elements in each of the x, y or z 3D coordinate axes. With the base div, I use rotateX to rotate the element 90° along the x or horizontal axis axis to give the impression the div is lying flat and viewed at an angle.

The translate3d property is also used to move the div along the z axis. The translate3d property takes 3 values that can be used to move the element along the x, y or z axis respectively. In this example I am using the $base-move variable that was derived from the base div width to shift the div away from the user along the z axis.

base.scss
1
2
3
4
$base-move: 0 - ($apothem - $half-base);
.base {
  transform: rotateX(90deg) translate3d(0px, 0px, $base-move);
}

This shifts the div along the z axis or appears to move the div away from the user. This value is important when it comes to positioning the four bases of the triangle divs. Below is how the base looks with these rules applied:

The following generic rules are applied to the triangle divs:

traingles.scss
1
2
3
4
5
6
7
8
#pyramid div:not(.base) {
  position: absolute;
  border-left: $half-base solid transparent;
  border-right: $half-base solid transparent;
  border-bottom: $apothem solid;
  transform-origin: $half-base $apothem 0; /* bottom of trangle (1/2 Base, Apothem) */
  opacity: .5;
}

The transform-origin property on line 6 provides a convenient way to control the origin about which transforms using the css transform are applied.

Below is how the front face of the pyramid looks without the transform-origin property set:

And below is a screenshot with it set:

Each face will be moved along the x or horizontal axis by half the base width and will be moved down the y axis by the apothem or slant height. transform-origin must be used with the transform property as it only changes the positioning of transformed elements.

Each triangle will be rotated by a multiple of 90° to orient each triangle for a different face of the pyramid (respctively 0°, 90°, 180°, 270°).

With this in mind, each triangle will have its own rules to set this, in the case of the front face, the following css properties are set:

front.scss
1
2
3
4
5
6
7
8
#pyramid div.front {
  border-bottom-color: #e04545;
  transform: rotateY(0deg) translate3d(0px, 0px, $half-base) rotateX($rotate-X);

  &:after {
    content: "1";
  }
}

The next face has the same rules except the rotateY property which is increased by 90°.

back.scss
1
2
3
4
5
6
7
8
#pyramid div.back {
  border-bottom-color: #ccaf5a;
  transform: rotateY(90deg) translate3d(0px, 0px, $half-base) rotateX($rotate-X);

  &:after {
    content: "2";
  }
}

Until all triangles are positioned:

Animating the pyramid

The keyFrames rules allows you to gradually change one set of css rules for another which is specified in the from and to properties in the code below:

keyframes.scss
1
2
3
4
5
6
7
8
@keyframes spin {
  from {
    transform: rotateY(0deg);
  }
  to {
    transform: rotateY(360deg);
  }
}

In the above code the a gradual rotation around the y axis is specified by starting at 0 degrees and completing at 360 degrees. Once the keyframes rules are specified you then need to associate it with an element via the animation property.

animation.scss
1
2
3
#pyramid {
  animation: spin 8s infinite linear;
}

The name of the keyframes animation is specified along with the length of the animation. The infinite values states that the animation will continue infinitely and linear specifies that the animation speed is constant throughout the animation.

Animating Paths and Arcs With d3.js

You can see a working example of the end result of this post here and above is a screenshot.

The full and current source can be found here.

Following up from my last post, I have created yet another sine wave animation (YASWA) and I want to blog about how I achieved a smooth animation for the svg path shapes and arcs with d3.js.

I am not going to go over how to set up the basic shapes in the animation, you can find out how that is done by referring to my last post.

The 3 path shapes that I found challenging to animate are the red sine wave that is progressively added and removed from the document as the unit circle rotates along the x axis and the two blue angled arcs that form the small angled arc at the centre of the centre of the larger circle and the blue arc that expands around the circumference of the larger unit circle.

The first step is to append the paths to an svg group element. Their initial position is not important at this stage.

add.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const state = {};

const sine = state.sine = d3.svg.line()
        .interpolate('monotone')
            .x((d, i) => { return state.xScale(d); })
            .y((d, i) => { return state.yScale(Math.sin(d) + 1); });

const sineData = state.sineData = [];

state.sineCurve = state.xAxisGroup.append('path')
  .attr('class', 'sine-curve');

state.innerAngle = state.xAxisGroup
  .append("path")
  .attr("class", "arc");

state.outerAngle = state.xAxisGroup
  .append("path")
  .attr("class", "arc");

There is a state object (line 1) that is used as a container to hold a reference to all the svg shapes that will be transformed on each tick of the animation. The shapes are added as properties of the state object. The sine curve path shape is added to the state object on line 10 and the two path shapes that will render the arcs are bound as properties of the state object on lines 13 and 17 repectively.

Line 3 creates a d3.js line function that will be used to generate the rather cryptic svg path mini language instructions for the d attriute of the svg path shape that will plot the points of the sine curve.

A sineData array is initialised on line 8 and the line function will be executed for every element in the array and the x and y accessor functions on lines 5 and 6 will be executed exactly once for each element in the array. Obviously as the array is initially empty, these functions will not be invoked until there actually is some data.

Now that the the svg shapes are on the document, the next step is to kick off the animation function:

initial.js
1
2
3
4
5
state = this.addShapes(state);

state.time = 0;

this.animate(state, {forward: true});

line 3 initialises a state.time variable that will be incremented on each tick of the animation. This variable is central to all calculations that will be used to position.

The body of the animate function that is called on line 5 of the above code snippit takes the following form:

animate.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
animate(state, direction) {
  if(direction.forward) {
    state.time += state.increase;
  } else {
    state.time -= state.increase;
  }

  // position shapes


  if(direction.forward && state.time > (Math.PI * 2)) {
    direction = {backwards: true};
  }

  if(direction.backwards && state.time < 0) {
    state.time = 0;
    direction = {forward: true};
  }

  requestAnimationFrame(this.animate.bind(this, state, direction));
}

The state.time counter is either incremented or decremented on each tick of the animation depending on whether the animation is moving forwards or backwards.

Lnes 11 to 18 make a simple check of whether the time is greater than 2π, in which case it is time to start animating backwards or if the animation is moving backwards and time is less than 0 then the shapes need to move forward. The current direction is passed into the animate function with each call to requestAnimationFrame.

Line 20 calls requestAnimationFrame to progress the animation each time.

Animating the sine wave

My first attempt at incrementally drawing the sine wave was to add and remove the sinewave on each call to animate but this led to a very jerky horrible visual effect as the browser struggled to recreate the curve each time from the origin.

The solution was incredibly simple and I simply make a call to a function called progressSineGraph that is listed below:

progressSineGraph.js
1
2
3
4
5
6
7
8
9
progressSineGraph(state, direction) {
  if(direction.forward) {
    state.sineData.push(state.time);
  } else {
    state.sineData.pop();
  }

  state.sineCurve.attr('d', state.sine(state.sineData));
}

The state object that contains references to all the shapes and properties that are needed to perform the animation is passed into the function along with the direction argument that specifies whether the shapes are animating forwards or backwards.

The code between lines 2 and 6 either adds the current value of the time variable to the sineData array if the animation is progressing forward or removes the current head of the array if the animation is moving backwards. The elements of this array will be used to plot the sine graph.

On line 8 the d attribute of the path shape is set to the state.sine function that is listed below. As mentioned earlier, the d attribute contains the mini language instructions to plot the curve. The line function will generate the instructions by calling the function below for each element in the array:

sine.js
1
2
3
4
const sine = state.sine = d3.svg.line()
        .interpolate('monotone')
        .x((d) => { return state.xScale(d); })
        .y((d) => { return state.yScale(Math.sin(d) + 1); });

The line function will be called for each element int the array and x and y coordinates will be created by calling the x and y accessor functions that accept the current element as an argument.

I was pretty amazed how easy this technique is to animate shapes progressing or regressing.

The d3.js line function is a great abstraction and you can see what the generated output of the line function looks like below. The state.sine function has created the d attribute of the path shape that contains the instructions to draw the sine curve. I know which I would rather work with:

sinecurve.html
1
<path class="sine-curve" d="M0,0C0.14211661304959536,-0.15833395510988238,1.6268299307366627,-1.8125204868447078,1.9111111111111112,-2.1291935853485757S3.680057650459382,-4.0993994575052985,3.8222222222222224,-4.257738597705085"></path>

It is also worth noting that montone interpolation is set to ensure a smooth curve is drawn.

Animating the Arcs

All of the shapes in the animation are interlinked in some certain way and all the calculations of where to place the shapes on each tick of the animation are based on the incrementing state.time counter.

The black line that rotates from the centre of the circle to the circumference can be thought of as the hypotenuse of a right angle triangle. The hypotenuse in this document is an svg line shape with x1, y1, x2 and y2 properties that are set using cartesian coordinates to position the shape.

The two arcs that form the angle at the centre of the circle and at the circumference will also use the hypotenuse coordinates once they have been calculated.

Below is the code that positions the svg line shape on each tick of the animation:

hypotenuse.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const xTo = state.xScale(state.time);

const dx = (state.radius * Math.cos(state.time));
const dy = (state.radius * -Math.sin(state.time));

const hypotenuseCentre = xTo - dx;

const hypotenuseCoords = {
  x1: hypotenuseCentre,
  y1: parseFloat(state.hypotenuse.attr('y1')),
  x2: xTo,
  y2: dy
};

state.hypotenuse
  .attr('x1', hypotenuseCoords.x1)
  .attr('x2', hypotenuseCoords.x2)
  .attr('y2', hypotenuseCoords.y2);

Once the x1, y1, x2 and y2 corrdinates have been calculated, they are bound as properties of a hypotenuseCoords object that can be referrenced later. Lines 15 to 18 positions the shape.

We can now use basic trigonometry and the d3.js arc function to create the arcs. The arc function is also a to the d3.js svg line function as it also generates instructions for the path shape’s d attribute but it also has some additional properties such as the innerRadius, outerRadius, startAngle and endAngle properties that are specific to generating arcs.

Below is the code that sets the properties pf the two arcs:

arc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let angle = Math.atan2(
  (hypotenuseCoords.y2 - hypotenuseCoords.y1),
  (hypotenuseCoords.x2 - hypotenuseCoords.x1)
);

if(angle > 0) {
  angle = (-2 * (Math.PI) + angle);
}

angle = angle + Math.PI / 2;

const innerArc = d3.svg.arc()
        .innerRadius(8)
        .outerRadius(12)
        .startAngle(Math.PI/2)
        .endAngle(angle);

const outerArc = d3.svg.arc()
        .innerRadius(state.radius - 1)
        .outerRadius(state.radius + 3)
        .startAngle(Math.PI/2)
        .endAngle(angle);

The main calculation takes place between lines 1 and 10 and I would love somebody to tell me there is a better way than this. I always find that if I have used let to declare a variable and later reassign the variable after initialisation, this usually tells to me that I have got something wrong or I have taken the wrong path.

In order to set the angle each time, I need a to give the d3.svg.arc function a startAngle and an endAngle each time the function is called.

The startAngle properties of the 2 arcs are set on lines 15 and 20 and I am using a static value of PI \ 2 which is 90 degrees in radians. This is because a startAngle of 0 will position the startAngle at 12 o’clock on the circle and I want it to be at 3 o’clock or 90 degrees.

On line 1 the atan2 function is called to find the angle that the hypotenuse makes with the startAngle of the arc or 0 on the y axis property.

Arctan2 is different than arctan because it takes 2 arguments and returns an angle in the correct quadrant. If you are unfamiliar with the 4 trigonometric quadrants, you can read about it here.

The atan2 function takes into the account the two signs of the arguments that are passed in and places the angle in the correct quadrant. In this example the angle of the hypotenuse is found by subracting the y2 and y1 properties and subtracting x2 and x1 of the hypotenuse and passing them into atan2. atan2 returns the angle in radians between π and -π. Thus, atan2(1, 1) = π/4 and atan2(−1, −1) = −3π/4.

My first attempt at drawing the arcs did not contain the readjustment of the angle variable below;

angle.js
1
2
3
if(angle > 0) {
  angle = (-2 * (Math.PI) + angle);
}

Without this readjustment, the arcs where being rendered like this:

This is because atan2 returns the angles between π and -π and the angle was being calculated correctly for rays in quadrant 1 and 2 but not what was required for quadrants 3 and 4. The readjustment of (-2 * (Math.PI) + angle) gives same ray but in the correct direction when in quadrants 3 and 4 by adding the angle onto -2π.

Epilogue

I am very happy with the end result. If you can suggest a better way for any of the above then please a comment below.

I think I need to move on from sine waves.

Animating a Sine Wave With d3.js and MathJax

Below is an animated gif of the end result of this post or you can see the real page here.

The full and current source can be found here.

I have spent the last year learning some of the maths I should have learned about 27 years ago. One of the things that I have found interesting while learning maths is the relationship between the unit circle and a sine wave graph of y = sin(x).

A sine wave is a mathmatical curve that describes a smooth repetitive oscillation and the unit circle is a circle of radius 1 centred at the origin (0, 0). The unit circle can be used to find special trigonometric ratios as well as aid in graphing. There is also a real number line wrapped around the circle that serves as the input value when evaluating trig functions such as sine and cosine.

A sine wave is a periodic function or a function that repeats itself at regular intervals. The most important examples of periodic functions are the trigonometric functions that repeat themselves over intervals of . One journey around the unit circle is 360 degrees or in radians. A sine wave shows the excursion around the circle happening in time and is ultimately a circle expressed in time. I have used d3.js to illlustrate how the journey around the circle corresponds to the sine wave movement over time.

There are a number of concepts that I wanted to capture in the animation:

  1. Illustrate where the input value of the unit circle corresponds to the (x, y) coordinate on the horizontal axis of the sine wave.

  2. Demonstrate how the right angle is formed by the angle of the radius moving counter-clockwise around the unit circle.

  3. Show the number scale of the unit circle in radians as ratios in proper mathmatical notation in the browser in both the unit circle and the x axis of the sine graph. MathJax appears to be the only show in town that fits this requiremnt.

This process of creating graphs from the unit circle is often called unwrapping the unit circle.

Unit Circle Setup

The first step is to create the basic shapes that will illustrate the unit circle and use cartesian coordinates to position them. D3.js has an excelent scale abstraction that allows you to deal in a finer grained scale than pixels. I can now think of the dimensions of the svg document as a 20 x 20 grid which makes positioning things easier to reason about.

scale.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  componentDidMount() {
    const el = this.refs.sine;

    const dimensions = this.getDimensions();

    const xScale = d3.scale.linear()
            .domain([0, 20])
            .range([0, dimensions.width]);

    const yScale = d3.scale.linear()
            .domain([0, 20])
            .range([dimensions.height, 0]);

    const svg = d3.select(el).append("svg")
            .attr('class', 'svg-container')
            .attr("width", dimensions.width)
            .attr("height", dimensions.height);

On lines 6 and 10 of the above code, horizontal x and vertical y scales are created and bound to two variables. These d3 scale functions take the input domain of 0 to 20 and map it to an output range of either the viewport width for the horizontal x axis or the viewport height for the vertical y axis. It is much easier to think of cartesian coordinates ranging from 0 to 20 than the very fine grained pixel scale.

With the scales in place, it is much easier to start positioning the elements:

shapes.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    const initialX = xScale(12);
    const initialY = yScale(15);

    const firstAxisXCoord = -(radius * 1.5);

    const graphContainer = container.append("g")
            .attr("class", "circle-container")
            .attr('transform', `translate(${initialX}, ${initialY})`);

    graphContainer.append('circle')
      .attr('cx', 0)
      .attr('cy', 0)
      .attr('r', radius)
      .attr('class', 'unit-circle')
      .style('fill', 'none');

    const hypotenuse = graphContainer.append('line')
            .attr('class', 'hypotenuse')
            .attr('x1', 0)
            .attr('y1', 0)
            .attr('x2', 0)
            .attr('y2', 0);

    const opposite = graphContainer.append('line')
            .attr('class', 'opposite')
            .attr('x1', 0)
            .attr('y1', 0)
            .attr('x2', 0)
            .attr('y2', 0);

    const adjacent = graphContainer.append('line')
            .attr('class', 'adjacent')
            .attr('x1', 0)
            .attr('y1', 0)
            .attr('x2', 0)
            .attr('y2', 0);

    const dot = graphContainer.append('circle')
            .attr('cx', radius)
            .attr('cy', 0)
            .attr('r', 5)
            .attr('class', 'circle-guide')
            .attr('fill-opacity', 0.1);

    const verticalDot = graphContainer.append('circle')
            .attr('cx', 0)
            .attr('cy', 0)
            .attr('r', 5)
            .attr('class', 'vertical-guide')
            .attr('fill-opacity', 0.1);

    const joiningLine = graphContainer.append('line')
            .attr('class', 'joining-line')
            .attr('x1', firstAxisXCoord)
            .attr('y1', 0)
            .attr('x2', 0)
            .attr('y2', 0);

An svg group element or a g element is created on line 5. All elements will be added to this group or container. Transformations applied to an svg group or g element are applied to all elements in the group which makes it extremely useful.

The three lines that will form the right angle triangle are created and bound to the adjacent, opposite and hypotenuse variables using the d3.js svg line shape. The initial values of the triangle line’s x1, x2, y1 and y2 coordinates are irrelevant at this stage, they will be set on each tick of the animation. It is just important to get them onto the document during the initialisation phase.

The larger unit circle is created and appended onto the group on line 10 of the above snippet and another smaller circle is added on line 38 which will be positioned counter-clockwise around the large circle by setting its cx and cy coordinates on each tick of the animation. This will give the impression that the small circle is rotating around the larger circle.

Below is how things look so far:

The next stage is to add the number line in radians of the unit circle:

Number Scale

In order to divide the circle into 8 lines and position the labels, I can kill two birds with one stone by creating an array of objects which have label and angle properties that I can iterate over and create the lines and labels:

scale.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
addRadianNumberLine(container) {
  [
    {val: Math.PI/4, label: "$\\frac" + "{\\pi}4$"},
    {val: Math.PI/2, label: "$\\frac" + "{\\pi}2$"},
    {val: (3 * Math.PI) / 4, label: "$\\frac" + "{3\\pi}4$"},
    {val: Math.PI, label: "$\\pi$"},
    {val: (5 * Math.PI) / 4, label: "$\\frac" + "{5\\pi}4$"},
    {val: (3 * Math.PI) / 2, label: "$\\frac" + "{3\\pi}2$"},
    {val: (7 * Math.PI) / 4, label: "$\\frac" + "{7\\pi}4$"},
    {val: (2 * Math.PI), label: "${2\\pi}$"},
  ].forEach((ray) => {
    const cosX = radius * Math.cos(ray.val);
    const sinY = radius * -Math.sin(ray.val);

    const offsetX = (ray.val > Math.PI / 2 && ray.val < (3 * Math.PI) / 2)  ? -20 : -5;
    const offsetY = (ray.val > 0 && ray.val < Math.PI)  ? -35 : 0;

    container.append('g')
      .attr('class', 'tick')
      .attr('transform', `translate(${cosX + offsetX}, ${sinY + offsetY})`)
      .append('text')
      .text(() => ray.label);

    container.append('line')
      .attr('x1', 0)
      .attr('y1', 0)
      .attr('x2', cosX)
      .attr('y2', sinY);
  });
}

Lines 2 to 11 define the array of objects to iterate over. Each object contains a val property that can be thought of as an angle to move counter-clockwise around the unit circle for each element in the array. There is also a label property which is the text of the angle in radians. The cryptic syntax of the label is written in LaTex, which allows you to define mathmatical notation that can be displayed in the browser. Mathjax will later parse these labels into authentic looking math symbols in the browser. More on my difficulties with MathJax at the end of the post.

On lines 12 and 13 the x and y coordinates are determined for each angle value in the array. By finding the cos(x) * radius and sin(x) * radius of each angle in the iteration of objects, I can ascertain the (x, y) coordinates of the point on the circumference of where to position the radian label and of where do draw the dividing line that will show the number scale on my unit circle.

Lines 15 to 16 add offset values for each x and y coordinate to ensure the labels all appear outside of the circle and not flush on the circumference.

Line 18 adds a new svg group element and the textual label is added to this newly created group.

Line 24 simply adds a line from the centre of the circle to the x and y coordinates on the circumference.

Before MathJax does the parsing, the circle looks like this:

A similar approach is used to create the sine graph axis but instead of iterating over an array, a pair of d3.js axis are created and latex syntax is used for the tickValues of the x-axis.

axis.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
addSineAxis(state) {
  const intTickFormat = d3.format('d');

  const yAxis = d3.svg.axis()
          .orient('left')
          .tickValues([-1, 0, 1])
          .tickFormat(intTickFormat)
          .scale(state.yScaleAxis);

  state.graphContainer
    .append('g')
    .attr('class', 'y axis left')
    .attr("transform", `translate(${state.firstAxisXCoord}, 0)`)
    .call(yAxis);

  const xTickValues = [0, 1.57, 3.14, 4.71, 6.28];

  const piMap = {'0': '0', '1.57': '\\pi\\over 2', '3.14': '\\pi', '4.71': '3\\pi\\over 2', '6.28': '2\\pi'};

  const xAxis = d3.svg.axis()
          .orient('bottom')
          .tickValues(xTickValues)
          .innerTickSize(0)
          .outerTickSize(0)
          .tickFormat((x) => `$${piMap[x]}$`)
          .scale(state.xScaleAxis);

  state.graphContainer
    .append('g')
    .attr('class', 'x axis left')
    .call(xAxis);
}

The basic setup is now complete:

Animating the unit circle

My first step is to create a hash of values that I have unimaginatively called state that serves as a container for all the elements and dimensions that I am going to need to transform the svg elements on the documents. I am going to pass this state object into the animation function that will set the new position of the elements on each tick of the animation. After that, the animation is kicked off with a call to drawGraph:

drawGraph.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const state = {
  initialX,
  initialY,
  firstAxisXCoord,
  graphContainer,
  xScaleAxis,
  yScaleAxis,
  dot,
  opposite,
  adjacent,
  hypotenuse,
  joiningLine,
  verticalDot,
  axisDot,
  time: 0,
  xIncrement: 0
};

this.drawGraph(state);

Arguably the most important value in the above is the time property on line 15 which will provide the x value of the horizontal value of the sine graph. This will be incremented on each tick of the animation.

The drawGraph function is below:

drawGraph.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
drawGraph(state) {
  const increase = ((Math.PI * 2) / 360);

  state.time += increase;
  state.xIncrement += increase;

  this.drawSineWave(state);

  if(state.xIncrement > (Math.PI * 2)) {
    state.xIncrement = increase;
  }

  const axisDotX = state.xScaleAxis(state.xIncrement);

  state.axisDot
    .attr('cx', axisDotX)
    .attr('cy', 0);

  const dx = radius * Math.cos(state.time);
  const dy = radius * -Math.sin(state.time); // counter-clockwise

  state.dot
    .attr('cx', dx)
    .attr('cy', dy);

  state.hypotenuse
    .attr('x2', dx)
    .attr('y2', dy);

  state.opposite
    .attr('x1', dx)
    .attr('y1', dy)
    .attr('x2', dx)
    .attr('y2', 0);

  state.adjacent
    .attr('x1', dx)
    .attr('y1', 0);

  state.verticalDot
    .attr('cy', dy);

  state.joiningLine
    .attr('y1', state.dot.attr('cy'))
    .attr('x2', state.dot.attr('cx'))
    .attr('y2', state.dot.attr('cy'));

  requestAnimationFrame(this.drawGraph.bind(this, state));
}

Every time the drawGraph function is called, the time property of the state variable is incremented by roughly 1 degree on line 4 to simulate time moving along the x axis. I do the same on line 5 for the circle that travels along the x axis. I have a separate counter as I need to reset it to zero each time the xIncrement variable exceeds .

Line 7 calls the drawSine function that draws the sine wave on each tick of the animation. More on this later.

Lines 9 to 17 positions the small circle that traverses along the horizontal x. The counter for this coordinate is reset each time it exceeds .

The rest of the code in this function takes care of finding and positoning the shapes on the unit circle to simulate the small circle rotating counter-clockwise on the unit circle and constructing the right angle triangle.

Lines 19 and 20 find the next x and y coordinates of the next point on the circumference to position the small circle that rotates the larger circle and bind them to the variables dy and dx. I use minus in the expression radius * -Math.sin(state.time) because we want to simultate rotating back counter-clockwise. Once we have this coordintate it is easy to position the three lines that make up the right angled triangle on lines 26 to 38.

Line 49 calls requestAnimationFrame which tells the browser that you wish to perform an animation and requests the browser call a specific function, drawGraph in this instance, before the next browser repaint.

I am also using a lesser known overload of bind on line 37 that creates a new partially applied function with some or all of the arguments already bound each time it is called. I have blogged about this previously here. In this case, the state hash will be bound every time the function is called because I am passing it into bind as an extra argument after this. I use this technique a lot in javascript land.

Animating the Sine wave

Now to the meat and potato of the piece, namely animating a smooth sine wave. The sine wave is mathmatically a very simple curve and a very simple graph. It is a simple x-y plot with the x-axis representing time and the y-axis representing angular displacement around the unit circle.

Below is the drawSineWave function that is called on each tick of the animation:

drawSine.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
drawSineWave(state) {
  d3.select('.sine-curve').remove();

  const sineData = d3.range(0, 54)
          .map(x => x * 10 / 84)
          .map((x) => {
            return {x: x, y: - Math.sin(x - state.time)};
          });

  const sine = d3.svg.line()
          .interpolate('monotone')
          .x( (d) => {return state.xScaleAxis(d.x);})
          .y( (d) => {return state.yScaleAxis(d.y);});

  state.graphContainer.append('path')
          .datum(sineData)
          .attr('class', 'sine-curve')
          .attr('d', sine);
  }

The fist step on line 2 is to remove the previously plotted curve before recreating it on each tick of the animation.

Lines 4 to 8 creates the points that will be used to plot the sine graph against.

One Line 4 the an array of integers is initialized using the d3 range function, this will create an array of 54 elements ranging from 0 to 53:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13...etc...53]

The upper bound of the array which is 53 is chosen to fill the available width in the viewport

I will come back to why the calculation on line 5 is needed but on line 6 I do a further map of the array that was initialised on line 4 to create an x and y coordinate for each element in the array that can be used to plot the sine wave. The x coordinate is simply the element of the array and y is calculated by getting the negative sine of x because we are simulating the sine wave moving counter-clockwise. For each tick of the animation we simply subtract the state.time variable that we have used previously from x. If you look at the animation, you can see how the small circle in the middle of the unit circle moves up and down at exactly the same rate. Maths in action!

Below is how the sine wave looks without the transformation .map(x => x * 10 / 84) applied to the original array:

If we just use integers to plot the points we get the rough sine wave but if we use floats, we get a much smoother flowing sine wave. I multiply each value by 10 to space it out across the width and then divide by 84 to ensure I get a float back. 84 was arrived at by trial and error to ensure the wave spans across the graph.

Once I have my coordinates to plot the curve, the following code takes care of creating the curve on each animation tick:

monotone.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  const sineData = d3.range(0, 54)
          .map(x => x * 10 / 85)
          .map((x) => {
            return {x: x, y: - Math.sin(x - state.time)};
          });

  const sine = d3.svg.line()
          .interpolate('monotone')
          .x( (d) => {return state.xScaleAxis(d.x);})
          .y( (d) => {return state.yScaleAxis(d.y);});

  state.graphContainer.append('path')
    .datum(sineData)
    .attr('class', 'sine-curve')
    .attr('d', sine);

Lines 7 to 10 define a d3 line function that will be used by the svg path element on lines 12 to 15. The d3 line function can be thought of as a path generator for a line or in this case a curve as the interpolation mode is set to montone interpolation in order to create a smooth curve for the sine wave. The line function will take the data array (sineData) and convert it into the rather cryptic svg path mini language instructions that the svg path element on lines 12 to 15 will use to construct the curve. We define accessor functions on lines 9 and 10 that will be called for every x and y coordinate of the sineData array on line 1. A monotone interpolation will then be performed by the path function for each point. Every x and y is mapped to the correct scale on lines 3 and 4.

Lines 12 to 15 attach the path element and sets its data via the datum attribute. The d attribute sets the path data or the mini language of path commands that the line function on line 7 will generate. The x and y accessors of the line function are invoked exactly once for each element in the array.

MathJax

All that remains is to tell MathJax to parse the latex into math symbols. I cannot believe how hard I found this. Below is how the code ended up after a lot of coffee, profanity and self doubt:

mathjax.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
addMathJax(svg) {
  const continuation = () => {
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'], ["\\(","\\)"] ],
        processEscapes: true
      }
    });

    MathJax.Hub.Register.StartupHook("End", function() {
      setTimeout(() => {
        svg.selectAll('.tick').each(function(){
          var self = d3.select(this),
               g = self.select('text>span>svg');

          if(g[0][0] && g[0][0].tagName === 'svg') {
            g.remove();
            self.append(function(){
              return g.node();
            });
          }
        });
      }, 500);
    });

    MathJax.Hub.Queue(["Typeset", MathJax.Hub, svg.node()]);
  };

  wait((window.hasOwnProperty('MathJax')), continuation.bind(this));
}

First of all MathJax did not seem to play nicely with react, I have to use a wait function to suspend execution until the MathJax is available. On lines 12 to 18 can best be desribed as extreme hackery. I have tagged any element on the svg document that contains latex with the tick css class. I add a hook to mathjax that is called whenever it has done its first parse, I then remove the svg element that was added by MathJax and re-add it again. This causes MathJax to reparse the markup and the symbols are rendered correctly. I don’t know if it was the fact that this is a react site that made this so difficult but it really did not play well with this site.

The wait function is below for completeness:

wait.js
1
2
3
4
5
6
7
export function wait(condition, func, counter = 0) {
  if(condition || counter > 10) {
    return func();
  }

  setTimeout(wait.bind(null, condition, func, (counter + 1)), 30);
};

Epilogue

I found this hugely enjoyable and I am more than satisfied with the result. This probably means I should get out more.

If you disagree with any of the above or can suggest better ways then please leave a comment below.

Tangent to a Curve on Mousemove With d3.js and math.js

I am available for work right now, if you are interested then email me directly.

Following on from my last post on axes positioning, I have added the functionality to add a tangent to the curve on mousemove. You can see a working example here by dragging the mouse over the svg document.

Below is a screenshot of the end result:

The first steps are to create the elements that I will use to display the tangent indicator and also to hook up an event listener for mousemove on the svg document:

listener.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    g.append('circle')
      .attr('class', 'diff')
      .attr('cx', 0)
      .attr('cy', 0)
      .attr('r', 7)
      .style('fill', 'red');

    g.append('text')
      .attr('class', 'difflabel');

    g.append('line')
      .style('stroke', 'red')
      .attr('class', 'tangent')
      .attr('x1', xScale(0))
      .attr('y1', yScale(0))
      .attr('x2', xScale(0))
      .attr('y2', yScale(0));

d3.select('svg').on('mousemove', mouseMove);

The above creates a circle to indicate where on the curve the mouse is relative to the x axis. A label is created to display the coordinate that the mouse is currently on with respect to the curve and lastly I create a line that will display the tangent.

Line 19 adds a mousemove handler to the svg element that has been previously created with the code below:

svg.js
1
2
3
4
5
6
7
8
9
10
componentDidMount() {
  const el = this.refs.curve;

  const dimensions = this.getDimensions();

  this.svg = d3.select(el).append("svg")
        .attr("width", dimensions.width + dimensions.margin.left + dimensions.margin.right)
        .attr("height", dimensions.height + dimensions.margin.top + dimensions.margin.bottom)
        .append("g")
        .attr("transform", "translate(" + dimensions.margin.left + "," + dimensions.margin.top + ")");

The goal of the mousemove handler is to draw the tangent of the curve with respect to the x axis as the mouse moves over the svg document.

In geometry, the tangent line to a plane curve at a given point is the straight line that just touches the curve at that point.

I can get the x for the tangent line from the mouse coordinates of the mousemove function:

xcoordinate.js
1
2
3
4
const mouseMove = function() {
  const m = d3.mouse(d3.select('.curve').node());

  let x = m[0];

With this, I can use mathematical differential calculus to work out the tangent line. If I was to perform these steps with pen and paper, I would take the following steps:

  • Find the derivative of the curve
  • Substitute the x retreived from the mousemove event into the derivative to calculate the gradient (or slope for the US listeners) of the line.
  • Substitute the gradient of the tangent and the coordinates of the given point into the equation of the line in the format y = mx + c.
  • Solve the equation of the line for y.

What was surprising and enjoyable for me was that the steps on paper transferred into machine instructions quite well which is not always the case.

Before I plot the line, I want to position my circle and label onto the curve. I am already using the excellent mathjs library to get the coordinates to draw the curve:

data.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
getDataFromProps(expr) {
  const expression = math.parse(expr);


  const fn = (x) => {
    return expression.eval({x: x});
  };

  return d3.range(-10, 11).map( (d) => {
    return {x:d, y:fn(d)};
  });
}

drawCurve(data) {
  const xScale = this.xScale;
  const yScale = this.yScale;
  const svg = this.svg;

  const line = d3.svg.line()
          .interpolate('basis')
          .x( (d) => {return xScale(d.x);})
          .y( (d) => {return yScale(d.y);});

Line 2 of the above code uses mathjs’s parse function to create an expression from the string input of the form:

Once I have the expression, I can evaluate it with different values. Lines 5 and line 9 evaluates the expression for each x value in a predetermined range of values. Line 19 plots the line.

As I know what x is from the mouse event, I can use mathjs to parse my expression with respect to x and get the y coordinate to position my label on the curve:

ycoordinate.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const mouseMove = function() {
  const m = d3.mouse(d3.select('.curve').node());

  let x = m[0];

  let y = yScale(math.parse(me.props.expression).eval({
    x: xScale.invert(x)
  }));

  const point = {
    x: xScale.invert(x),
    y: yScale.invert(y)
  };

  if(point.x > maxX) {
    point.x = maxX;
    point.y = maxY;

    x = xScale(maxX);
    y = yScale(maxY);
  }

  g.select('.diff')
    .attr('cx', x)
    .attr('cy', y);

  g.select('.difflabel')
    .text( function() {
      const xLabel = Math.round(point.x);
      const yLabel = Math.round(point.y);

      return `(${xLabel}, ${yLabel})`;
    })
    .attr('dx', x + 10)
    .attr('dy', y + 8);

Lines 2 and 4 retrive x from the mouse event and line 6 evaluates y by parsing and evaluating the equation of the curve with respect to x. I then use the x and y coordinates to position my label elements.

Mathjs does not come with its own differentiation module to work out the derivative of the user entered expression but I found this plugin that seems to work out well for this task.

Armed with this module, it was plain sailing to create an equation for the tangent line that I could use to find out y values for the tangent.

tangent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const derivative = math.diff(math.parse(me.props.expression), "x");

const gradient = derivative.eval({x: point.x});

const yIntercept = getYIntercept(point, gradient);

const lineEquation = math.parse("m * x + c");

const getTangentPoint = (delta) => {
  const deltaX = xScale.invert(x + delta);

  const tangentPoint = {
    x: deltaX,
    y: lineEquation.eval({
      m: gradient,
      x: deltaX,
      c: yIntercept
    })
  };

  return tangentPoint;
};

const length = xScale(200);

const tangentPoint1 = getTangentPoint(+ length);
const tangentPoint2 = getTangentPoint(- length);

g.select('.tangent')
  .attr('x1', xScale(tangentPoint1.x))
  .attr('y1', yScale(tangentPoint1.y))
  .attr('x2', xScale(tangentPoint2.x))
  .attr('y2', yScale(tangentPoint2.y));
  • Line 1 creates a derivative function from the equation of the curve.
  • The derivative function is evaluted to get the gradient or slope for the US viewers on line 3.
  • The c constant or y-intercept of the equation of the line y = mx + c is retrieved on line 5.
  • Mathjs is employed to create an expression in the format of y = mx + c.
  • A getTangentPoint function is created that will be used to get the points at either end of the function.
  • Lines 24 - 26 create 2 points for x that will be far off the length and height of the svg document to give the impression of stretching off to infinity.
  • Each point gets its y value by calling the getTangentPoint funtion on line 9 that in turn will solve for x for the equation of the line function previously created on line 7.
  • Once we have the pair of points, the line can be plotted on lines 29 - 33.

You can see the end result here.

The following util function will return the y-intercept for a point and gradient:

yintercpet.js
1
2
3
export function getYIntercept(vertex, slope) {
  return vertex.y - (slope * vertex.x);
}

I am available for work right now, if you are interested then email me directly.

Negative Axes and Axes Positioning With d3.js

I am available for work right now, if you are interested then email me directly.

Up until now when I have been dealing with d3.js’s axes components, I have always kept the axes positive, i.e. both the x and y axes where showing values greater than 0.

I have been hacking around with this fun side project that takes the input of an algebraic expression and plots a graph for a sample range of values for x. You can checkout the source code here if you are interested.

Below is a screenshot of the end result:

It quickly became apparent that in order to show the curve of the expression properly, I would need to construct negative and positive x and y axes.

I have the following function below that constructs the data of x and y coordinates to plot the curve against which uses the excellent mathjs library to transform the string algebraic expression into a javascript function (line 2). I then create a sample range for x values ranging from -10 to 11 and evaluate the y coordinate for each item in the range by applying the function on line 5 to each item:

expression.js
1
2
3
4
5
6
7
8
9
10
11
12
  getDataFromProps(expr) {
    const expression = math.parse(expr);


    const fn = (x) => {
      return expression.eval({x: x});
    };

    return d3.range(-10, 11).map( (d) => {
      return {x:d, y:fn(d)};
    });
  }

Armed with this data, I can now construct my axes.

I first create the axis against a scale that is in proportion with the viewport dimensions.

axes.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    const dimensions = this.getDimensions();

    xScale = d3.scale.linear()
          .range([0, dimensions.width]);

    yScale = d3.scale.linear()
          .range([dimensions.height, 0]);

    const xAxis = d3.svg.axis()
            .scale(xScale);

    const yAxis = d3.svg.axis()
            .orient('left')
            .scale(yScale);

The domain function of d3 allows you to specify a minimum and maximum value as the range of values that we can use for a particular axis.

Below I am using d3’s extent function that returns the minum and maximum values of an array and is equivalent to calling d3.min and d3.max simultaneously.

extent.js
1
this.xScale.domain(d3.extent(data, function (d) {return d.x;}));

As I am supplying the values for the range of x values in the code above, I know that I will always have negative x values and positive x values.

The y coordinates are different depending on the function generated from the algebraic expression. Depending on the expression, there are basically 3 conditions I want to capture when displaying a curve.

The first case is when there are only positive y values:

The next case is when there are both negative and positive y values;

Lastly, only negative y values:

With this in mind, the code below creates a domain based on the minimum values of y and the maximum values of y:

minimamaxima.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const minY = d3.min(data, (d) => { return d.y; });
const maxY = d3.max(data, (d) => { return d.y; });

const nonNegativeAxis = minY >= 0 && maxY >= 0;
const positiveAndNegativeAxis = minY < 0 && maxY > 0;

let yScaleDomain, xAxisPosition;

if(nonNegativeAxis) {
  yScaleDomain = [0, d3.max(data, (d) => {return d.y;})];
}  else {
  yScaleDomain = d3.extent(data, (d) => {return d.y;});
}

this.yScale.domain(yScaleDomain);

this.svg.append('g')
  .attr('class', 'axis')
  .attr('transform', 'translate(' + dimensions.width/2 + ',0)')
  .call(yAxis);

I either start my domain at 0 or use d3.extent again to get the maximum and minimum values for y like I did before for x.

The last problem to solve was to position the x axis. In the 3 code samples below, I am capturing the domain for y and the x axis position for each condition.

This is easy if I only have negative or positive values for y. I can simply place the x axis at the bottom for only positive values:

pos.js
1
2
yScaleDomain = [0, d3.max(data, (d) => {return d.y;})];
xAxisPosition = dimensions.height;

When I have only negative values, then I can place the x axis at the top of the document:

neg.js
1
2
yScaleDomain = d3.extent(data, (d) => {return d.y;});
xAxisPosition = 0;

The interesting case was when I have both positive and negative values for y.

What I ended up doing was selecting all the ticks or labels from the y axis and finding the label that had 0 against it and from that I could use d3 to to select its position and then use that for my x axis position.

Below is the code that does that:

both.js
1
2
3
4
5
xAxisPosition = this.svg.selectAll(".tick").filter((data) => {
    return data === 0;
}).map((tick) => {
    return d3.transform(d3.select(tick[0]).attr('transform')).translate[1];
});

In the code above, I filter out all the other ticks apart from the 0 label. The zero tick is then passed into the map function which selects the transform attribute of the tick which might look something like this translate(0,280). The second value of translate, 280 in this instance gives me the position of the 0 label in the y axis. I can use this value to position my x axis.

Once I have the position of 0 in the y axis, I can position the axis to the document:

xaxis.js
1
2
3
4
this.svg.append('g')
    .attr('class', 'axis')
    .attr('transform', 'translate(0,' + xAxisPosition + ')')
    .call(xAxis);

When it comes to positioning the y axis, I simply divide the width by 2 and position it there:

ypos.js
1
2
3
4
this.svg.append('g')
  .attr('class', 'axis')
  .attr('transform', 'translate(' + dimensions.width/2 + ',0)')
  .call(yAxis);

I am available for work right now, if you are interested then email me directly.

Resize to Scale With d3.js

I am available for work right now, if you are interested then email me directly.

Following on from my last two posts, Perpendicular Bisectors of a Triangle With d3.js and Altitude of a Triangle With d3.js, I want to document how I ensured that my svg tranformation is resized to scale during a resize event or if the user selection can change.

Below is the end result of the last two blog posts:

You can see the result at this url.

You can change the current triangle effects by changing the radio buttons at the top. You can also drag and drop the triangle vertices by dragging the red circles at each triangle endpoint or vertex. This led to an interesting problem, which was how to how to maintain the current state or coordinates of all the shapes when the user selects a new effect from the radio buttons.

Another, more challening problem was to make sure that everything resized to the current ratio or scale if the browser window is resized. If you go to this url and resize the browser, you can see that everything re-renders nicely to scale. This does not happen out of the box. You need to code for this eventuality.

The bulk of the work takes place in the render method below:

render.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
function render(state = {}) {
  if(state.resizeFunc) {
    window.removeEventListener("resize", state.resizeFunc);
  }

  const viewportDimensions = availableViewPort();

  const availableHeight = viewportDimensions.h - 50;
  const availableWidth = availableHeight * 1.32;

  const margin = {top: 20, right: 100, bottom: 30, left: 100},
        width = availableWidth - margin.left - margin.right,
        height = availableHeight - margin.top - margin.bottom;

  d3.select("body").select("svg").remove();

  d3.selectAll('label').remove();
  d3.selectAll('input[type=radio]').remove();

  const xScale = d3.scale.linear()
          .domain([0, 20])
          .range([0, width]);

  const yScale = d3.scale.linear()
          .domain([0, 20])
          .range([height, 0]);

  let points;

  if(state.points) {
    points = {
      a: {
        x: xScale(state.xScale.invert(state.points.a.x)),
        y: yScale(state.yScale.invert(state.points.a.y))
      },
      b: {
        x: xScale(state.xScale.invert(state.points.b.x)),
        y: yScale(state.yScale.invert(state.points.b.y))
      },
      c: {
        x: xScale(state.xScale.invert(state.points.c.x)),
        y: yScale(state.yScale.invert(state.points.c.y))
      }
    };
  } else {
    points = {
      a: {x: xScale(0), y: yScale(0)},
      b: {x: xScale(6), y: yScale(18)},
      c: {x: xScale(16), y: yScale(2)}
    };
  }

  const xAxis = d3.svg.axis()
          .scale(xScale)
          .orient("bottom");

  const yAxis = d3.svg.axis()
          .scale(yScale)
          .orient("left");

  const svg = d3.select("body").append("svg")
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)
          .append("g")
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  svg.append('g')
    .attr('class', 'x axis')
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

  svg.append('g')
    .attr('class', 'y axis')
    .call(yAxis);

  const area = {
    xScale: xScale,
    yScale: yScale
  };

  area.currentEffect = state.currentEffect || drawMedians;

  area.points = points;

  addRadioButtons(area);

  const g = svg.append('g');

  area.g = g;
  area.svg = svg;

  const vertices = [
    {point: area.points.a, label: 'a'},
    {point: area.points.b, label: 'b'},
    {point: area.points.c, label: 'c'}
  ];

  drawTriangle(points, g);

  addCurrentEffects(area);

  addPointLabels(area, vertices);

  addGrabbers(area, vertices);

  const resizeFunc = render.bind(null, area);

  area.resizeFunc = resizeFunc;

  window.addEventListener("resize", area.resizeFunc);
}

The render function as you might expect, creates the svg document and renders all the shapes onto their specific coordinates as I outlined in the previous blog posts here and here. I use this function to both initially draw the shapes and also as the function that is attached to the resize event.

Below is the end of the render function that creates a hash that will keep track of the current state of the document or the coordinates of the all the shapes at any given time.

render2.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  const area = {
    xScale: xScale,
    yScale: yScale
  };

  area.currentEffect = state.currentEffect || drawMedians;

  area.points = points;

  addRadioButtons(area);

  const g = svg.append('g');

  area.g = g;
  area.svg = svg;

  const vertices = [
    {point: area.points.a, label: 'a'},
    {point: area.points.b, label: 'b'},
    {point: area.points.c, label: 'c'}
  ];

  drawTriangle(points, g);

  addCurrentEffects(area);

  addPointLabels(area, vertices);

  addGrabbers(area, vertices);

  const resizeFunc = render.bind(null, area);

  area.resizeFunc = resizeFunc;

  window.addEventListener("resize", area.resizeFunc);

Lines 1 to 3 create a the hash and assign the xScale and yScale d3 scale objects that allow you to deal with a finer granuated scale than pixels. The x and y axis in the documment were created using these scale objects and you can think in terms of placing these objects at coordinates on these scales, e.g. (1, 1).

Lines 6 to 29 assign properties to this area hash such as the vertices of the triangle that will be used to read and write to when drawing the shapes. I pass this structure into most functions.

Line 31 uses the lesser known partial application properties of the bind function to create a new version of the render function. This partial function when called, will always be called with the area hash as an argument, that contains all the information we need to reconstruct the document. Line 33 adds this function to the hash, we will use this to remove the event listener each time it is called or else there will be a memory leak. LIne 35 creates an event listener for the resize event and assigns the new version of render to this event.

The beginning of the render function below uses the new es6 default paramaters feature to allow render to be called with no arguments or called from the resize event with an argument. If it is called in response to a resize event then there will be a state argument. Lines 2 to 5 remove the event listener each time it is called.

render3.js
1
2
3
4
function render(state = {}) {
  if(state.resizeFunc) {
    window.removeEventListener("resize", state.resizeFunc);
  }

Everytime render is called, I create new xScale and yScale objects that reflect the current window size on line 15 - 21 below:

render5.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  const viewportDimensions = availableViewPort();

  const availableHeight = viewportDimensions.h - 50;
  const availableWidth = availableHeight * 1.32;

  const margin = {top: 20, right: 100, bottom: 30, left: 100},
        width = availableWidth - margin.left - margin.right,
        height = availableHeight - margin.top - margin.bottom;

  d3.select("body").select("svg").remove();

  d3.selectAll('label').remove();
  d3.selectAll('input[type=radio]').remove();

  const xScale = d3.scale.linear()
          .domain([0, 20])
          .range([0, width]);

  const yScale = d3.scale.linear()
          .domain([0, 20])
          .range([height, 0]);

Below is the code that will reassign the points of the triangle to scale from the state hash:

render3.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  if(state.points) {
    points = {
      a: {
        x: xScale(state.xScale.invert(state.points.a.x)),
        y: yScale(state.yScale.invert(state.points.a.y))
      },
      b: {
        x: xScale(state.xScale.invert(state.points.b.x)),
        y: yScale(state.yScale.invert(state.points.b.y))
      },
      c: {
        x: xScale(state.xScale.invert(state.points.c.x)),
        y: yScale(state.yScale.invert(state.points.c.y))
      }
    };
  } else {
    points = {
      a: {x: xScale(0), y: yScale(0)},
      b: {x: xScale(6), y: yScale(18)},
      c: {x: xScale(16), y: yScale(2)}
    };
  }

If we have a state hash then the invert method of the scale objects is used to get the value in pixels before using the scale to recreate the new x and y coordinates that are in scale with the new browser dimensions.

I also use partial application when adding the drag and drop event to the red circles at the vertices of the triangle on line 22 of the below:

drag.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function addPointLabels(area, vertices) {
  area.g.selectAll('text')
    .data(vertices)
    .enter().append('text')
    .attr("x", function(d){return d.point.x + 10;})
    .attr("y", function(d){return d.point.y + 10;})
    .attr('class', function(d) {return "label " + d.label;})
    .text( function(d) {
      const x = Math.round(area.xScale.invert(d.point.x));
      const y = Math.round(area.yScale.invert(d.point.y));

      return `${d.label.toUpperCase()} (${x}, ${y})`;
    })
    .attr("font-family", "sans-serif")
    .attr("font-size", "24px")
    .attr("fill", "black");
}

function addGrabbers(area, vertices) {
  const drag = d3.behavior
        .drag()
        .on("drag", draggable.bind(null, area));

After all this, I take great pleasure in resizing the window and watching everything beautifully scale.

You can checkout the github repo that contains all the code for this.

I am available for work right now, if you are interested then email me directly.

Perpendicular Bisectors of a Triangle With d3.js

Following up from my last post on how to draw the altitude of a side of a triangle through a vertex, I wanted to draw the 3 perpendicular bisectors of a triangle and the circumcircle of the triangle.

Let us get some definitions for these terms, the perpendicular bisectors of a circle are described as:

the lines passing through the midpoint of each side of which are perpendicular to the given side.

Below is a triangle with one perpendicular bisector running through side AB .

The circumcircle of a triangle is:

The point of concurrency of the 3 perpendicular bisectors of each side of the triangle.

The centre point of the circumcircle is the point of intersection of all the perpendicular bisectors of a triangle.

Below is a triangle with all 3 perpendicular bisectors and the circumcircle drawn with d3.js.

The first step was to draw one perpendicular bisector of a triangle.

I chose 3 arbitary points for the vertices of the triangle.

points.js
1
2
3
4
5
const points = {
  a: {x: xScale(1), y: yScale(1)},
  b: {x: xScale(5), y: yScale(19)},
  c: {x: xScale(17), y: yScale(6)}
};

This is all the information I need, to calculate the perpendicular bisectors and the circumcircle.

If I wanted to find the perpendicular bisector of AB using pen and paper, I would perform the following steps:

  • I would find the gradient or slope (for US readers) of the point AB.
  • I would then find the perpendicular gradient or slope which would give me the ratio of rise over run that the perpendicular line flows through. If lines are perpendicular then M1 x M2 = -1.
  • I would find the midpoint of the line using the distance formula ((x1 + x2 / 2), (y1 + y2 / 2)).
  • I could then plug these values into the equation of a line which takes the form of y = mx + c.

I have blogged previously in this post about how to set up the graduated x and y axis and a more managable scale for positioning vertices etc.

My first step was to find the perpendicular bisector of the line AB.

Below are two helper functions that take javascript point objects as arguments with x and y properties that map to coordinates and return either a gradient/slope or the perpendicular gradient/slope that occurrs between the 2 coordinates:

gradient.js
1
2
3
4
5
6
7
const gradient = function(a, b) {
  return ((b.y - a.y) / (b.x - a.x));
};

const perpendicularGradient = function (a, b) {
  return -1 / gradient(a, b);
};

Below is a helper function to find the midpoint between two vertices or points:

midpoint.js
1
2
3
const midpoint = function(a, b) {
return {x: ((a.x + b.x) / 2), y: ((a.y + b.y) / 2)};
};

Using these values, I can then find the y-intercept or the point where the perpendicular line will cut the y-axis.

Below is a function that will find the y-intercept given a vertex and a gradient/slope:

yintercept.js
1
2
3
function getYIntercept(vertex, slope) {
  return vertex.y - (slope * vertex.x);
}

You can think of the above function as rearranging y = mx + c to solve for c or c = y - mx.

All that is left is to find the x-intercept or the point where the bisector line cuts the x-axis.

Below is the code that brings this all together:

perpendicular-bisector.js
1
2
3
4
5
function perpendicularBisector(a, b) {
  const slope = perpendicularGradient(a, b),
        midPoint = midpoint(a, b),
        yIntercept = getYIntercept(midPoint, slope),
        xIntercept =  - yIntercept / (slope);

The x-intercept on line 5 is again re-arranging the equation of the line formula y = mx + c to solve for x.

The finshed function looks like this and there are a number of if statements I had to add for the conditions when the slope or gradient function might end up undefined or equalling infinity when it encounters horizontal or vertical values that have catches with the formula. I would love to know if there is an algorithm that will avoid such checks:

perpendicularBisector.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function perpendicularBisector(a, b) {
  const slope = perpendicularGradient(a, b),
        midPoint = midpoint(a, b),
        yIntercept = getYIntercept(midPoint, slope),
        xIntercept =  - yIntercept / (slope);

  if((yIntercept === Infinity || yIntercept === -Infinity)) {
    return drawTriangleLine(g, {
      x1: xScale(midPoint.x),
      y1: yScale(0),
      x2: xScale(midPoint.x),
      y2: yScale(20)
    });
  }

  if((a.x === b.x) || isNaN(xIntercept)) {
    return drawTriangleLine(g, {
      x1: xScale(0),
      y1: yScale(midPoint.y),
      x2: xScale(20),
      y2: yScale(midPoint.y)
    });
  }

  if(xIntercept < 0 || yIntercept < 0) {
    return drawTriangleLine(g, {
      x1: xScale(xIntercept),
      y1: yScale(0),
      x2: xScale(20),
      y2: yScale((slope * 20) + yIntercept)
    });
  }

  drawTriangleLine(g, {
      x1: xScale(xIntercept),
      y1: yScale(0),
      x2: xScale(0),
      y2: yScale(yIntercept)
    });

  return {vertex: midPoint, slope: slope};
}

The drawTriangleLine function looks like this and simply adds a d3.js line:

drawTriangleLine.js
1
2
3
4
5
6
7
8
9
const drawTriangleLine = function drawTriangleLine(group, vertices) {
  group.append('line')
    .style('stroke', 'green')
    .attr('class', 'line')
    .attr('x1', vertices.x1)
    .attr('y1', vertices.y1)
    .attr('x2', vertices.x2)
    .attr('y2', vertices.y2);
};

Every time I call the perpendicularBisector function, I return an object that contains a vertex and point that I can use to draw the circumcircle.

return.js
1
return {vertex: midPoint, slope: slope};

All that is left is to draw the circumcircle and here is the function I wrote to do just that:

circumcircle.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function drawCirumCircle(lineA, lineB) {
  if(!lineA || !lineB) {
    return;
  }

  const x1 = - lineA.slope,
      y1 = 1,
      c1 = getYIntercept(lineA.vertex, lineA.slope),
      x2 = - lineB.slope,
      y2 = 1,
      c2 = getYIntercept(lineB.vertex, lineB.slope);

  const matrix = [
    [x1, y1],
    [x2, y2]
  ];

  const circumCircleCentre = solveMatrix(matrix, [c1, c2]),
      dist = distance(convertPoint(points.b), circumCircleCentre);

  g.append('circle')
   .attr('cx', xScale(circumCircleCentre.x))
   .attr('cy', yScale(circumCircleCentre.y))
   .attr('r', xScale(dist))
   .attr('class', 'circumcircle')
   .attr('fill-opacity', 0.0)
   .style('stroke', 'black');
}

In order to find the centre of the circumcirle or the point of intersection of the perpendicular bisectors, the function takes two arguments lineA and lineB which are two of the perpendicular bisectors of the traingle. The function then arranges these line objects into y = mx + c format on lines 6 to 11 of the above. I then solve these equations simulataneously using matrices and specifically using cramer’s rule to find the point where the line intersect.

Once I have the 2x2 matrix assembled on lines 13-16, I then pass it to the solveMatrix function with the 2 y-intercept values that will apply cramer’s rule:

solveMatrix.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function det(matrix) {
  return (matrix[0][0]*matrix[1][1])-(matrix[0][1]*matrix[1][0]);
}

function solveMatrix(matrix, r) {
   const determinant = det(matrix);
   const x = det([
      [r[0], matrix[0][1]],
      [r[1], matrix[1][1]]
    ]) / determinant;

   const y = det([
     [matrix[0][0], r[0]],
     [matrix[1][0], r[1]]
   ]) / determinant;

  return {x: Math.approx(x), y: Math.approx(y)};
}

I now have the point of intersection of the perpendicular bisectors. All I need to know now is the radius of the circle. The calculation I used is to use the distance formula. From the point of intersection we just found to one of the vertices of the triangle.

Below is a helper function for the distance formuala:

distance.js
1
2
3
function distance(a, b) {
  return Math.floor(Math.sqrt(Math.pow((b.x - a.x), 2) + Math.pow((b.y - a.y), 2)));
}

All I have to do now is draw the circle from the two knowns, i.e. the point of intersection and the radius:

circle.js
1
2
3
4
5
6
7
8
9
10
  const circumCircleCentre = solveMatrix(matrix, [c1, c2]),
      dist = distance(convertPoint(points.b), circumCircleCentre);

  g.append('circle')
   .attr('cx', xScale(circumCircleCentre.x))
   .attr('cy', yScale(circumCircleCentre.y))
   .attr('r', xScale(dist))
   .attr('class', 'circumcircle')
   .attr('fill-opacity', 0.0)
   .style('stroke', 'black');

Here is a working jsbin that illustrates all I have wrote about.

I have also added drag and drop so you can drag the vertices around by the red circles and watch it all redraw.

Please leave a comment below if I could have achieved this in a more efficient way.

Altitude of a Triangle With d3.js

I’m back at college learning the maths that I should have learned a long time ago. I am also trying to kill 2 birds with one stone by using what I’ve learned to help me learn d3.js at the same time. The task I set myself this week was to draw the altitude of a triangle through a point.

In geometry, an altitude of a triangle is a line segment through a vertex (point) and perpendicular (i.e. forming a right angle with) a line containing the base (the opposite side of the triangle). This line containing the opposite side is called the extended base of the altitude.

My first steps are to create a scale that is of much lower resolution than the finely grained pixels and below is the code that creates both the scale and the axis. I blogged about scales in more detail in my last blog post:

scale.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var margin = {top: 20, right: 100, bottom: 30, left: 100},
    width = 660 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var xScale = d3.scale.linear()
    .domain([0, 20])
    .range([0, width]);

var yScale = d3.scale.linear()
    .domain([0, 20])
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left");

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

svg.append('g')
    .attr('class', 'x axis')
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

svg.append('g')
    .attr('class', 'y axis')
    .call(yAxis);

I then created 3 arbitrary vertices for my triangle that would fill the x and y axis as much as possible:

points.js
1
2
3
var a = {x: xScale(1), y: yScale(1)},
    b = {x: xScale(6), y: yScale(18)},
    c = {x: xScale(14), y: yScale(6)};

My next task was to then try and draw a triangle from these points. Below is what I ended up with before I explain the solution:

After a bit of trial and error by first of all trying to draw lines with the line function, I came across d3’s path function:

path.js
1
2
3
4
5
6
7
8
svg.append('path')
    .attr('d', function(d) {
      return 'M ' + a.x +' '+ a.y +
             ' L' + b.x + ' ' + b.y +
             ' L' + c.x + ' ' + c.y +
             ' z';
    })
    .style('stroke', 'blue');

This is effectively a DSL or mini-language for drawing shapes.

I’ll add a translation for each line:

  • 'M ' + a.x +' '+ a.y + - This means place a point at the x and y coordinates of the point I previously created with var a = {x: xScale(1), y: yScale(1)}. This is analgous to the starting point where you might place your pen.
  • ' L' + b.x + ' ' + b.y + - This means draw a line created from the point created above to the point b that was declared like this b = {x: xScale(6), y: yScale(18)}. Because we are using scales, we can pick nice friendly points like (6,18) rather than the harshness of pixels.
  • ' L' + c.x + ' ' + c.y + - draw a line to the c point
  • the z command closes the path.

I really like the path function as it is how a human would draw a triangle with pen and paper and is very easy to grok.

With the easy bit done, I now wanted to draw the altitude through point A that would be perpendicular to line BC.

If I was doing this with pen and paper, I would perform the following steps:

  1. I would find the gradient (or slope for those of you from the US) of the line BC.
  2. I would use this gradient/slope to create the equation of the line in y = mx + c format.
  3. I would find the perpendicular gradient/slope of BCthat I can use to create an equation of the line that will go through point A and will be perpendicular to point C in y = mx + c format.
  4. I would then solve these simultaneously to find the point of intersection from point A to the point on BC that was perpendicular to point A.

What I quickly found out was that transfering pen to paper calculations to machine instructions or javascript was extremelly difficult and different. Here are the steps I took:

Like in the pen and paper version, I found the grandient of BC and created this function:

gradient.js
1
2
3
var gradient = function(a, b) {
  return (b.y - a.y) / (b.x - a.x);
};

I then created a function to find the perpendicular gradient using the graident found in point 1:

perpendicular.js
1
2
3
var perpendicularGradient = function (a, b) {
  return -1 / gradient(a, b);
};

In order to get both line equations into y = mx + c, I needed a function that would take a point and a gradient and give me the y-intercept or the point where the line cuts the y-axis:

y-intercept.js
1
2
3
function getYIntercept(vertex, slope) {
  return vertex.y - (slope * vertex.x);
}

I could then get the y, mx and c values of y + mx = c for both lines so I could solve the equations simultaneously.

points.js
1
2
3
4
5
6
7
8
  var slope = gradient(a, b),
      x1 = - slope,
      y1 = 1,
      c1 = getYIntercept(a, slope),
      perpendicularSlope = perpendicularGradient(a, b),
      x2 = - perpendicularSlope,
      y2 = 1,
      c2 = getYIntercept(vertex, perpendicularSlope);

I would use the substitution method or the addition method to solve a series of equations with pen in paper but writing it in code was a different matter and matrices seemed like the obvious fit. Please write a comment below if there is a more efficient way. There is cramer’s law which seemed ideal for my needs. I needed to get my vars into the following format:

matrix.js
1
2
[x1, y1] [x]  = [c1]
[x2, y2] [y]  = [c2]

Below is my altitude function that gets the values into matrices before passing to a function that will use cramer’s law to find the point of intersection.

altitude.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function altitude(vertex, a, b) {
  var slope = gradient(a, b),
      x1 = - slope,
      y1 = 1,
      c1 = getYIntercept(a, slope),
      perpendicularSlope = perpendicularGradient(a, b),
      x2 = - perpendicularSlope,
      y2 = 1,
      c2 = getYIntercept(vertex, perpendicularSlope);

  var matrix = [
    [x1, y1],
    [x2, y2]
  ];

  var result = solveMatrix(matrix, [c1, c2]);

Below is the function I used to first of all find the determinant of the matrix before applying cramer’s law:

cramer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function det(matrix) {
  return (matrix[0][0]*matrix[1][1])-(matrix[0][1]*matrix[1][0]);
}

function solveMatrix(matrix, r) {
   var determinant = det(matrix);
   var x = det([
      [r[0], matrix[0][1]],
      [r[1], matrix[1][1]]
    ]) / determinant;

   var y = det([
     [matrix[0][0], r[0]],
     [matrix[1][0], r[1]]
   ]) / determinant;

  return {x: Math.approx(x), y: Math.approx(y)};
}

The above returns a point that I can then use to join point A to the point of intersection returned from the solveMatrix function.

line.js
1
2
3
4
5
6
7
  g.append('line')
    .style('stroke', 'red')
    .attr('class', 'line')
    .attr('x1', xScale(vertex.x))
    .attr('y1', yScale(vertex.y))
    .attr('x2', xScale(result.x))
    .attr('y2', yScale(result.y));

I want to add drag and drop to rotate the triangle so I’ll probably need to put checks in for vertical and horizontal values for x and y but this will do for now.

Below is the end result of my troubles with the altitudes of all three vertices shown.

The hardest part was to solve the simultaneous equations and I belive there is a better and more efficient way that uses vectors to work this out but I have not covered this with my course as yet.

Here is a working jsbin of my efforts.

Grid Lines and the Equation of a Line With d3.js

I’ve recently gone back to college to do a higher maths class at night and I want to use some of the concepts I am learning in my programming to enforce what I have learned so far. I’ve been meaning to learn d3 for some time so this seems like a perfect opportunity to kill two birds with one stone. I thought I would start with something simple and have a draggble line that shows the equation of a straight line with respect to the two coordinates at the end of each line. The equation of a line may still exist in the memories of your shool days.

Here is a jsbin with the product of my fumblings. You can drag the line by either of the red circles at each end and the calculations will recalculate.

Below is the end result

I will now breakdown the code:

The first code blocks create an x and a y scale that will scale from 0 to 20 units in each axis which is preferable to using the much more granular pixels. These scales are used to create the labels on the x and y axis and also make positioning elements much, much easier.

scale.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var margin = {top: 20, right: 100, bottom: 30, left: 100},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var xScale = d3.scale.linear()
    .domain([0, 20])
    .range([0, width]);

var yScale = d3.scale.linear()
    .domain([0, 20])
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom")
    .innerTickSize(-height)
    .outerTickSize(0)
    .tickPadding(10);

var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left")
    .innerTickSize(-width)
    .outerTickSize(0)
    .tickPadding(10);
  • Lines 1 to 3 simply define some dimensions for the document and a margin.
  • Line 5 - 7 and lines 8 - 11 create an x axis and y axis respectively which will use the available width and height to spread out the 20 units of scale across each axis.

Scales

The definition of a scale from the d3 wiki is:

Scales are functions that map from an input domain to an output range.

Scales transform a number in a certain interval (called the domain) into a number in another interval called the range. If we look at the code used to create the x axis scale:

x-scale.js
1
2
3
var xScale = d3.scale.linear()
    .domain([0, 20])
    .range([0, width]);

The code above returns a converter function and binds it to the variable xScale. We will use this function a lot to convert the x coordinate of a point from the real pixel value into the 0 - 20 output range scale we have specified. The code specifies that the scale is linear and that it has a minum value of 0 and a maximum value of 20. This is achieved by passing the array [0, 20] as an argument to the domain function on line 2 of the above. The code on line 2 then specifies the range that is used to equally space out the 0 to 20 units. In this example we are using the available width of the svg document.

The exact same logic is used to create a converter function for the y scale but this time, the height of the svg document is used as the range.

y-scale.js
1
2
3
var yScale = d3.scale.linear()
    .domain([0, 20])
    .range([height, 0]);

The next step is to draw the x and y axis complete with graduated labels. The following code creates the x axis:

The x-axis looks like this:

x-axis.js
1
2
3
4
5
6
var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom")
    .innerTickSize(-height)
    .outerTickSize(0)
    .tickPadding(10);
  • The call to d3.svg.axis on line 1 unsurprisingly returns an instance of the axis object.
  • Line 2 makes a call to the scale function and passes in our xScale converter reference we created earlier. The labels or ticks as they are known as in d3 speak will be graduated with respect to the [0, 20] domain array of min and max values we specified.
  • Line 3 positions the axis at the bottom of the document.
  • On line 4 the innerTickSize function of the axis object is called to create the horizontal lines that are vertically aligned from each label or tick. I struggled with a good way of creating the gridlines for a long time and it turns out you need to pass in a negarive argument like -height in line 2 of the above because we want the lines to fill the full document and if we do not pass a negative value then grid lines flow down from the labels and not up. You can see this by adding and removing the line from the jsbin.

The yAxis is created in a similar fashion only the horizontal grid lines are creating by passing in -width into innerTickSize to create the horizontal lines.

Positioning the line and circles was very easy after I had my grid created and my scales set up. The line is created below:

line.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var line = {
    start: {x: 2, y: 3, type: 'start'},
    finish: {x: 14, y: 6, type: 'finish'}
  };

var g = svg.append('g');

g.append('line')
    .style('stroke', 'blue')
    .attr('class', 'line')
    .attr('x1', xScale(line.start.x))
    .attr('y1', yScale(line.start.y))
    .attr('x2', xScale(line.finish.x))
    .attr('y2', yScale(line.finish.y));
  • Line 6 creates an svg g element or container that can be used to group shapes together.
  • Lines 11 to 14 specify the start and end points of the line. What is important about this is that the xScale and yScale converter functions are used to place the line on the 0..20 scale.

The circle and text label objects that specify the coordinates of the end points follow a similar approach:

circle.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var lineData = [line.start, line.finish];

var circles = g
     .selectAll('circle')
     .data(lineData)
     .enter().append("circle")
     .attr('class', function(d){ return "circle " + d.type;})
     .attr('cx', function(d){return xScale(d.x);})
     .attr('cy', function(d){return yScale(d.y);})
     .attr('r', 10)
     .style('fill', 'red')
     .call(drag);

var text = g
     .selectAll('text')
     .data(lineData)
     .enter().append('text')
     .attr("x", function(d){return xScale(d.x);})
     .attr("y", function(d){return yScale(d.y + 1);})
     .attr('class', function(d) {return "text" + d.type;})
     .text( function (d) { return "( " + d.x  + ", " + d.y +" )"; })
     .attr("font-family", "sans-serif")
     .attr("font-size", "14px")
     .attr("fill", "red");

What is interesting about the above code is the data function call on lines 5 and 16. I liken this to data binding and a circle or text object is created for each element in the lineData array on line 1.

What I found confusing about the above code when I first encountered it is that selectAll works on elements that you are going to create rather than what you have created. The enter function on lines 6 and 17 is used to add the new elements to the document as the data changes.

All that is left is to display the code that handles the drag events:

drag.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var drag = d3.behavior
     .drag()
     .on("drag", function(d) {
        var circle = d3.select(this),
            line = d3.select('.line'),
            isStart = circle.classed('start'),
            textClass = isStart ? ".textstart" : ".textfinish",
            lineX = isStart ? 'x1' : 'x2',
            lineY = isStart ? 'y1' : 'y2',
            text = d3.select(textClass),
            title = d3.select('.title'),
            xStart = d3.format(",.0f")(xScale.invert(line.attr('x1'))),
            yStart = d3.format(",.0f")(yScale.invert(line.attr('y1'))),
            xFinish = d3.format(",.0f")(xScale.invert(line.attr('x2'))),
            yFinish = d3.format(",.0f")(yScale.invert(line.attr('y2')));

        text.text( function (d) { return "( " + d3.format(",.0f")(xScale.invert(d.x))  + ", " + d3.format(",.0f")(yScale.invert(d.y)) +" )"; });

        line.attr(lineX, d3.event.x).attr(lineY, d3.event.y);
        text.attr('x', d3.event.x).attr('y', d3.event.y - 20);
        circle.attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);

        title.text(equationOfLine({x: xStart, y: yStart}, {x: xFinish, y: yFinish}));
     });

The above code creates a drag function that I can attach to elements of the document.

I struggled for a while with getting the correct text for labels of the coordinates of the end points of the line and the main header label. The drag event handler on line 3 gets passed a d argument which is a d3 behaviour object that has the x and y mouse cooridinates of where the mouse was dragged to. I had to use xScale.invert and yScale.invert to get the correct scaled cooridinates for the text labels. The invert function returns the inverse mapping of range to domain. The rest of the code just positions the elements to the new mouse positions.

The drag function is then attached to each circle as it is created on the last line of the code below;

circle.js
1
2
3
4
5
6
7
8
9
10
var circles = g
     .selectAll('circle')
     .data(lineData)
     .enter().append("circle")
     .attr('class', function(d){ return "circle " + d.type;})
     .attr('cx', function(d){return xScale(d.x);})
     .attr('cy', function(d){return yScale(d.y);})
     .attr('r', 10)
     .style('fill', 'red')
     .call(drag);

Emberjs - Detail View Without Switching Routes

Rendering long lists of tabular data on the client is something I have spent a lot of time on and this dates back to my backbone days. You need to at the very least employ infinite scrolling which opens up other cans of worms. What if my tabular data has a cell with a link and I click the link and then click the back button. Unless I spend a lot of engineering effort, I will have lost my place in the infinitely scrolled list.

This was a problem I recently faced on the application that I am working on. We have a large table of infinitely scrolled rows of contact data that had links to the detail of each contact on each row. We started off with the links redirecting to a new route that displayed the individual contact’s details. This meant that when you clicked the back button from the detail view, the table would be re-renderd from scratch.

I came up with quite a nice and not so technical solution that avoided many dark hours of development. I came up with the idea of sliding out the view of the contact detail from the same route that contains the table. This means the user never loses their place in the table. Throw in some css3 animations and I am very happy with the solution. Below is an animated gif that shows what I have christened the x-drawer component:

Here is a working jsbin which shows the code in action. The x-drawer component is a container for other components and is declared in its nested form and any component can be placed inside it:

I actually use the component helper in the production app as I now use the drawer a lot but in the above gist I simply have a property showDetail that when set to true will activate the drawer.

The x-drawer component is actually very simple and the sliding animation is achieved with css3 animations and specifically css transitons that enables the drawer to slide out slowly rather than just appear. I used a combination of translateX and transform to slide out the view.

Another requirement that I had thrust apon me was to make sure the drawer was closed if the user clicked anywhere outside of the drawer. Below is the code that creates an overlay on the remaining screen real estate that when clicked sends the action to close the drawer:

overlay.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  _setup: Ember.on('didInsertElement', function() {
    var addOverlay, el, self;
    this._super.apply(this, arguments);
    el = this.$();
    self = this;
    addOverlay = function() {
      var overlay, rect;
      rect = el.get(0).getBoundingClientRect();
      overlay = $("<div class='drawer-overlay'></div>");
      overlay.css({
        position: "absolute",
        top: rect.top + "px",
        left: (rect.right - 10) + "px",
        height: rect.height + "px"
        }).appendTo('body');

      return overlay.one('click',function(e) {
        self.send("closeDrawer");
        e.stopPropagation();
        return e.preventDefault();
      });
    };

    el.one($.support.transition.end, addOverlay);

    el.get(0).offsetWidth;
    return el.addClass('open');
  }),

This was actually quite a simple solution to a difficult problem. My first thoughts where that I need to work on the performance of rendering the table but with this solution, the table is always present as we don’t have to naviagate away.