💾 Archived View for gemini.robrohan.com › 2021-04-24-matrix-fun.md captured on 2022-06-11 at 23:32:15. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2022-04-28)
-=-=-=-=-=-=-
---
author: rob
date: 2020-10-16 08:00:00+11:00
slug: matrix-fun
title: Fun with Matrices
tags: [kale, maths]
---
This post is a little bit contrived.
Since I've been playing around with 3D graphics and GPUs, I've become
fascinated with matrices (and maths in general). On top of that, I've been
looking for a reason to play with Jupyter Notebooks. I've become interested
in [literate programming](https://en.wikipedia.org/wiki/Literate_programming)
as well - it seems like a fantastic teaching tool.
So instead of doing what I was supposed to be doing this lovely Saturday, I
decided to try to write a super basic (non compatible) version of Jupyter
Notebooks for Javascript posts (I call it
[Kale](https://en.wikipedia.org/wiki/Kale_(moon))). I decided to write a
simple post about using Matrices in Javascript to try it out.
When you click the run button on any of the code blocks on this post, the
output of one block will be the input to the next block. You'll need to
execute them in order for them to work - if you skip one you'll get an
error.
One last thing - I am not a maths major or anything of the sort. If you've
found this while doing homework for school, you're probably better off
reading something else. If you're just a tinkerer / hacker / explorer you
might like it.
Ok, lets try out this slightly interactive post...
I think of matrices as little black boxes that transform a thing (usually a
set of numbers) into another thing (usually a set of numbers). I believe the
proper term is System of Equations.
Imagine you have a multi-threaded application that can run a set of
functions in parallel. You could define all the functions, store them in a
table, and then run them all at the same time.
If you wired up a bunch of these, you could make some interesting things -
like maybe a GPU or a quantum computer.
It'll be clearer with some examples.
For this post I am going to be using a matrix layout that is useful for 2D
programming. This was the easiest for me to get started with, and it scales
to more dimensions once you have it down.
The first matrix we'll talk about is the identity matrix. It looks like this:
<figure>
$
I=
\begin{bmatrix}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1 \\
\end{bmatrix}
$
</figure>
The identity matrix is like a 1 in standard maths. If you multiply anything
by 1, you'll get that same number back. Same thing with the identity matrix.
Anything you multiply by the identity matrix will give you back the original
matrix.
Also note that the main feature of a matrix is made up of rows and columns.
That's all that is up there. It's just an array of numbers layed out in rows
and columns - called a 3x3 matrix.
To represent this in some code, let's make a simple class in Javascript
(click run after having a look (you can make the textarea bigger)):
class Matrix { /** Create a new matrix with the given row and column size */ constructor(row, col, data) { if (!col || !row) { throw Error("Need column and row size"); } this.rc = [row, col]; if (data) { this.d = data; } else { this.d = new Array(row * col); } } } return Matrix;
On top of the identity matrix, there are a few other commonly used matrices
that are used for translation (moving on the x and y axis), rotation, and
scale. Here are what those look like:
<figure>
$
T=
\begin{bmatrix}
1 & 0 & tx \\
0 & 1 & ty \\
0 & 0 & 1 \\
\end{bmatrix}
$
$
S=
\begin{bmatrix}
sx & 0 & 0 \\
0 & sy & 0 \\
0 & 0 & 1 \\
\end{bmatrix}
$
$
R=
\begin{bmatrix}
\cos(\theta) & -\sin(\theta) & 0 \\
\sin(\theta) & \cos(\theta) & 0 \\
0 & 0 & 1 \\
\end{bmatrix}
$
</figure>
Where _tx_ and _ty_ are translation on the x and y axis, _sx_ and _sy_ are
scale on the x and y axis, and theta is the degree in radians to rotate. The
coolest one being rotation.
One thing you can do with these matrices is multiply them together to
describe how something should move from one place to another. Using the
class we made above, lets build a few matrices to describe a translation and
rotation:
const [Matrix, me] = arguments; // Translate to 30,30 const m1 = new Matrix(3,3, [ 1, 0, 30, 0, 1, 30, 0, 0, 1, ]); const deg = 270; const rad = (deg * Math.PI) / 180; // Rotate by 270 degrees const m2 = new Matrix(3,3, [ Math.cos(rad), -Math.sin(rad), 0, Math.sin(rad), Math.cos(rad), 0, 0, 0, 1, ]); return [m1, m2];
One of these matrices describe a translation by 30px on the X and Y axis,
and the other a rotation by 270 degrees.
yourself (or learn about) the [unit circle](https://en.wikipedia.org/wiki/Unit_circle).
However, you don't have to understand how it works "under the hood"
if you don't want to.
Now we have two matrices that we're going to multiply together. Multiplying
two matrices together is not intuitive at first. What you have to do is
multiply the rows of one matrix against the columns of another. So we are
going to do the following:
<figure>
$
M_{1}=\begin{bmatrix}
1 & 0 & tx \\
0 & 1 & ty \\
0 & 0 & 1 \\
\end{bmatrix}
\times
\begin{bmatrix}
\cos(\theta) & -\sin(\theta) & 0 \\
\sin(\theta) & \cos(\theta) & 0 \\
0 & 0 & 1 \\
\end{bmatrix}
$
</figure>
There isn't a built-in way to do this in Javascript, and this isn't the
fastest way to do it, but here is a, hopefully, straightforward example of
multiplying our two matrices together:
const [[m1, m2], me] = arguments; const ROW = 0; const COL = 1; // Make an array big enough to hold our result const out = new Array(m1.rc[ROW] * m2.rc[COL]); function mat_mul(m1, m2, out) { if (m1.rc[COL] != m2.rc[ROW]) { throw Error("column size of m1 must match row size of m2"); } const row = new Array(m1.rc[ROW]); const col = new Array(m2.rc[COL]); // Loop over each row of the first matrix for (let i = 0; i < m1.rc[ROW]; i++) { // Load a single Row for (let r = 0; r < m1.rc[COL]; r++) { row[r] = m1.d[r + i * m1.rc[COL]]; } // Loop over the columns to use when multiplying // against the row loaded above for (let j = 0; j < m2.rc[COL]; j++) { let v = 0; // Load a single column for (let c = 0; c < m2.rc[ROW]; c++) { col[c] = m2.d[j + c * m2.rc[COL]]; v += row[c] * col[c]; } out[j + i * m1.rc[ROW]] = v; } } } mat_mul(m1, m2, out); K.Print(me, out); return out;
Note: If you're into it, trying to figure out how to quickly multiply two
matrices together is a really fun project. I want to play with a quantum
computer :-D.
Ok now that we've got this matrix that will move and rotate something, what
do we do with it? Let's use it to move my profile picture up there on the
left (if you're on a desktop computer).
One problem with using a matrix in CSS is that the way CSS uses matrices
is... well... special.
Our created matrix looks like this:
<figure>
$
M_{1} =
\begin{bmatrix}
a & c & tx \\
b & d & ty \\
0 & 0 & 1 \\
\end{bmatrix}
$
</figure>
But CSS [defines the input](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix())
for it's matrix transform like this:
<figure>
$ CSS = \left[ \begin{array}{ccc} a & b & c & d & tx & ty \end{array} \right] $
</figure>
Which is neither row nor column order. I guess they decided to split the
difference and just make everyone slightly irritated.
So, lets make a quick little function that will make our awesomely cool
matrix into the version CSS wants, and apply it to that profile picture over
there:
const [out, me] = arguments; function mat_2d_to_css(a) { if(a.length < 6) { throw Error("Array must have at least 6 elements to be formatted"); } const out3 = [a[0], a[1], a[3], a[4], a[2], a[5]]; return `matrix(${out3.join(",")})`; } const transform = mat_2d_to_css(out); K.Print(me, transform); const b = document.querySelector("img"); b.style.transition = "all 3s ease-out"; b.style.transform = transform;
Don't forget you need to have run all the code fragments for the image to
move. Or, you can:
<form>
<input type="button" value="Run them all" onclick="kale_runAll()">
</form>
Hopefully that was interesting. Matrices are one of my favourite things to
play with. If you're interested in graphics programming or quantum
computing, I highly recommend you dig into them and play around.
Kale can also load 3rd party Javascript and run it. Here it is using d3 to
make a simple graph:
await K.Include("https://d3js.org/d3.v4.js"); const me = arguments[1]; const svg = K.GetD3Canvas(me); const x = d3.scaleLinear().domain([0, 100]).range([0, 400]); svg.call(d3.axisBottom(x)); svg .append("circle") .attr("cx", x(10)) .attr("cy", 100) .attr("r", 40) .style("fill", "blue"); svg .append("circle") .attr("cx", x(50)) .attr("cy", 100) .attr("r", 40) .style("fill", "purple"); svg .append("circle") .attr("cx", x(100)) .attr("cy", 100) .attr("r", 40) .style("fill", "green"); return x;