Published
Edited
Dec 4, 2021
Importers
2 stars
Insert cell
Insert cell
Insert cell
T = new AffineFunction([
[
[3, 4],
[1, -2]
],
[4, 3]
])
Insert cell
T.invert().compose(T)
Insert cell
Insert cell
Insert cell
T.f([1, 2])
Insert cell
Insert cell
T.compose(T)
Insert cell
Insert cell
T.norm
Insert cell
Insert cell
Insert cell
Insert cell
T.is_contractive
Insert cell
Insert cell
T.is_similarity
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
let pts = [
[0, 0],
[3, 0],
[3, 1],
[1, 1],
[1, 2],
[0, 2],
[0, 0]
];

let T = shift([5, 0])
.compose(scale(0.5))
.compose(reflect([1, 0]))
.compose(rotate(Math.PI / 3));

return Plot.plot({
x: { domain: [0, 6] },
y: { domain: [0, 2] },
width: 600,
height: 200,
marks: [
Plot.line(pts, { fill: "#eee", stroke: "black" }),
Plot.line(pts.map(T.f), { fill: "#ccc", stroke: "black" })
]
});
}
Insert cell
reflect([1, 0], [1, 0])
Insert cell
Insert cell
AffineFunction = {
let AffineFunction = class AffineFunction {
constructor(Ab) {
if (AffineFunction.is_affine_list(Ab)) {
this.f = _Ab_to_function(Ab);
this.affine_list = Ab;
this.linear_part = Ab[0];
this.shift = Ab[1];
this.is_similarity = _is_similarity(Ab);
this.norm = _norm(Ab);
this.is_contractive = this.norm < 1;
this.fixed_point = _fixed_point(Ab);
} else {
// if (Ab instanceof AffineFunction) {
return Ab;
}
}
};

// Generate the actual function that acts on points like [x,y].
function _Ab_to_function(Ab) {
let A = Ab[0];
let b = Ab[1] || [0, 0];
let f = function (xy) {
return [
A[0][0] * xy[0] + A[0][1] * xy[1] + b[0],
A[1][0] * xy[0] + A[1][1] * xy[1] + b[1]
];
};
return f;
}

function _fixed_point(Ab) {
let A = Ab[0];
let a = A[0][0];
let b = A[0][1];
let c = A[1][0];
let d = A[1][1];
let [e, f] = Ab[1];

if (a * d - b * c != 0) {
let denominator = 1 - a - b * c - d + a * d;
return [
(e - d * e + b * f) / denominator,
(c * e + f - a * f) / denominator
];
} else {
return null;
}
}

function _norm(Ab) {
let A = Ab[0];
let a = A[0][0];
let b = A[0][1];
let c = A[1][0];
let d = A[1][1];
return (
Math.sqrt(
Math.pow(a, 2) +
Math.pow(b, 2) +
Math.pow(c, 2) +
Math.pow(d, 2) +
Math.sqrt(
(Math.pow(b + c, 2) + Math.pow(a - d, 2)) *
(Math.pow(b - c, 2) + Math.pow(a + d, 2))
)
) / Math.sqrt(2)
);
}
function _is_similarity(Ab) {
let eps = 0.00000001;
let a, b, c, d;
[[a, b], [c, d]] = Ab[0];
return (
Math.abs(a * a + c * c - (b * b + d * d)) < eps &&
Math.abs(a * c + b * d) < eps
);
}

// Checks to make sure we've got good input
AffineFunction.is_numeric = function (x) {
return !isNaN(parseFloat(x)) && isFinite(x);
};
AffineFunction.is_vector = function (b) {
return (
Array.isArray(b) &&
b.length == 2 &&
AffineFunction.is_numeric(b[0]) &&
AffineFunction.is_numeric(b[1])
);
};
AffineFunction.is_matrix = function (A) {
return (
Array.isArray(A) &&
A.length == 2 &&
Array.isArray(A[0]) &&
A[0].length == 2 &&
Array.isArray(A[1]) &&
A[1].length == 2 &&
AffineFunction.is_numeric(A[0][0]) &&
AffineFunction.is_numeric(A[0][1]) &&
AffineFunction.is_numeric(A[1][0]) &&
AffineFunction.is_numeric(A[1][1])
);
};
AffineFunction.is_affine_list = function (Ab) {
return (
Array.isArray(Ab) &&
Ab.length == 2 &&
AffineFunction.is_matrix(Ab[0]) &&
AffineFunction.is_vector(Ab[1])
);
};

AffineFunction.prototype.compose = function (Ms) {
let M, s;
if (Ms instanceof AffineFunction) {
M = Ms.linear_part;
s = Ms.shift;
} else {
M = Ms[0];
s = Ms[1];
}
let m11, m12, m21, m22, s1, s2;
m11 = M[0][0];
m12 = M[0][1];
m21 = M[1][0];
m22 = M[1][1];
s1 = s[0];
s2 = s[1];
let A, a11, a12, a21, a22, b1, b2;
A = this.linear_part;
a11 = A[0][0];
a12 = A[0][1];
a21 = A[1][0];
a22 = A[1][1];
b1 = this.shift[0];
b2 = this.shift[1];

let af = new AffineFunction([
[
[a11 * m11 + a12 * m21, a11 * m12 + a12 * m22],
[a21 * m11 + a22 * m21, a21 * m12 + a22 * m22]
],
[b1 + a11 * s1 + a12 * s2, b2 + a21 * s1 + a22 * s2]
]);

return af;
};

AffineFunction.prototype.invert = function () {
let A = this.linear_part;
let a = A[0][0];
let b = A[0][1];
let c = A[1][0];
let d = A[1][1];
let [e, f] = this.shift;

let det = a * d - b * c;
if (det != 0) {
let B = [
[d / det, -b / det],
[-c / det, a / det]
];
let s = [(b * f - d * e) / det, (c * e - a * f) / det];
return new AffineFunction([B, s]);
}
};

AffineFunction.prototype.equal = function (af) {
let eps = 10 ** -3;
let A = this.linear_part;
let a1 = A[0][0];
let b1 = A[0][1];
let c1 = A[1][0];
let d1 = A[1][1];
let [e1, f1] = this.shift;
let B = af.linear_part;
let a2 = B[0][0];
let b2 = B[0][1];
let c2 = B[1][0];
let d2 = B[1][1];
let [e2, f2] = af.shift;

//return [a1 - a2, b1 - b2, c1 - c2, d1 - d2, e1 - e2, f1 - f2];

return (
Math.abs(a1 - a2) < eps &&
Math.abs(b1 - b2) < eps &&
Math.abs(c1 - c2) < eps &&
Math.abs(d1 - d2) < eps &&
Math.abs(e1 - e2) < eps &&
Math.abs(f1 - f2) < eps
);
};

AffineFunction.scale = function (r, x0y0) {
let x0, y0;
if (x0y0) {
x0 = x0y0[0];
y0 = x0y0[1];
} else {
x0 = 0;
y0 = 0;
}
let af = new AffineFunction([
[
[r, 0],
[0, r]
],
[x0 - r * x0, y0 - r * y0]
]);
return af;
};

AffineFunction.shift = function (direction) {
let af = new AffineFunction([
[
[1, 0],
[0, 1]
],
direction
]);
return af;
};

AffineFunction.rotate = function (theta, x0y0) {
let x0, y0;
if (x0y0) {
x0 = x0y0[0];
y0 = x0y0[1];
} else {
x0 = 0;
y0 = 0;
}
let A = [
[Math.cos(theta), -Math.sin(theta)],
[Math.sin(theta), Math.cos(theta)]
];
let b = [
x0 * (1 - Math.cos(theta)) + y0 * Math.sin(theta),
y0 * (1 - Math.cos(theta)) - x0 * Math.sin(theta)
];
let af = new AffineFunction([A, b]);
return af;
};

AffineFunction.reflect = function (direction, x0y0) {
let a = direction[0];
let b = direction[1];
let norm_squared = a * a + b * b;
let A = [
[1 - (2 * a * a) / norm_squared, (-2 * a * b) / norm_squared],
[(-2 * a * b) / norm_squared, 1 - (2 * b * b) / norm_squared]
];
let x0, y0;
if (x0y0) {
x0 = x0y0[0];
y0 = x0y0[1];
} else {
x0 = 0;
y0 = 0;
}
b = [
(2 * a * (a * x0 + b * y0)) / norm_squared,
(2 * b * (a * x0 + b * y0)) / norm_squared
];
let af = new AffineFunction([A, b]);
return af;
};

return AffineFunction;
}
Insert cell
Insert cell
scale = AffineFunction.scale
Insert cell
shift = AffineFunction.shift
Insert cell
rotate = AffineFunction.rotate
Insert cell
reflect = AffineFunction.reflect
Insert cell
pi = Math.PI
Insert cell
degree = Math.PI / 180
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more