Public
Edited
Nov 29, 2022
2 forks
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
canvas = {
const ctx = DOM.context2d(width, height);
ctx.fillStyle = "hsl(216deg 100% 13%)";
ctx.fillRect(0, 0, boxWidth * width, height);
ctx.canvas.style.background = "hsl(216deg 20% 90%)";

const xScale = d3.scaleLinear().domain([-1.2, 1.2]).range([0, width]),
yScale = d3
.scaleLinear()
.domain([-1, 1])
.range([(height / 5) * 4, height / 3]);

// Setup params
const srcParam = customParam,
dstParam = keepRandomParam ? randomParam : rndParam();

// Draw params
drawSrcDstParam();
const params = drawInterpolateParams();

// Draw distance matrix
{
const nw = [width - width / 10 - width / 20, width / 20],
w = width / 10,
h = width / 10;
drawDistanceMatrix(
ctx,
params,
nw,
w,
h,
"dist-matrix-div",
"dist-line-div"
);
}

// Draw param cycle (radius: sigma, sigma: phi)
{
const center = [(width / 20) * 5, (width / 20) * 2],
radius = width / 20;
drawParamCycle(ctx, params, center, radius);
}

// Draw Param rect (y: sigma, x: omega)
{
const nw = [width / 20, width / 20],
w = width / 10,
h = width / 10;
drawParamRect(ctx, params, nw, w, h);
}

// Draw source and target gabor
function drawSrcDstParam() {
// Draw Source gabor
{
ctx.strokeStyle = d3.color(colorMap(0)).hex() + opacityPostfix;
ctx.lineWidth = 3;
ctx.beginPath();
computeGabor(srcParam).map((pnt) => {
const { x, y } = pnt;
ctx.lineTo(xScale(x), yScale(y));
});
ctx.stroke();
}

// Draw Target gabor
{
ctx.strokeStyle = d3.color(colorMap(1)).hex() + opacityPostfix;
ctx.lineWidth = 3;
ctx.beginPath();
computeGabor(dstParam).map((pnt) => {
const { x, y } = pnt;
ctx.lineTo(xScale(x), yScale(y));
});
ctx.stroke();
}
}

// Interpolate params, draw them and return them
// Each param contains { param, color, gabor }
function drawInterpolateParams() {
const params = [];
for (let r = 0; r < 1; r += 1 / lines) {
const param = interpolateParam(srcParam, dstParam, r),
color = colorMap(r),
gabor = computeGabor(param);

params.push({ param, color, gabor });

ctx.strokeStyle = d3.color(color).hex() + opacityPostfix;
ctx.lineWidth = 3;
ctx.beginPath();
gabor.map((pnt) => {
const { x, y } = pnt;
ctx.lineTo(xScale(x), yScale(y));
});
ctx.stroke();
}
return params;
}

return ctx.canvas;
}
Insert cell
// Draw distance matrix,
// it also update "dist-matrix-div" and "dist-line-div"
function drawDistanceMatrix(ctx, params, nw, w, h, divMatrix, divLine) {
// NorthWest, Width, Height
// I will translate the canvas to the NorthWest point,
// So the painting inside the box focuses only the w and h

ctx.save();
ctx.translate(nw[0], nw[1]);

// Draw box
{
ctx.strokeStyle = "black";
ctx.lineWidth = 5;

ctx.beginPath(),
ctx.lineTo(0, 0),
ctx.lineTo(w, 0),
ctx.lineTo(w, h),
ctx.lineTo(0, h),
ctx.closePath(),
ctx.stroke();
}

const xScale = d3.scaleLinear().domain([0, 1]).range([0, w]),
yScale = d3.scaleLinear().domain([0, 1]).range([0, h]);

const n = params.length,
nr = 1 / params.length,
maxDst = computeDst(params[0].gabor, params[n - 1].gabor);

var g1, g2, dst, color, x, y;

// Draw axis
const radius = xScale(nr);
for (let i = 0; i < n; ++i) {
const { color } = params[i];
ctx.fillStyle = d3.color(color).hex() + opacityPostfix;

x = xScale(i * nr);
y = yScale(1.1 + nr);
ctx.beginPath(), ctx.arc(x, y, radius, 0, Math.PI * 2), ctx.fill();

y = yScale(i * nr);
x = xScale(-0.1 - nr);
ctx.beginPath(), ctx.arc(x, y, radius, 0, Math.PI * 2), ctx.fill();
}

// Draw matrix
const maybeMatrix = [];

for (let i = 0; i < n; ++i) {
for (let j = i; j < n; ++j) {
dst = computeDst(params[i].gabor, params[j].gabor);
maybeMatrix.push({ i, j, dst });
color = dstColorMap(dst / maxDst);
x = xScale(i * nr);
y = yScale(j * nr);
ctx.fillStyle = d3.color(color).hex();
ctx.beginPath(), ctx.rect(x, y, xScale(nr), yScale(nr)), ctx.fill();
}
}

// Update matrix div
{
const p = Plot.plot({
width: width / 4,
height: width / 4,
padding: 0.05,
grid: true,
x: {
axis: "top",
label: "Src"
},
y: {
label: "Target"
},
color: {
scheme: "PiYG",
legend: true
},
marks: [
Plot.cell(maybeMatrix, {
x: "i",
y: "j",
fill: "dst"
// rx: 20 // uncomment for circles
}),
Plot.text(maybeMatrix, {
x: "i",
y: "j",
text: (d) => (d) => d.dst?.toFixed(1),
title: "title"
})
]
});

document.getElementById(divMatrix).innerHTML = p.outerHTML;
}

// Update line div
{
const p = Plot.plot({
width: width / 4,
height: width / 4,
y: {
grid: true
},
x: {
reverse: true
},
marks: [
Plot.line(
maybeMatrix.filter((e) => e.i == 0),
{ x: "j", y: "dst" }
)
]
});
document.getElementById(divLine).innerHTML = p.outerHTML;
}

ctx.restore();
}
Insert cell
// Draw param cycle (radius: sigma, sigma: phi)
function drawParamCycle(ctx, params, center, radius) {
// Center, Radius
// I will translate the canvas to the Center point,
// So the painting inside the box focuses only the Radius

const { sigma: sigmaRange } = paramRange;
const sigmaRScale = d3
.scaleLinear()
.domain([0, sigmaRange[1]])
.range([0, radius]);

ctx.save();
ctx.translate(center[0], center[1]);

// Draw outline
{
ctx.strokeStyle = "black";
ctx.lineWidth = 5;
ctx.beginPath(), ctx.arc(0, 0, radius, 0, Math.PI * 2), ctx.stroke();

sigmaRange.map((sigma) => {
ctx.strokeStyle = "white";
ctx.lineWidth = 2;
ctx.beginPath(),
ctx.arc(0, 0, sigmaRScale(sigma), 0, Math.PI * 2),
ctx.stroke();
});
}

// Draw params
{
const radius = 3;
params.map(({ param, color }) => {
const { sigma, phi } = param;
ctx.fillStyle = d3.color(color).hex() + opacityPostfix;
ctx.beginPath(),
ctx.arc(
sigmaRScale(sigma) * Math.cos(phi),
sigmaRScale(sigma) * Math.sin(phi),
radius,
0,
Math.PI * 2
),
ctx.fill();
});
}

ctx.restore();
}
Insert cell
// Draw Param rect (y: sigma, x: omega)
function drawParamRect(ctx, params, nw, w, h) {
// NorthWest, Width, Height
// I will translate the canvas to the NorthWest point,
// So the painting inside the box focuses only the w and h

ctx.save();
ctx.translate(nw[0], nw[1]);

const { sigma: sigmaRange } = paramRange;
const sigmaScale = d3.scaleLinear().domain([0, 1]).range(sigmaRange),
sigmaYScale = d3.scaleLinear().domain(sigmaRange).range([h, 0]),
omegaXScale = d3
.scaleLinear()
.domain([0, omegaFactor / sigmaRange[0]])
.range([0, w]);

// Draw box
{
ctx.strokeStyle = "black";
ctx.lineWidth = 5;

ctx.beginPath(),
ctx.lineTo(0, 0),
ctx.lineTo(w, 0),
ctx.lineTo(w, h),
ctx.lineTo(0, h),
ctx.closePath(),
ctx.stroke();
}

// Draw margin, x: omega, y: sigma
{
ctx.strokeStyle = "white";
ctx.lineWidth = 2;

var sigma, omega;
ctx.beginPath();
ctx.lineTo(omegaXScale(0), sigmaYScale(sigmaScale(0)));
for (let i = 0; i <= 1; i += 0.1) {
sigma = sigmaScale(i);
omega = omegaFactor / sigma;
ctx.lineTo(omegaXScale(omega), sigmaYScale(sigma));
}
ctx.lineTo(omegaXScale(0), sigmaYScale(sigmaScale(1)));
ctx.closePath();
ctx.stroke();
}

// Draw params
{
const radius = 3;
params.map(({ param, color }) => {
const { omega, sigma } = param;
ctx.fillStyle = d3.color(color).hex() + opacityPostfix;
ctx.beginPath(),
ctx.arc(omegaXScale(omega), sigmaYScale(sigma), radius, 0, Math.PI * 2),
ctx.fill();
});
}

ctx.restore();
}
Insert cell
opacityPostfix = "90"
Insert cell
Insert cell
Insert cell
Insert cell
// Compute norm2 distance between a and b
// foo gets the required attribute
computeDst = (a, b, foo = (e) => e.y) => {
var sum = 0;
a.map((a, i) => {
sum += (foo(a) - foo(b[i])) * (foo(a) - foo(b[i]));
});
return sum;
}
Insert cell
interpolateParam(randomParam, customParam, 0.1)
Insert cell
interpolateParam = (p1, p2, r) => {
var scale = d3.scaleLinear().domain([0, 1]).range([0, 1]);

const n = p1.n;

scale.range([p1.sigma, p2.sigma]);
const sigma = scale(r);

scale.range([p1.omega, p2.omega]);
const omega = scale(r);

scale.range([p1.phi, p2.phi]);
const phi = scale(r);

return { n, sigma, omega, phi };
}
Insert cell
rndParam()
Insert cell
rndParam = () => {
const { sigma, phi } = paramRange;
const s = d3.randomUniform(sigma[0], sigma[1])();
const omega = [0, 5 / s];

return {
n,
sigma: s,
omega: d3.randomUniform(omega[0], omega[1])(),
phi: d3.randomUniform(phi[0], phi[1])()
};
}
Insert cell
computeGabor = (param) => {
const { n, sigma, phi } = param;
var omega1 = param.omega !== undefined ? param.omega : omega;
return gabor(n, sigma, omega1, phi);
}
Insert cell
gabor = (n, sigma, omega, phi, mu = 0) => {
const sigma_r = 1 / sigma;

const scale = d3.scaleLinear().domain([0, n]).range([-1, 1]);
const x = Array(n)
.fill(0)
.map((e, i) => scale(i));

var a, b, c;
const y = x.map((x) => {
a = (x - mu) * sigma_r;
b = Math.exp(-a * a);
c = Math.cos(omega * x + phi);
return { x, y: b * c };
});

return y;
}
Insert cell
paramRange = {
return {
n: [100, 500],
sigma: [0.2, 1],
phi: [0, 2 * Math.PI]
};
}
Insert cell
omegaFactor = 10
Insert cell
Insert cell
Insert cell
height = (width * 9) / 16
Insert cell
d3 = require("d3")
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