diff --git a/index.html b/index.html
index 6ed913e..da2794d 100644
--- a/index.html
+++ b/index.html
@@ -13,7 +13,6 @@
-
diff --git a/package.json b/package.json
index fd82b29..094d79f 100644
--- a/package.json
+++ b/package.json
@@ -1,23 +1,25 @@
{
- "name": "eigen-tour",
- "version": "0.0.0",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "preview": "vite preview"
- },
- "author": "Trevor Manz",
- "license": "MIT",
- "dependencies": {
- "@types/d3": "^7.4.0",
- "@types/numeric": "^1.2.2",
- "apache-arrow": "^9.0.0",
- "d3": "^7.6.1",
- "mathjs": "^11.0.1",
- "numeric": "^1.2.6"
- },
- "devDependencies": {
- "typescript": "^4.7.4",
- "vite": "^3.0.4"
- }
+ "name": "eigen-tour",
+ "version": "0.0.0",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "check": "tsc --noEmit",
+ "fmt": "deno fmt --ignore=dist,node_modules --options-use-tabs"
+ },
+ "author": "Trevor Manz",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3": "^7.4.0",
+ "@types/numeric": "^1.2.2",
+ "apache-arrow": "^9.0.0",
+ "d3": "^7.6.1",
+ "mathjs": "^11.0.1",
+ "numeric": "^1.2.6"
+ },
+ "devDependencies": {
+ "typescript": "^4.7.4",
+ "vite": "^3.0.4"
+ }
}
diff --git a/src/GrandTour.ts b/src/GrandTour.ts
index c3a9c88..bbe1c2b 100644
--- a/src/GrandTour.ts
+++ b/src/GrandTour.ts
@@ -1,137 +1,149 @@
-// @ts-check
import * as math from "mathjs";
import numeric from "numeric";
import * as utils from "./utils";
-export function GrandTour(ndim, init_matrix) {
- this.ndim = ndim;
- this.N = ndim*ndim;
-
- this.STEPSIZE = 0.02;
-
-
- this.angles;
-
- this.initThetas = function(N) {
- this.thetas = new Array(N);
- for (let i=0; i this.ndim){
- for(let i=this.N; irow.slice(0,newNdim));
- this.matrix = utils.orthogonalize(this.matrix);
- }
- this.ndim = newNdim;
- this.N = this.ndim * this.ndim;
- return this.matrix;
- };
-
- this.getMatrix = function(dt) {
- if (dt !== undefined) {
- if (this.angles === undefined) {
- // torus method
- // this.angles = this.thetas.map(theta=>0);
- //
- // another implementation similar to torus method
- this.angles = this.thetas;
- this.matrix = math.identity(this.ndim)._data;
- } else {
- // torus method
- // this.angles = this.angles.map(
- // (a,i) => a+dt*this.STEPSIZE*this.thetas[i]);
- //
- // another implementation similar to torus method
- this.angles = this.thetas.map( (theta) =>
- theta * dt * this.STEPSIZE );
- }
- // torus method
- // this.matrix = math.identity(this.ndim)._data;
- let k = -1;
- for (let i=0; id.slice());
- let columnI = matrix.map((d)=>d[i]);
- let columnJ = matrix.map((d)=>d[j]);
- for (let rowIndex=0; rowIndex (Math.random() + 0.5) * Math.PI);
+}
- this.setNdim(this.ndim);
- this.matrix = this.getMatrix(0);
- if(init_matrix !== undefined){
- this.setMatrix(init_matrix);
- }
+export class GrandTour {
+ STEPSIZE = 0.02;
+ matrix: number[][];
+
+ STEPSIZE_PREV?: number;
+ angles?: number[];
+ thetas: number[];
+ #ndim: number;
+
+ constructor(ndim: number, init_matrix?: number[][]) {
+ this.#ndim = ndim;
+ this.thetas = initThetas(this.N);
+ this.matrix = this.getMatrix(0);
+ if (init_matrix) {
+ this.setMatrix(init_matrix);
+ }
+ }
+
+ get N() {
+ return this.ndim * this.ndim;
+ }
+
+ get ndim() {
+ return this.#ndim;
+ }
+
+ set ndim(newNdim: number) {
+ if (newNdim > this.#ndim) {
+ for (let i = this.N; i < newNdim * newNdim; i++) {
+ this.thetas[i] = (Math.random() - 0.5) * 2 * Math.PI;
+ }
+ this.matrix = utils.embed(
+ this.matrix,
+ (math.identity(newNdim) as math.Matrix).toArray() as number[][],
+ );
+ } else if (newNdim < this.ndim) {
+ this.matrix = this.matrix.slice(0, newNdim).map((row) =>
+ row.slice(0, newNdim)
+ );
+ this.matrix = utils.orthogonalize(this.matrix);
+ }
+ this.#ndim = newNdim;
+ }
+
+ getMatrix(dt?: number) {
+ if (dt !== undefined) {
+ if (this.angles === undefined) {
+ // torus method
+ // this.angles = this.thetas.map(theta=>0);
+ //
+ // another implementation similar to torus method
+ this.angles = this.thetas;
+ let mat = math.identity(this.ndim) as math.Matrix;
+ this.matrix = mat.toArray() as number[][];
+ } else {
+ // torus method
+ // this.angles = this.angles.map(
+ // (a,i) => a+dt*this.STEPSIZE*this.thetas[i]);
+ //
+ // another implementation similar to torus method
+ this.angles = this.thetas.map((theta) => theta * dt * this.STEPSIZE);
+ }
+ // torus method
+ // this.matrix = math.identity(this.ndim)._data;
+ let k = -1;
+ for (let i = 0; i < this.ndim; i++) {
+ for (let j = 0; j < this.ndim; j++) {
+ if (i !== j && (true || i <= 3 || j <= 3)) {
+ k++;
+ this.matrix = this.multiplyRotationMatrix(
+ this.matrix,
+ i,
+ j,
+ this.angles[k],
+ );
+ }
+ }
+ }
+ }
+ return this.matrix;
+ }
+
+ setMatrix(m: number[][]) {
+ this.matrix = numeric.clone(m);
+ }
+
+ getRotationMatrix(dim0: number, dim1: number, theta: number) {
+ let m = math.identity(this.ndim) as math.Matrix;
+ let res = m.toArray() as number[][];
+ res[dim0][dim0] = Math.cos(theta);
+ res[dim0][dim1] = Math.sin(theta);
+ res[dim1][dim0] = -Math.sin(theta);
+ res[dim1][dim1] = Math.cos(theta);
+ return res;
+ }
+
+ multiplyRotationMatrix(
+ matrix: number[][],
+ i: number,
+ j: number,
+ theta: number,
+ ) {
+ if (theta == 0) {
+ return matrix;
+ }
+ let sin = Math.sin(theta);
+ let cos = Math.cos(theta);
+ // var res = matrix.map(d=>d.slice());
+ let columnI = matrix.map((d) => d[i]);
+ let columnJ = matrix.map((d) => d[j]);
+ for (let rowIndex = 0; rowIndex < matrix.length; rowIndex++) {
+ matrix[rowIndex][i] = columnI[rowIndex] * cos +
+ columnJ[rowIndex] * (-sin);
+ matrix[rowIndex][j] = columnI[rowIndex] * sin + columnJ[rowIndex] * cos;
+ }
+ return matrix;
+ }
+
+ get3dRotationMatrix(t: number) {
+ let theta = 0.0 * t;
+ let cos = Math.cos(theta);
+ let sin = Math.sin(theta);
+ return [
+ [cos, 0, sin] as const,
+ [0, 1, 0] as const,
+ [-sin, 0, cos] as const,
+ ] as const;
+ }
+
+ project(data: number[][], dt?: number, view?: number[][]) {
+ let matrix = this.getMatrix(dt);
+ matrix = math.transpose(matrix);
+ matrix = matrix.slice(0, 3);
+ matrix = math.transpose(matrix);
+ if (view !== undefined) {
+ matrix = math.multiply(view, matrix) as number[][];
+ }
+ return math.multiply(data, matrix.slice(0, data[0].length)) as number[][];
+ }
}
diff --git a/src/TeaserOverlay.ts b/src/TeaserOverlay.ts
index e972b57..00c656d 100644
--- a/src/TeaserOverlay.ts
+++ b/src/TeaserOverlay.ts
@@ -3,305 +3,244 @@ import * as d3 from "d3";
import * as math from "mathjs";
import * as utils from "./utils";
-export function TeaserOverlay(renderer, kwargs) {
- let canvas = renderer.gl.canvas;
- let width = canvas.clientWidth;
- let height = canvas.clientHeight;
- this.selectedClasses = new Set();
- this.renderer = renderer;
- Object.assign(this, kwargs);
-
- let that = this;
- let figure = d3.select("d-figure." + renderer.gl.canvas.id);
- this.figure = figure;
-
- this.getDataset = function () {
- return this.renderer.fixed_dataset || utils.getDataset();
+import type { TeaserRenderer } from "./TeaserRenderer";
+import { ColorRGB, Scale } from "./types";
+
+export interface TeaserOverlayOptions {}
+
+export class TeaserOverlay {
+ selectedClasses: Set;
+ figure: d3.Selection;
+ epochSlider: d3.Selection;
+ playButton: d3.Selection;
+ fullScreenButton: d3.Selection;
+ grandtourButton: d3.Selection;
+ svg: d3.Selection & {
+ sc?: (color: number) => string;
+ anchors?: d3.Selection<
+ SVGCircleElement,
+ [number, number],
+ SVGSVGElement,
+ unknown
+ >;
+ drag?: d3.DragBehavior;
};
+ epochIndicator: d3.Selection;
+
+ anchorRadius?: number;
+ annotate?: (renderer: TeaserRenderer) => void;
+
+ legendBox?: d3.Selection;
+ legendTitle?: d3.Selection;
+ legendTitleBg?: d3.Selection;
+ legendMark?: d3.Selection<
+ SVGCircleElement,
+ d3.RGBColor,
+ SVGSVGElement,
+ unknown
+ >;
+ legendText?: d3.Selection;
+
+ legend_sx?: Scale;
+ legend_sy?: Scale;
+
+ constructor(
+ public renderer: TeaserRenderer,
+ _opts: Partial = {},
+ ) {
+ this.selectedClasses = new Set();
+ this.renderer = renderer;
+
+ let figure = d3.select("d-figure." + renderer.gl.canvas.id);
+ this.figure = figure;
- this.epochSlider = figure
- .insert("input", ":first-child")
- .attr("type", "range")
- .attr("class", "slider epochSlider")
- .attr("min", renderer.epochs[0])
- .attr("max", renderer.epochs[renderer.epochs.length - 1])
- .attr("value", renderer.epochIndex)
- .on("input", function () {
- let value = d3.select(this).property("value");
- renderer.shouldAutoNextEpoch = false;
- renderer.setEpochIndex(parseInt(value));
- // renderer.render(0);
- that.playButton.attr("class", "tooltip play-button fa fa-play");
- that.playButton.select("span").text("Play training");
- });
-
- //special treatment when showing only one peoch
- if (renderer.epochs.length <= 1) {
- this.epochSlider.style("display", "none");
- }
+ let self = this;
+ this.epochSlider = figure
+ .insert("input", ":first-child")
+ .attr("type", "range")
+ .attr("class", "slider epochSlider")
+ .attr("min", renderer.epochs[0])
+ .attr("max", renderer.epochs[renderer.epochs.length - 1])
+ .attr("value", renderer.epochIndex)
+ .on("input", function () {
+ let value = d3.select(this).property("value");
+ renderer.shouldAutoNextEpoch = false;
+ renderer.setEpochIndex(parseInt(value));
+ // renderer.render(0);
+ self.playButton.attr("class", "tooltip play-button fa fa-play");
+ self.playButton.select("span").text("Play training");
+ });
- this.playButton = figure
- .insert("i", ":first-child")
- .attr(
- "class",
- "play-button tooltip fa " +
- (renderer.shouldAutoNextEpoch ? "fa-pause" : "fa-play"),
- )
- .on("mouseover", function () {
- d3.select(this).style("opacity", 1);
- })
- .on("mouseout", function () {
- d3.select(this).style("opacity", 0.7);
- })
- .on("click", function () {
- renderer.shouldAutoNextEpoch = !renderer.shouldAutoNextEpoch;
- if (renderer.shouldAutoNextEpoch) {
- d3.select(this).attr("class", "tooltip play-button fa fa-pause");
- d3.select(this).select("span")
- .text("Pause training");
- } else {
- d3.select(this).attr("class", "tooltip play-button fa fa-play");
- d3.select(this).select("span")
- .text("Play training");
- }
- });
- this.playButton.append("span")
- .attr("class", "tooltipText")
- .text("Pause training");
+ this.playButton = figure
+ .insert("i", ":first-child")
+ .attr(
+ "class",
+ "play-button tooltip fa " +
+ (renderer.shouldAutoNextEpoch ? "fa-pause" : "fa-play"),
+ )
+ .on("mouseover", function () {
+ d3.select(this).style("opacity", 1);
+ })
+ .on("mouseout", function () {
+ d3.select(this).style("opacity", 0.7);
+ })
+ .on("click", function () {
+ renderer.shouldAutoNextEpoch = !renderer.shouldAutoNextEpoch;
+ if (renderer.shouldAutoNextEpoch) {
+ d3.select(this).attr("class", "tooltip play-button fa fa-pause");
+ d3.select(this).select("span").text("Pause training");
+ } else {
+ d3.select(this).attr("class", "tooltip play-button fa fa-play");
+ d3.select(this).select("span").text("Play training");
+ }
+ });
- if (renderer.epochs.length <= 1) {
- this.playButton.style("display", "none");
- }
+ this.playButton.append("span")
+ .attr("class", "tooltipText")
+ .text("Pause training");
- this.fullScreenButton = figure
- .insert("i", ":first-child")
- .attr("class", "tooltip teaser-fullscreenButton fas fa-expand-arrows-alt")
- .on("mouseover", function () {
- d3.select(this).style("opacity", 0.7);
- })
- .on("mouseout", function () {
- if (renderer.isFullScreen) {
- d3.select(this).style("opacity", 0.7);
- } else {
- d3.select(this).style("opacity", 0.3);
- }
- })
- .on("click", function () {
- renderer.setFullScreen(!renderer.isFullScreen);
- // that.resize();
+ if (renderer.epochs.length <= 1) {
+ this.playButton.style("display", "none");
+ }
- if (renderer.isFullScreen) {
+ this.fullScreenButton = figure
+ .insert("i", ":first-child")
+ .attr("class", "tooltip teaser-fullscreenButton fas fa-expand-arrows-alt")
+ .on("mouseover", function () {
d3.select(this).style("opacity", 0.7);
- } else {
- d3.select(this).style("opacity", 0.3);
- }
- });
+ })
+ .on("mouseout", function () {
+ d3.select(this).style("opacity", renderer.isFullScreen ? 0.7 : 0.3);
+ })
+ .on("click", function () {
+ renderer.setFullScreen(!renderer.isFullScreen);
+ d3.select(this).style("opacity", renderer.isFullScreen ? 0.7 : 0.3);
+ });
- this.fullScreenButton.append("span")
- .attr("class", "tooltipTextBottom")
- .text("Toggle fullscreen");
-
- this.grandtourButton = figure
- .insert("i", ":first-child")
- .attr("class", "teaser-grandtourButton tooltip fas fa-globe-americas")
- .attr("width", 32)
- .attr("height", 32)
- .style("opacity", renderer.shouldPlayGrandTour ? 0.7 : 0.3)
- .on("mouseover", function () {
- d3.select(this).style("opacity", 0.7);
- })
- .on("mouseout", function () {
- if (renderer.shouldPlayGrandTour) {
+ this.fullScreenButton.append("span")
+ .attr("class", "tooltipTextBottom")
+ .text("Toggle fullscreen");
+
+ this.grandtourButton = figure
+ .insert("i", ":first-child")
+ .attr("class", "teaser-grandtourButton tooltip fas fa-globe-americas")
+ .attr("width", 32)
+ .attr("height", 32)
+ .style("opacity", renderer.shouldPlayGrandTour ? 0.7 : 0.3)
+ .on("mouseover", function () {
d3.select(this).style("opacity", 0.7);
- } else {
- d3.select(this).style("opacity", 0.3);
- }
- });
+ })
+ .on("mouseout", function () {
+ d3.select(this).style(
+ "opacity",
+ renderer.shouldPlayGrandTour ? 0.7 : 0.3,
+ );
+ });
- this.grandtourButton.append("span")
- .attr("class", "tooltipText")
- .text("Pause Grand Tour");
-
- this.grandtourButton
- .on("click", function () {
- renderer.shouldPlayGrandTour = !renderer.shouldPlayGrandTour;
- renderer.shouldCentralizeOrigin = renderer.shouldPlayGrandTour;
-
- renderer.isScaleInTransition = true;
- renderer.setScaleFactor(1.0);
- renderer.scaleTransitionProgress = renderer.shouldCentralizeOrigin
- ? Math.min(1, renderer.scaleTransitionProgress)
- : Math.max(0, renderer.scaleTransitionProgress);
-
- let dt = 0.03;
- renderer.scaleTransitionDelta = renderer.shouldCentralizeOrigin
- ? -dt
- : dt;
-
- if (renderer.shouldPlayGrandTour) {
- d3.select(this).select("span")
- .text("Pause Grand Tour");
- d3.select(this).style("opacity", 0.7);
- } else {
- d3.select(this).select("span")
- .text("Play Grand Tour");
- d3.select(this).style("opacity", 0.3);
- }
- });
+ this.grandtourButton.append("span")
+ .attr("class", "tooltipText")
+ .text("Pause Grand Tour");
+
+ this.grandtourButton
+ .on("click", function () {
+ renderer.shouldPlayGrandTour = !renderer.shouldPlayGrandTour;
+ renderer.shouldCentralizeOrigin = renderer.shouldPlayGrandTour;
+
+ renderer.isScaleInTransition = true;
+ renderer.setScaleFactor(1.0);
+ renderer.scaleTransitionProgress = renderer.shouldCentralizeOrigin
+ ? Math.min(1, renderer.scaleTransitionProgress)
+ : Math.max(0, renderer.scaleTransitionProgress);
+
+ let dt = 0.03;
+ renderer.scaleTransitionDelta = renderer.shouldCentralizeOrigin
+ ? -dt
+ : dt;
+
+ if (renderer.shouldPlayGrandTour) {
+ d3.select(this).select("span").text("Pause Grand Tour");
+ d3.select(this).style("opacity", 0.7);
+ } else {
+ d3.select(this).select("span").text("Play Grand Tour");
+ d3.select(this).style("opacity", 0.3);
+ }
+ });
- this.svg = figure
- .insert("svg", ":first-child")
- .attr("class", "overlay")
- .attr("width", width)
- .attr("height", height)
- .on("dblclick", function () {
- // renderer.shouldPlayGrandTour = !renderer.shouldPlayGrandTour;
- })
- .on("mousemove", () => {
- //handle unsuccessful onscreen event
- if (renderer.shouldRender == false) {
- renderer.shouldRender = true;
- if (renderer.animId === null) {
- renderer.play();
+ this.svg = figure
+ .insert("svg", ":first-child")
+ .attr("class", "overlay")
+ .attr("width", this.width)
+ .attr("height", this.height)
+ .on("dblclick", function () {
+ // renderer.shouldPlayGrandTour = !renderer.shouldPlayGrandTour;
+ })
+ .on("mousemove", () => {
+ //handle unsuccessful onscreen event
+ if (renderer.shouldRender == false) {
+ renderer.shouldRender = true;
+ if (renderer.animId === null) {
+ renderer.play();
+ }
}
- }
- });
+ });
- this.epochIndicator = this.svg.append("text")
- .attr("id", "epochIndicator")
- .attr("text-anchor", "middle")
- .text(`Epoch: ${renderer.epochIndex}/99`);
-
- this.controlOptionGroup = figure
- .insert("div", ":first-child");
-
- // this.datasetOption = this.controlOptionGroup
- // .insert('div', ':first-child')
- // .attr('class', 'form-group datasetOption');
- // this.datasetOption.append('label')
- // .text('Dataset: ');
- // this.datasetSelection = this.datasetOption.append('select')
- // .attr('class', 'datasetSelection')
- // .on('change', function() {
- // let dataset = d3.select(this).property('value');
- // utils.setDataset(dataset)
- // });
- // this.datasetSelection.selectAll('option')
- // .data([
- // {value:'mnist',text:'MNIST'},
- // {value:'fashion-mnist',text:'fashion-MNIST'},
- // {value:'cifar10',text:'CIFAR-10'}])
- // .enter()
- // .append('option')
- // .text(d=>d.text)
- // .attr('value', d=>d.value)
- // .property('selected', d=>{
- // //show default selection
- // return d.value == this.getDataset();
- // });
- //
- this.zoomSliderDiv = this.controlOptionGroup
- .insert("div", ":first-child")
- .attr("class", "form-group zoomSliderDiv");
- this.zoomLabel = this.zoomSliderDiv
- .append("label")
- .text("Zoom: ");
- this.zoomSlider = this.zoomLabel
- .append("input")
- .attr("type", "range")
- .attr("class", "slider zoomSlider")
- .attr("min", 0.5)
- .attr("max", 2.0)
- .attr("value", this.renderer.scaleFactor)
- .attr("step", 0.01)
- .on("input", function () {
- let value = +d3.select(this).property("value");
- renderer.setScaleFactor(value);
- });
+ this.epochIndicator = this.svg.append("text")
+ .attr("id", "epochIndicator")
+ .attr("text-anchor", "middle")
+ .text(`Epoch: ${renderer.epochIndex}/99`);
- this.modeOption = this.controlOptionGroup
- .insert("div", ":first-child")
- .attr("class", "form-group modeOption");
- this.modeLabel = this.modeOption.append("label")
- .text("Instances as: ");
- let select = this.modeLabel.append("select")
- .on("change", function () {
- let mode = d3.select(this).property("value");
- renderer.setMode(mode);
- that.updateArchorRadius(mode);
- });
- select.selectAll("option")
- .data(["point", "image"])
- .enter()
- .append("option")
- .text((d) => d)
- .attr("selected", (d) => {
- return (d == this.renderer.mode) ? "selected" : null;
- });
+ //special treatment when showing only one peoch
+ if (renderer.epochs.length <= 1) {
+ this.epochSlider.style("display", "none");
+ this.epochIndicator.style("display", "none");
+ }
+ }
- this.datasetOption = this.controlOptionGroup
- .insert("div", ":first-child")
- .attr("class", "form-group datasetOption");
- this.datasetLabel = this.datasetOption
- .append("label")
- .text("Dataset: ");
- this.datasetSelection = this.datasetLabel
- .append("select")
- .on("change", function () {
- let dataset = d3.select(this).property("value");
- utils.setDataset(dataset);
- });
- this.datasetSelection.selectAll("option")
- .data([
- { value: "mnist", text: "MNIST" },
- { value: "fashion-mnist", text: "fashion-MNIST" },
- { value: "cifar10", text: "CIFAR-10" },
- ])
- .enter()
- .append("option")
- .text((d) => d.text)
- .attr("value", (d) => d.value)
- .property("selected", (d) => {
- return d.value == this.getDataset();
- });
+ get canvas() {
+ return this.renderer.gl.canvas;
+ }
+
+ get width() {
+ return this.canvas.clientWidth;
+ }
+
+ get height() {
+ return this.canvas.clientHeight;
+ }
+
+ getDataset() {
+ return utils.getDataset() as keyof typeof utils.legendTitle;
+ }
- this.banner = figure.selectAll(".banner")
- .data([0])
- .enter()
- .append("div")
- .attr("class", "banner");
- this.banner = figure.selectAll(".banner");
- this.bannerText = this.banner
- .selectAll(".bannerText")
- .data([0])
- .enter()
- .append("p")
- .attr("class", "bannerText");
- this.bannerText = this.banner.selectAll(".bannerText");
-
-
- this.updateArchorRadius = function (mode) {
+ updateArchorRadius(mode: string) {
if (mode == "point") {
- this.archorRadius = utils.clamp(7, 10, Math.min(width, height) / 50);
+ this.anchorRadius = utils.clamp(
+ 7,
+ 10,
+ Math.min(this.width, this.height) / 50,
+ );
} else {
- this.archorRadius = utils.clamp(7, 15, Math.min(width, height) / 30);
+ this.anchorRadius = utils.clamp(
+ 7,
+ 15,
+ Math.min(this.width, this.height) / 30,
+ );
}
this.svg.selectAll(".anchor")
- .attr("r", this.archorRadius);
- };
+ .attr("r", this.anchorRadius);
+ }
- this.resize = function () {
- width = canvas.clientWidth;
- height = canvas.clientHeight;
+ resize() {
+ let width = this.renderer.gl.canvas.clientWidth;
+ let height = this.renderer.gl.canvas.clientHeight;
this.svg.attr("width", width);
this.svg.attr("height", height);
-
this.initLegendScale();
- this.updateArchorRadius(renderer.mode);
+ this.updateArchorRadius(this.renderer.mode);
this.repositionAll();
- };
+ }
- this.repositionAll = function () {
+ repositionAll() {
let width = +this.svg.attr("width");
let height = +this.svg.attr("height");
@@ -313,24 +252,27 @@ export function TeaserOverlay(renderer, kwargs) {
.attr("x", sliderMiddle)
.attr("y", height - 35);
- if (renderer.epochs.length <= 1) {
+ if (this.renderer.epochs.length <= 1) {
this.epochIndicator
.attr("x", width / 2 - 10)
.attr("y", height - 20);
}
+ if (!(this.legend_sx && this.legend_sy)) return;
+
let r = (this.legend_sy(1) - this.legend_sy(0)) / 4;
+
this.legendMark
- .attr("cx", this.legend_sx(0.0) + 2.5 * r)
- .attr("cy", (c, i) => this.legend_sy(i + 0.5))
+ ?.attr("cx", this.legend_sx(0.001) + 2.5 * r)
+ .attr("cy", (_, i) => this.legend_sy!(i + 0.5))
.attr("r", r);
this.legendText
- .attr("x", +this.legend_sx(0.0) + 2.5 * r + 2.5 * r)
- .attr("y", (l, i) => this.legend_sy(i + 0.5));
+ ?.attr("x", +this.legend_sx(0.0) + 2.5 * r + 2.5 * r)
+ .attr("y", (_, i) => this.legend_sy!(i + 0.5));
this.legendBox
- .attr("x", this.legend_sx.range()[0])
+ ?.attr("x", this.legend_sx.range()[0])
.attr("y", this.legend_sy(-1))
.attr("width", this.legend_sx.range()[1] - this.legend_sx.range()[0])
.attr(
@@ -345,78 +287,70 @@ export function TeaserOverlay(renderer, kwargs) {
.attr("y", this.legend_sy(-1))
.text(utils.legendTitle[this.getDataset()] || "");
- let rectData = this.legendTitle.node().getBBox();
+ let rectData = this.legendTitle.node()!.getBBox();
let padding = 2;
- this.legendTitleBg
+ this.legendTitleBg!
.attr("x", rectData.x - padding)
.attr("y", rectData.y - padding)
.attr("width", rectData.width + 2 * padding)
.attr("height", rectData.height + 2 * padding)
.attr("opacity", utils.legendTitle[this.getDataset()] ? 1 : 0);
}
- if (this.banner) {
- this.banner.remove();
- }
- };
+ }
- this.init = function () {
+ init() {
let labels = utils.getLabelNames(false, this.getDataset());
- this.initLegend(
- utils.baseColors.slice(0, labels.length),
- labels,
- );
+ let colors = utils.baseColors.slice(0, labels.length);
+ this.initLegend(colors, labels);
this.resize();
this.initAxisHandle();
if (this.annotate !== undefined) {
this.annotate(this.renderer);
}
- // if(this.banner){
- // this.banner.remove();
- // }
- };
+ }
- this.initAxisHandle = function () {
+ initAxisHandle() {
this.svg.sc = d3.interpolateGreys;
this.drawAxes();
- };
+ }
- this.drawAxes = function () {
+ drawAxes() {
let svg = this.svg;
- let ndim = renderer.dataObj.ndim || 10;
- let coordinates = math.zeros(ndim, ndim)._data;
+ let ndim = this.renderer.dataObj?.ndim || 10;
+ let renderer = this.renderer;
+
+ let mat = math.zeros(ndim, ndim);
+ let coordinates = (mat as unknown as { _data: [number, number][] })._data;
- svg.selectAll(".anchor")
+ let anchors = svg.selectAll(".anchor")
.data(coordinates)
.enter()
.append("circle")
.attr("class", "anchor")
.attr("opacity", 0.2);
- let anchors = svg.selectAll(".anchor")
- .attr("cx", (d) => renderer.sx(d[0]))
- .attr("cy", (d) => renderer.sy(d[1]))
- .attr("r", this.archorRadius)
- .attr("fill", (_, i) => d3.rgb(...utils.baseColors[i]).darker())
+ anchors
+ .attr("cx", ([x, _]) => this.renderer.sx(x))
+ .attr("cy", ([_, y]) => this.renderer.sy(y))
+ .attr("r", this.anchorRadius!)
.attr("stroke", () => "white")
.style("cursor", "pointer");
svg.anchors = anchors;
let self = this;
- svg.drag = d3.drag()
+ let drag = d3.drag()
.on("start", () => {
renderer.shouldPlayGrandTourPrev = renderer.shouldPlayGrandTour;
renderer.shouldPlayGrandTour = false;
renderer.isDragging = true;
})
.on("drag", function (event) {
+ if (!renderer.gt) return;
let dx = renderer.sx.invert(event.dx) - renderer.sx.invert(0);
let dy = renderer.sy.invert(event.dy) - renderer.sy.invert(0);
let matrix = renderer.gt.getMatrix();
-
- const e = svg.anchors.nodes();
- const i = e.indexOf(this);
-
+ let i = anchors.nodes().indexOf(this);
matrix[i][0] += dx;
matrix[i][1] += dy;
matrix = utils.orthogonalize(matrix, i);
@@ -425,45 +359,36 @@ export function TeaserOverlay(renderer, kwargs) {
})
.on("end", function () {
renderer.isDragging = false;
- renderer.shouldPlayGrandTour = renderer.shouldPlayGrandTourPrev;
- renderer.shouldPlayGrandTourPrev = null;
+ renderer.shouldPlayGrandTour = renderer.shouldPlayGrandTourPrev ??
+ false;
+ renderer.shouldPlayGrandTourPrev = undefined;
});
anchors
.on("mouseover", () => {
+ if (!renderer.gt) return;
renderer.gt.STEPSIZE_PREV = renderer.gt.STEPSIZE;
renderer.gt.STEPSIZE = renderer.gt.STEPSIZE * 0.2;
})
.on("mouseout", () => {
+ if (renderer.gt?.STEPSIZE_PREV === undefined) return;
renderer.gt.STEPSIZE = renderer.gt.STEPSIZE_PREV;
delete renderer.gt.STEPSIZE_PREV;
})
- .call(svg.drag);
- };
-
- this.redrawAxis = function () {
- let svg = this.svg;
-
- if (renderer.gt !== undefined) {
- let handlePos = renderer.gt.project(
- math.identity(renderer.dataObj.ndim)._data,
- );
-
- svg.selectAll(".anchor")
- .attr("cx", (_, i) => renderer.sx(handlePos[i][0]))
- .attr("cy", (_, i) => renderer.sy(handlePos[i][1]));
- }
-
- // svg.anchors.filter((_,j)=>renderer.gt.fixedAxes[j].isFixed)
- // .attr('fill', 'red')
- // .attr('opacity', 0.5);
+ .call(drag);
+ }
- // svg.anchors.filter((_,j)=>!renderer.gt.fixedAxes[j].isFixed)
- // .attr('fill', 'black')
- // .attr('opacity', 0.1);
- };
+ redrawAxis() {
+ if (this.renderer.gt === undefined) return;
+ let m = math.identity(this.renderer.dataObj?.ndim ?? 10);
+ let points = (m as unknown as { _data: number[][] })._data;
+ let handlePos = this.renderer.gt.project(points);
+ this.svg.selectAll(".anchor")
+ .attr("cx", (_, i) => this.renderer.sx(handlePos[i][0]))
+ .attr("cy", (_, i) => this.renderer.sy(handlePos[i][1]));
+ }
- this.initLegendScale = function () {
+ initLegendScale() {
let width = +this.svg.attr("width");
let marginTop = 20;
let padding = 8;
@@ -487,35 +412,40 @@ export function TeaserOverlay(renderer, kwargs) {
marginTop + 170,
marginTop + 170 + padding,
]);
- };
+ }
- this.initLegend = function (colors, labels) {
+ initLegend(colors: ColorRGB[], labels: string[]) {
this.initLegendScale();
+ let clearColor = d3.rgb(
+ ...utils.CLEAR_COLOR.map((d) => d * 255) as ColorRGB,
+ );
+
if (this.legendBox === undefined) {
this.legendBox = this.svg.selectAll(".legendBox")
.data([0])
.enter()
.append("rect")
.attr("class", "legendBox")
- .attr("fill", d3.rgb(...utils.CLEAR_COLOR.map((d) => d * 255)))
+ .attr("fill", clearColor.formatRgb())
.attr("stroke", "#c1c1c1")
.attr("stroke-width", 1);
}
+ let legendTitleText =
+ utils.legendTitle[this.getDataset() as keyof typeof utils.legendTitle];
if (
- this.legendTitle === undefined &&
- utils.legendTitle[this.getDataset()] !== undefined
+ this.legendTitle === undefined && legendTitleText !== undefined
) {
this.legendTitleBg = this.svg.selectAll(".legendTitleBg")
.data([0])
.enter()
.append("rect")
.attr("class", "legendTitleBg")
- .attr("fill", d3.rgb(...utils.CLEAR_COLOR.map((d) => d * 255)));
+ .attr("fill", clearColor.formatRgb());
this.legendTitle = this.svg.selectAll(".legendTitle")
- .data([utils.legendTitle[this.getDataset()]])
+ .data([legendTitleText])
.enter()
.append("text")
.attr("class", "legendTitle")
@@ -524,110 +454,121 @@ export function TeaserOverlay(renderer, kwargs) {
.text((d) => d);
}
- this.svg.selectAll(".legendMark")
- .data(colors)
+ let self = this;
+
+ this.legendMark = this.svg.selectAll(".legendMark")
+ .data(colors.map((c) => d3.rgb(...c)))
.enter()
.append("circle")
- .attr("class", "legendMark")
- .attr("fill", (c, i) => "rgb(" + c + ")")
- .on("mouseover", (_, i) => {
- let classes = new Set(this.selectedClasses);
+ .attr("class", "legendMark");
+
+ this.legendMark
+ .attr("fill", (color) => color.formatRgb())
+ .on("mouseover", function () {
+ const e = self.legendMark!.nodes();
+ const i = e.indexOf(this);
+
+ let classes = new Set(self.selectedClasses);
if (!classes.has(i)) {
classes.add(i);
}
- this.onSelectLegend(classes);
+ self.onSelectLegend(classes);
})
.on("mouseout", () => this.restoreAlpha())
- .on("click", (_, i) => {
- if (this.selectedClasses.has(i)) {
- this.selectedClasses.delete(i);
+ .on("click", function () {
+ const e = self.legendMark!.nodes();
+ const i = e.indexOf(this);
+
+ if (self.selectedClasses.has(i)) {
+ self.selectedClasses.delete(i);
} else {
- this.selectedClasses.add(i);
+ self.selectedClasses.add(i);
}
- this.onSelectLegend(this.selectedClasses);
- if (this.selectedClasses.size == renderer.dataObj.ndim) {
- this.selectedClasses = new Set();
+ self.onSelectLegend(self.selectedClasses);
+ if (self.selectedClasses.size == self.renderer.dataObj?.ndim) {
+ self.selectedClasses = new Set();
}
});
- this.legendMark = this.svg.selectAll(".legendMark");
- this.svg.selectAll(".legendText")
+
+ this.legendText = this.svg.selectAll(".legendText")
.data(labels)
.enter()
.append("text")
.attr("class", "legendText");
- this.legendText = this.svg.selectAll(".legendText")
+ this.legendText
.attr("alignment-baseline", "middle")
.attr("fill", "#333")
- .text((l) => l)
- .on("mouseover", (_, i) => {
- let classes = new Set(this.selectedClasses);
+ .text((label) => label)
+ .on("mouseover", function () {
+ const e = self.legendText!.nodes();
+ const i = e.indexOf(this);
+ let classes = new Set(self.selectedClasses);
if (!classes.has(i)) {
classes.add(i);
}
- this.onSelectLegend(classes);
+ self.onSelectLegend(classes);
})
.on("mouseout", () => this.restoreAlpha())
- .on("click", (_, i) => {
- if (this.selectedClasses.has(i)) {
- this.selectedClasses.delete(i);
+ .on("click", function () {
+ const e = self.legendText!.nodes();
+ const i = e.indexOf(this);
+
+ if (self.selectedClasses.has(i)) {
+ self.selectedClasses.delete(i);
} else {
- this.selectedClasses.add(i);
+ self.selectedClasses.add(i);
}
- this.onSelectLegend(this.selectedClasses);
- if (this.selectedClasses.size == renderer.dataObj.ndim) {
- this.selectedClasses = new Set();
+ self.onSelectLegend(self.selectedClasses);
+
+ if (self.selectedClasses.size == self.renderer.dataObj?.ndim) {
+ self.selectedClasses = new Set();
}
});
- };
+ }
+
+ onSelectLegend(labelClasses: number | number[] | Set) {
+ if (!this.renderer.dataObj) return;
- this.onSelectLegend = function (labelClasses) {
- if (typeof (labelClasses) === "number") {
+ if (typeof labelClasses === "number") {
labelClasses = [labelClasses];
}
- labelClasses = new Set(labelClasses);
+ let labelSet = new Set(labelClasses);
- for (let i = 0; i < renderer.dataObj.npoint; i++) {
- if (labelClasses.has(renderer.dataObj.labels[i])) {
- renderer.dataObj.alphas[i] = 255;
+ for (let i = 0; i < this.renderer.dataObj.npoint; i++) {
+ if (labelSet.has(this.renderer.dataObj.labels[i])) {
+ this.renderer.dataObj.alphas[i] = 255;
} else {
- renderer.dataObj.alphas[i] = 0;
+ this.renderer.dataObj.alphas[i] = 0;
}
}
- this.svg.selectAll(".legendMark")
- .attr("opacity", (d, j) => {
- if (!labelClasses.has(j)) {
- return 0.1;
- } else {
- return 1.0;
- }
- });
- // renderer.render(0);
- };
- this.restoreAlpha = function () {
+ this.legendMark?.attr("opacity", (_, j) => labelSet.has(j) ? 1.0 : 0.1);
+ }
+
+ restoreAlpha() {
+ if (!this.renderer.dataObj) return;
let labelClasses = new Set(this.selectedClasses);
if (labelClasses.size == 0) {
- for (let i = 0; i < renderer.dataObj.npoint; i++) {
- renderer.dataObj.alphas[i] = 255;
+ for (let i = 0; i < this.renderer.dataObj.npoint; i++) {
+ this.renderer.dataObj.alphas[i] = 255;
}
} else {
- for (let i = 0; i < renderer.dataObj.npoint; i++) {
- if (labelClasses.has(renderer.dataObj.labels[i])) {
- renderer.dataObj.alphas[i] = 255;
+ for (let i = 0; i < this.renderer.dataObj.npoint; i++) {
+ if (labelClasses.has(this.renderer.dataObj.labels[i])) {
+ this.renderer.dataObj.alphas[i] = 255;
} else {
- renderer.dataObj.alphas[i] = 0;
+ this.renderer.dataObj.alphas[i] = 0;
}
}
}
- this.svg.selectAll(".legendMark")
- .attr("opacity", (d, i) => {
- if (labelClasses.size == 0) {
- return 1.0;
- } else {
- return labelClasses.has(i) ? 1.0 : 0.1;
- }
- });
- };
+ this.legendMark?.attr("opacity", (_, i) => {
+ if (labelClasses.size == 0) {
+ return 1.0;
+ } else {
+ return labelClasses.has(i) ? 1.0 : 0.1;
+ }
+ });
+ }
}
diff --git a/src/TeaserRenderer.ts b/src/TeaserRenderer.ts
index e5cfa41..55c29ba 100644
--- a/src/TeaserRenderer.ts
+++ b/src/TeaserRenderer.ts
@@ -1,111 +1,153 @@
-// @ts-check
import * as arrow from "apache-arrow";
import * as d3 from "d3";
import * as math from "mathjs";
import * as utils from "./utils";
import { GrandTour } from "./GrandTour";
-import { TeaserOverlay } from "./TeaserOverlay";
+import { TeaserOverlay, TeaserOverlayOptions } from "./TeaserOverlay";
+
+import type { ColorRGBA, Renderer } from "./types";
+
+interface Data {
+ labels: number[];
+ dataTensor: number[][][];
+ dmax: number;
+ ndim: number;
+ npoint: number;
+ nepoch: number;
+ alphas: number[];
+ points?: number[][];
+ colors?: ColorRGBA[];
+}
interface TeaserRendererOptions {
- epochs: number;
- shouldAutoNextEpoch: boolean;
+ epochs: number[];
+ epochIndex: number;
+ shouldAutoNextEpoch: boolean;
+ shouldPlayGrandTour: boolean;
+ isFullScreen: boolean;
+ overlayKwargs: TeaserOverlayOptions;
+ pointSize: number;
}
-export function TeaserRenderer(gl: WebGLRenderingContext, program: WebGLProgram, kwargs: TeaserRendererOptions) {
- this.gl = gl;
- this.program = program;
- this.id = gl.canvas.id;
-
- this.framesPerTransition = 30;
- this.framesPerEpoch = 60;
- this.scaleTransitionProgress = 0;
-
- Object.assign(this, kwargs);
-
- this.dataObj = {};
- this.mode = this.mode || "point"; // default point mode, or overwritten by kwargs
- this.epochIndex = this.epochIndex || this.epochs[0];
- this.colorFactor = utils.COLOR_FACTOR;
- this.isFullScreen = false;
- if (this.shouldPlayGrandTour === undefined) {
- this.shouldPlayGrandTour = true;
+export class TeaserRenderer implements Renderer {
+ framesPerTransition = 30;
+ framesPerEpoch = 60;
+ scaleTransitionProgress = 0;
+ scaleTransitionDelta = 0;
+ colorFactor = utils.COLOR_FACTOR;
+ isFullScreen = false;
+ isDataReady = false;
+ shouldRender = true;
+ scaleFactor = 1.0;
+ s = 1.0;
+ mode: "point" = "point";
+
+ id: string;
+ epochs: number[];
+ epochIndex: number;
+ shouldAutoNextEpoch: boolean;
+ shouldPlayGrandTour: boolean;
+ pointSize: number;
+ pointSize0: number;
+ overlay: TeaserOverlay;
+ sx_span: d3.ScaleLinear;
+ sy_span: d3.ScaleLinear;
+ sz_span: d3.ScaleLinear;
+ sx_center: d3.ScaleLinear;
+ sy_center: d3.ScaleLinear;
+ sz_center: d3.ScaleLinear;
+ sx: d3.ScaleLinear;
+ sy: d3.ScaleLinear;
+ sz?: d3.ScaleLinear;
+
+ dataObj?: Data;
+ shouldRecalculateColorRect?: boolean;
+ isPlaying?: boolean;
+ animId?: number;
+ isScaleInTransition?: boolean;
+ isDragging?: boolean;
+ shouldPlayGrandTourPrev?: boolean;
+
+ colorBuffer?: WebGLBuffer;
+ colorLoc?: number;
+ positionBuffer?: WebGLBuffer;
+ positionLoc?: number;
+ pointSizeLoc?: WebGLUniformLocation;
+ isDrawingAxisLoc?: WebGLUniformLocation;
+ canvasWidthLoc?: WebGLUniformLocation;
+ canvasHeightLoc?: WebGLUniformLocation;
+ modeLoc?: WebGLUniformLocation;
+ colorFactorLoc?: WebGLUniformLocation;
+ gt?: GrandTour;
+ shouldCentralizeOrigin?: boolean;
+
+ constructor(
+ public gl: WebGLRenderingContext,
+ public program: WebGLProgram,
+ opts: Partial = {},
+ ) {
+ this.id = gl.canvas.id;
+ this.epochs = opts.epochs ?? [0];
+ this.epochIndex = opts.epochIndex ?? this.epochs[0];
+ this.shouldAutoNextEpoch = opts.shouldAutoNextEpoch ?? true;
+ this.shouldPlayGrandTour = opts.shouldPlayGrandTour ?? true;
+ this.pointSize = opts.pointSize ?? 6.0;
+ this.pointSize0 = this.pointSize;
+ this.overlay = new TeaserOverlay(this, opts.overlayKwargs);
+
+ this.sx_span = d3.scaleLinear();
+ this.sy_span = d3.scaleLinear();
+ this.sz_span = d3.scaleLinear();
+ this.sx_center = d3.scaleLinear();
+ this.sy_center = d3.scaleLinear();
+ this.sz_center = d3.scaleLinear();
+ this.sx = this.sx_center;
+ this.sy = this.sy_center;
}
- if (!this.hasOwnProperty("shouldAutoNextEpoch")) {
- this.shouldAutoNextEpoch = true;
- }
- this.pointSize0 = this.pointSize || 6.0;
-
- this.overlay = new TeaserOverlay(this, this.overlayKwargs);
- this.sx_span = d3.scaleLinear();
- this.sy_span = d3.scaleLinear();
- this.sz_span = d3.scaleLinear();
- this.sx_center = d3.scaleLinear();
- this.sy_center = d3.scaleLinear();
- this.sz_center = d3.scaleLinear();
- this.sx = this.sx_center;
- this.sy = this.sy_center;
- this.scaleFactor = 1.0;
-
- this.setScaleFactor = function (s) {
+ setScaleFactor(s: number) {
this.scaleFactor = s;
- };
+ }
- this.initData = async function (buffer, url) {
+ async initData(buffer: ArrayBuffer) {
let table = arrow.tableFromIPC(buffer);
-
let ndim = 5;
let nepoch = 1;
- let dsample = 10;
let labels = [];
let arr = [];
- let fields = d3.range(ndim).map((i) => "E" + i);
- let mapping = Object.fromEntries(
+ let fields = d3.range(ndim).map((i) => "E" + i);
+ let labelMapping = Object.fromEntries(
["A0", "A1", "B0", "B1", "B2"].map((name, i) => [name, i]),
);
- for (let row of utils.iterN(table, dsample)) {
- labels.push(mapping[row["name"]]);
- for (let field of fields) {
- arr.push(row[field]);
- }
+ for (let row of utils.iterN(table, 10)) {
+ labels.push(labelMapping[row.name]);
+ for (let field of fields) arr.push(row[field]);
}
let npoint = labels.length;
+ let shape: [number, number, number] = [nepoch, npoint, ndim];
+ let dataTensor = utils.reshape(new Float32Array(arr), shape);
- arr = new Float32Array(arr);
-
- this.dataObj.labels = labels;
this.shouldRecalculateColorRect = true;
- this.dataObj.dataTensor = utils.reshape(arr, [nepoch, npoint, ndim]);
+ this.isDataReady = true;
- if (
- this.dataObj.dataTensor !== undefined &&
- this.dataObj.labels !== undefined
- ) {
- this.isDataReady = true;
- let { dataTensor } = this.dataObj;
- // this.dataObj.trajectoryLength = 5;
- this.dataObj.dmax = 1.05 * math.max(
+ this.dataObj = {
+ labels,
+ dataTensor,
+ dmax: 1.05 * math.max(
math.abs(dataTensor[dataTensor.length - 1]),
- );
- this.dataObj.ndim = dataTensor[0][0].length;
- this.dataObj.npoint = dataTensor[0].length;
- this.dataObj.nepoch = dataTensor.length;
- if (this.dataObj.alphas === undefined) {
- this.dataObj.alphas = d3.range(
- this.dataObj.npoint + 5 * this.dataObj.npoint,
- ).map((_) => 255);
- } else {
- this.overlay.restoreAlpha();
- }
- this.initGL(this.dataObj);
- } else {
- this.isDataReady = false;
- }
+ ),
+ ndim,
+ npoint,
+ nepoch,
+ alphas: d3.range(npoint + 5 * npoint).map(() => 255),
+ };
+
+ this.initGL(this.dataObj);
if (this.isDataReady && this.isPlaying === undefined) {
// renderer.isPlaying===undefined indicates the renderer on init
@@ -117,135 +159,81 @@ export function TeaserRenderer(gl: WebGLRenderingContext, program: WebGLProgram,
if (
this.isDataReady &&
- (this.animId == null ||
- this.shouldRender == false)
+ (this.animId == null || this.shouldRender == false)
) {
this.shouldRender = true;
// this.shouldRecalculateColorRect = true;
this.play();
}
- };
+ }
- this.setFullScreen = function (shouldSet) {
+ setFullScreen(shouldSet: boolean) {
this.isFullScreen = shouldSet;
let canvas = this.gl.canvas;
let canvasSelection = d3.select("#" + canvas.id);
- let topBarHeight = 0 || d3.select("nav").node().clientHeight;
-
- d3.select(canvas.parentNode)
+ d3.select(canvas.parentNode as HTMLElement)
.classed("fullscreen", shouldSet);
- if (shouldSet) {
- canvasSelection
- .classed("fullscreen", true);
- } else {
- canvasSelection
- .classed("fullscreen", false);
- }
+ canvasSelection.classed("fullscreen", shouldSet);
utils.resizeCanvas(canvas);
this.overlay.resize();
- gl.uniform1f(this.canvasWidthLoc, canvas.clientWidth);
- gl.uniform1f(this.canvasHeightLoc, canvas.clientHeight);
- gl.viewport(0, 0, canvas.width, canvas.height);
- };
-
- this.setMode = function (mode = "point") {
- this.mode = mode;
- if (mode === "point") {
- gl.uniform1i(this.modeLoc, 0);
- } else if (mode === "image") {
- gl.uniform1i(this.modeLoc, 1);
- }
- };
+ this.gl.uniform1f(this.canvasWidthLoc!, canvas.clientWidth);
+ this.gl.uniform1f(this.canvasHeightLoc!, canvas.clientHeight);
+ this.gl.viewport(0, 0, canvas.width, canvas.height);
+ }
- this.initGL = function (dataObj) {
- let gl = this.gl;
+ initGL(dataObj: Data) {
let program = this.program;
- // init
- utils.resizeCanvas(gl.canvas);
+ utils.resizeCanvas(this.gl.canvas);
- gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
- gl.clearColor(...utils.CLEAR_COLOR, 1.0);
+ this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
+ this.gl.clearColor(...utils.CLEAR_COLOR, 1.0);
// gl.enable(gl.DEPTH_TEST);
- gl.enable(gl.BLEND);
- gl.disable(gl.DEPTH_TEST);
- gl.blendFuncSeparate(
- gl.SRC_ALPHA,
- gl.ONE_MINUS_SRC_ALPHA,
- gl.ONE,
- gl.ONE_MINUS_SRC_ALPHA,
+ this.gl.enable(this.gl.BLEND);
+ this.gl.disable(this.gl.DEPTH_TEST);
+ this.gl.blendFuncSeparate(
+ this.gl.SRC_ALPHA,
+ this.gl.ONE_MINUS_SRC_ALPHA,
+ this.gl.ONE,
+ this.gl.ONE_MINUS_SRC_ALPHA,
);
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
- gl.useProgram(program);
-
- this.colorBuffer = gl.createBuffer();
- this.colorLoc = gl.getAttribLocation(program, "a_color");
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
+ this.gl.useProgram(program);
- this.positionBuffer = gl.createBuffer();
- this.positionLoc = gl.getAttribLocation(program, "a_position");
+ this.colorBuffer = this.gl.createBuffer()!;
+ this.colorLoc = this.gl.getAttribLocation(program, "a_color");
- this.textureCoordBuffer = gl.createBuffer();
- this.textureCoordLoc = gl.getAttribLocation(program, "a_textureCoord");
+ this.positionBuffer = this.gl.createBuffer()!;
+ this.positionLoc = this.gl.getAttribLocation(program, "a_position");
- this.pointSizeLoc = gl.getUniformLocation(program, "point_size");
-
- let textureCoords = [];
- for (let i = 0; i < dataObj.npoint; i++) {
- textureCoords.push(...utils.getTextureCoord(i));
- }
- for (let i = 0; i < dataObj.ndim * 2; i++) {
- textureCoords.push([0, 0]);
- }
+ this.pointSizeLoc = this.gl.getUniformLocation(program, "point_size")!;
- if (this.textureCoordLoc !== -1) {
- gl.bindBuffer(gl.ARRAY_BUFFER, this.textureCoordBuffer);
- gl.bufferData(
- gl.ARRAY_BUFFER,
- utils.flatten(textureCoords),
- gl.STATIC_DRAW,
- );
- gl.vertexAttribPointer(this.textureCoordLoc, 2, gl.FLOAT, false, 0, 0);
- gl.enableVertexAttribArray(this.textureCoordLoc);
- }
+ this.isDrawingAxisLoc = this.gl.getUniformLocation(
+ program,
+ "isDrawingAxis",
+ )!;
- let texture = utils.loadTexture(
- gl,
- utils.getTextureURL(this.overlay.getDataset()),
- );
- this.samplerLoc = gl.getUniformLocation(program, "uSampler");
- gl.activeTexture(gl.TEXTURE0);
- gl.bindTexture(gl.TEXTURE_2D, texture);
- gl.uniform1i(this.samplerLoc, 0);
+ this.canvasWidthLoc = this.gl.getUniformLocation(program, "canvasWidth")!;
+ this.canvasHeightLoc = this.gl.getUniformLocation(program, "canvasHeight")!;
+ this.gl.uniform1f(this.canvasWidthLoc, this.gl.canvas.clientWidth);
+ this.gl.uniform1f(this.canvasHeightLoc, this.gl.canvas.clientHeight);
- this.isDrawingAxisLoc = gl.getUniformLocation(program, "isDrawingAxis");
+ this.modeLoc = this.gl.getUniformLocation(program, "mode")!;
+ this.gl.uniform1i(this.modeLoc, 0); // "point" mode
- this.canvasWidthLoc = gl.getUniformLocation(program, "canvasWidth");
- this.canvasHeightLoc = gl.getUniformLocation(program, "canvasHeight");
- gl.uniform1f(this.canvasWidthLoc, gl.canvas.clientWidth);
- gl.uniform1f(this.canvasHeightLoc, gl.canvas.clientHeight);
-
- this.modeLoc = gl.getUniformLocation(program, "mode");
- this.setMode(this.mode);
-
- this.colorFactorLoc = gl.getUniformLocation(program, "colorFactor");
+ this.colorFactorLoc = this.gl.getUniformLocation(program, "colorFactor")!;
this.setColorFactor(this.colorFactor);
if (this.gt === undefined || this.gt.ndim != dataObj.ndim) {
- let gt = new GrandTour(dataObj.ndim, this.init_matrix);
- this.gt = gt;
+ this.gt = new GrandTour(dataObj.ndim);
}
- };
-
- // this.shouldCentralizeOrigin = this.shouldPlayGrandTour;
-
- this.shouldRender = true;
- this.s = 0;
+ }
- this.play = (t = 0) => {
+ play(_t = 0) {
let dt = 0;
if (
@@ -280,42 +268,42 @@ export function TeaserRenderer(gl: WebGLRenderingContext, program: WebGLProgram,
}
this.animId = requestAnimationFrame(this.play.bind(this));
- };
+ }
- this.setColorFactor = function (f) {
+ setColorFactor(f: number) {
this.colorFactor = f;
- this.gl.uniform1f(this.colorFactorLoc, f);
- };
+ this.gl.uniform1f(this.colorFactorLoc!, f);
+ }
- this.setPointSize = function (s) {
+ setPointSize(s: number) {
this.pointSize = s;
- gl.uniform1f(this.pointSizeLoc, s * window.devicePixelRatio);
- };
+ this.gl.uniform1f(this.pointSizeLoc!, s * window.devicePixelRatio);
+ }
- this.pause = function () {
+ pause() {
if (this.animId) {
cancelAnimationFrame(this.animId);
- this.animId = null;
+ this.animId = undefined;
}
this.shouldRender = false;
console.log("paused");
- };
+ }
- this.setEpochIndex = (i) => {
+ setEpochIndex(i: number) {
this.epochIndex = i;
this.overlay.epochSlider.property("value", i);
-
+ if (!this.dataObj) return;
this.overlay.svg.select("#epochIndicator")
.text(`Epoch: ${this.epochIndex}/${(this.dataObj.nepoch - 1)}`);
- };
+ }
- this.playFromEpoch = function (epoch) {
+ playFromEpoch(epoch: number) {
this.shouldAutoNextEpoch = true;
this.setEpochIndex(epoch);
this.overlay.playButton.attr("class", "tooltip play-button fa fa-pause");
- };
+ }
- this.nextEpoch = function () {
+ nextEpoch() {
if (this.epochs.length == 1) {
return;
}
@@ -325,9 +313,9 @@ export function TeaserRenderer(gl: WebGLRenderingContext, program: WebGLProgram,
} else {
this.setEpochIndex(this.epochs[0]);
}
- };
+ }
- this.prevEpoch = function () {
+ prevEpoch() {
if (this.epochs.length == 1) {
return;
}
@@ -336,21 +324,17 @@ export function TeaserRenderer(gl: WebGLRenderingContext, program: WebGLProgram,
} else {
this.setEpochIndex(this.epochs.length - 1);
}
- };
+ }
+
+ render(dt: number) {
+ if (!this.dataObj || !this.gt) return;
- this.render = function (dt: number) {
- if (this.dataObj.dataTensor === undefined) {
- return;
- }
let dataObj = this.dataObj;
let data = this.dataObj.dataTensor[this.epochIndex];
let labels = this.dataObj.labels;
- let gl = this.gl;
- let gt = this.gt;
- let program = this.program;
data = data.concat(utils.createAxisPoints(dataObj.ndim));
- let points = gt.project(data, dt);
+ let points = this.gt.project(data, dt);
if (
this.epochIndex > 0 &&
@@ -358,7 +342,7 @@ export function TeaserRenderer(gl: WebGLRenderingContext, program: WebGLProgram,
) {
let data0 = this.dataObj.dataTensor[this.epochIndex - 1];
data0 = data0.concat(utils.createAxisPoints(dataObj.ndim));
- let points0 = gt.project(data0, dt / this.framesPerTransition);
+ let points0 = this.gt.project(data0, dt / this.framesPerTransition);
points = utils.linearInterpolate(
points0,
points,
@@ -368,7 +352,7 @@ export function TeaserRenderer(gl: WebGLRenderingContext, program: WebGLProgram,
utils.updateScale_center(
points,
- gl.canvas,
+ this.gl.canvas,
this.sx_center,
this.sy_center,
this.sz_center,
@@ -379,7 +363,7 @@ export function TeaserRenderer(gl: WebGLRenderingContext, program: WebGLProgram,
utils.updateScale_span(
points,
- gl.canvas,
+ this.gl.canvas,
this.sx_span,
this.sy_span,
this.sz_span,
@@ -390,9 +374,9 @@ export function TeaserRenderer(gl: WebGLRenderingContext, program: WebGLProgram,
let transition;
if (this.scaleTransitionDelta > 0) {
- transition = (t) => Math.pow(t, 0.5);
+ transition = (t: number) => Math.pow(t, 0.5);
} else {
- transition = (t) => 1 - Math.pow(1 - t, 0.5);
+ transition = (t: number) => 1 - Math.pow(1 - t, 0.5);
}
this.sx = utils.mixScale(
this.sx_center,
@@ -410,103 +394,76 @@ export function TeaserRenderer(gl: WebGLRenderingContext, program: WebGLProgram,
points = utils.data2canvas(points, this.sx, this.sy, this.sz);
- if (this.mode == "image") {
- points = utils.point2rect(
- points,
- dataObj.npoint,
- 14 * math.sqrt(this.scaleFactor),
- );
- }
-
dataObj.points = points;
- let colors = labels.map((d) => utils.baseColors[d]);
let bgColors = labels.map((d) => utils.bgColors[d]);
+ let colors: ColorRGBA[] = labels
+ .map((d) => utils.baseColors[d])
+ .concat(utils.createAxisColors(dataObj.ndim))
+ .map((c, i) => [c[0], c[1], c[2], dataObj.alphas[i]]);
- colors = colors.concat(utils.createAxisColors(dataObj.ndim));
- colors = colors.map((c, i) => [c[0], c[1], c[2], dataObj.alphas[i]]);
-
- if (this.mode == "image") {
- if (this.colorRect === undefined || this.shouldRecalculateColorRect) {
- this.colorRect = utils.color2rect(colors, dataObj.npoint, dataObj.ndim);
- this.bgColorRect = utils.color2rect(
- bgColors,
- dataObj.npoint,
- dataObj.ndim,
- );
- this.shouldRecalculateColorRect = false;
- }
- colors = this.colorRect;
- bgColors = this.bgColorRect;
- }
dataObj.colors = colors;
- let colorBuffer = this.colorBuffer;
- let positionBuffer = this.positionBuffer;
- let colorLoc = this.colorLoc;
- let positionLoc = this.positionLoc;
+ this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
- gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+ this.gl.clearColor(...utils.CLEAR_COLOR, 1.0);
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
- gl.clearColor(...utils.CLEAR_COLOR, 1.0);
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
-
- gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, utils.flatten(points), gl.STATIC_DRAW);
- gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 0, 0);
- gl.enableVertexAttribArray(positionLoc);
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer!);
+ this.gl.bufferData(
+ this.gl.ARRAY_BUFFER,
+ utils.flatten(points),
+ this.gl.STATIC_DRAW,
+ );
+ this.gl.vertexAttribPointer(
+ this.positionLoc!,
+ 3,
+ this.gl.FLOAT,
+ false,
+ 0,
+ 0,
+ );
+ this.gl.enableVertexAttribArray(this.positionLoc!);
- gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
- gl.bufferData(
- gl.ARRAY_BUFFER,
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.colorBuffer!);
+ this.gl.bufferData(
+ this.gl.ARRAY_BUFFER,
new Uint8Array(utils.flatten(colors)),
- gl.STATIC_DRAW,
+ this.gl.STATIC_DRAW,
+ );
+ this.gl.vertexAttribPointer(
+ this.colorLoc!,
+ 4,
+ this.gl.UNSIGNED_BYTE,
+ true,
+ 0,
+ 0,
);
- gl.vertexAttribPointer(colorLoc, 4, gl.UNSIGNED_BYTE, true, 0, 0);
- gl.enableVertexAttribArray(colorLoc);
+ this.gl.enableVertexAttribArray(this.colorLoc!);
- let c0 = bgColors.map((c, i) => [c[0], c[1], c[2], utils.pointAlpha]);
- gl.bufferData(
- gl.ARRAY_BUFFER,
+ let c0 = bgColors.map((c) => [c[0], c[1], c[2], utils.pointAlpha]);
+ this.gl.bufferData(
+ this.gl.ARRAY_BUFFER,
new Uint8Array(utils.flatten(c0)),
- gl.STATIC_DRAW,
+ this.gl.STATIC_DRAW,
);
let c1;
- if (this.mode === "point") {
- gl.uniform1i(this.isDrawingAxisLoc, 0);
- this.setPointSize(this.pointSize0 * Math.sqrt(this.scaleFactor));
+ this.gl.uniform1i(this.isDrawingAxisLoc!, 0);
+ this.setPointSize(this.pointSize0 * Math.sqrt(this.scaleFactor));
- gl.drawArrays(gl.POINTS, 0, dataObj.npoint);
- c1 = colors.map((c, i) => [c[0], c[1], c[2], dataObj.alphas[i]]);
- } else {
- gl.drawArrays(gl.TRIANGLES, 0, dataObj.npoint * 6);
- c1 = colors.map((
- c,
- i,
- ) => [c[0], c[1], c[2], dataObj.alphas[Math.floor(i / 6)]]);
- }
- gl.bufferData(
- gl.ARRAY_BUFFER,
+ this.gl.drawArrays(this.gl.POINTS, 0, dataObj.npoint);
+ c1 = colors.map((c, i) => [c[0], c[1], c[2], dataObj.alphas[i]]);
+ this.gl.bufferData(
+ this.gl.ARRAY_BUFFER,
new Uint8Array(utils.flatten(c1)),
- gl.STATIC_DRAW,
+ this.gl.STATIC_DRAW,
);
- if (this.mode === "point") {
- gl.uniform1i(this.isDrawingAxisLoc, 0);
- gl.drawArrays(gl.POINTS, 0, dataObj.npoint);
+ this.gl.uniform1i(this.isDrawingAxisLoc!, 0);
+ this.gl.drawArrays(this.gl.POINTS, 0, dataObj.npoint);
- gl.uniform1i(this.isDrawingAxisLoc, 1);
- gl.drawArrays(gl.LINES, dataObj.npoint, dataObj.ndim * 2);
- } else {
- gl.uniform1i(this.isDrawingAxisLoc, 0);
- gl.drawArrays(gl.TRIANGLES, 0, dataObj.npoint * 6);
-
- this.setMode("point");
- gl.uniform1i(this.isDrawingAxisLoc, 1);
- gl.drawArrays(gl.LINES, dataObj.npoint * 6, dataObj.ndim * 2);
- this.setMode("image");
- }
- return;
- };
+ this.gl.uniform1i(this.isDrawingAxisLoc!, 1);
+ this.gl.drawArrays(this.gl.LINES, dataObj.npoint, dataObj.ndim * 2);
+ }
}
diff --git a/src/index.ts b/src/index.ts
index f4b742f..edf614d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,67 +1,28 @@
-// @ts-check
import * as d3 from "d3";
-import { TeaserRenderer } from './TeaserRenderer';
-import * as utils from './utils';
+import { TeaserRenderer } from "./TeaserRenderer";
+import * as utils from "./utils";
+import fs from "./shaders/teaser_fragment.glsl";
+import vs from "./shaders/teaser_vertex.glsl";
-import fs from './shaders/teaser_fragment.glsl';
-import vs from './shaders/teaser_vertex.glsl';
-
-const teaserFigure = document.querySelector("d-figure.teaser")!;
-let teaser: typeof TeaserRenderer;
-let allViews: typeof teaser[] = [];
-
-let c = utils.CLEAR_COLOR.map(d => d * 255);
-// @ts-expect-error
-d3.selectAll('.flex-item').style('background', d3.rgb(...c))
-
-teaserFigure.addEventListener("ready", function() {
- console.log('teaserFigure ready');
- var epochs = d3.range(0, 1, 1);
- var urls = utils.getChromTeaserDataURL();
- var { gl, program } = utils.initGL("#teaser", fs, vs);
-
- teaser = new TeaserRenderer(gl, program, {
- epochs: epochs,
- shouldAutoNextEpoch: true
- });
-
- allViews.push(teaser);
-
- teaser.overlay.fullScreenButton.style('top', '18px');
- teaser.overlay.epochSlider.style('top', 'calc(100% - 28px)');
- teaser.overlay.playButton.style('top', ' calc(100% - 34px)');
- teaser.overlay.grandtourButton.style('top', ' calc(100% - 34px)');
-
- // teaser.overlay.fullScreenButton.remove();
- teaser.overlay.modeOption.remove();
- teaser.overlay.datasetOption.remove();
- teaser.overlay.zoomSliderDiv.remove();
- // teaser.overlay.grandtourButton.remove();
-
- teaser = utils.loadDataToRenderer(urls, teaser);
-
- window.addEventListener('resize', ()=>{
- teaser.setFullScreen(teaser.isFullScreen);
- });
+let figure = document.querySelector("d-figure.teaser")!;
+let canvas = figure.querySelector("canvas")!;
+let { gl, program } = utils.initGL(canvas, fs, vs);
+let teaser = new TeaserRenderer(gl, program, {
+ epochs: d3.range(0, 1, 1),
+ shouldAutoNextEpoch: true,
});
-teaserFigure.addEventListener("onscreen", function() {
- console.log('teaser onscreen');
- if(teaser && teaser.play){
- teaser.shouldRender = true;
- teaser.play();
- }
- for(let view of allViews){
- if(view !== teaser && view.pause){
- view.pause();
- }
- }
-});
+teaser.overlay.fullScreenButton.style("top", "18px");
+teaser.overlay.epochSlider.style("top", "calc(100% - 28px)");
+teaser.overlay.playButton.style("top", " calc(100% - 34px)");
+teaser.overlay.grandtourButton.style("top", " calc(100% - 34px)");
+
+await utils.loadDataToRenderer([
+ new URL("../data/eigs.arrow", import.meta.url).href,
+], teaser);
-teaserFigure.addEventListener("offscreen", function() {
- if(teaser && teaser.pause){
- teaser.pause();
- }
+window.addEventListener("resize", () => {
+ teaser.setFullScreen(teaser.isFullScreen);
});
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..d2711cc
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,25 @@
+import type { BaseType, ScaleContinuousNumeric, Selection } from "d3";
+
+export type Point = [number, number, number];
+
+export type Scale = ScaleContinuousNumeric;
+
+export type ColorRGB = [number, number, number];
+
+export type ColorRGBA = [number, number, number, number];
+
+export interface Renderer {
+ gl: WebGLRenderingContext;
+ render(dt: number): void;
+ play(t?: number): void;
+ pause(): void;
+ initData(
+ buffer: ArrayBuffer,
+ url?: string,
+ i?: number,
+ length?: number,
+ ): Promise;
+ overlay: {
+ figure: Selection;
+ };
+}
diff --git a/src/utils.ts b/src/utils.ts
index 90a1ce1..ec7a4e9 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -2,12 +2,14 @@ import * as d3 from "d3";
import * as math from "mathjs";
import numeric from "numeric";
+import type { ColorRGB, ColorRGBA, Point, Renderer, Scale } from "./types";
+
export const CLEAR_COLOR = [1, 1, 1] as const;
export const CLEAR_COLOR_SMALL_MULTIPLE = [1, 1, 1] as const;
export const MIN_EPOCH = 0;
export const MAX_EPOCH = 99;
export const COLOR_FACTOR = 0.9;
-export const dataset = "mnist";
+export let dataset = "mnist";
export const datasetListener = [];
export const pointAlpha = 255 * 0.1;
@@ -35,11 +37,16 @@ export const buttonColors = {
"off": "#f3f3f3",
};
-export function clamp(min, max, v) {
+export function clamp(min: number, max: number, v: number) {
return Math.max(max, Math.min(min, v));
}
-export function mixScale(s0, s1, progress, func) {
+export function mixScale(
+ s0: Scale,
+ s1: Scale,
+ progress: number,
+ func: (x: number) => number,
+) {
let range0 = s0.range();
let range1 = s1.range();
@@ -54,7 +61,12 @@ export function mixScale(s0, s1, progress, func) {
.range(mix(range0, range1, progress));
}
-export function data2canvas(points, sx, sy, sz) {
+export function data2canvas(
+ points: number[][],
+ sx: Scale,
+ sy: Scale,
+ sz: Scale,
+) {
points = points.map((row) => {
return [sx(row[0]), sy(row[1]), sz(row[2])];
});
@@ -62,28 +74,19 @@ export function data2canvas(points, sx, sy, sz) {
}
export function updateScale_span(
- points,
- canvas,
- sx,
- sy,
- sz,
+ points: number[][],
+ canvas: HTMLCanvasElement,
+ sx: d3.ScaleLinear,
+ sy: d3.ScaleLinear,
+ sz: d3.ScaleLinear,
scaleFactor = 1.0,
- marginRight = undefined,
- marginBottom = undefined,
- marginLeft = undefined,
- marginTop = undefined,
+ marginRight?: number,
+ marginBottom = 65,
+ marginLeft = 32,
+ marginTop = 22,
) {
- if (marginTop === undefined) {
- marginTop = 22;
- }
- if (marginBottom === undefined) {
- marginBottom = 65;
- }
- if (marginLeft === undefined) {
- marginLeft = 32;
- }
if (marginRight === undefined) {
- marginRight = d3.max(Object.values(legendLeft)) + 15;
+ marginRight = d3.max(Object.values(legendLeft))! + 15;
}
let vmin = math.min(points, 0);
@@ -114,16 +117,16 @@ export function updateScale_span(
}
export function updateScale_center(
- points,
- canvas,
- sx,
- sy,
- sz,
+ points: number[][],
+ canvas: HTMLCanvasElement,
+ sx: Scale,
+ sy: Scale,
+ sz: Scale,
scaleFactor = 1.0,
- marginRight = undefined,
- marginBottom = undefined,
- marginLeft = undefined,
- marginTop = undefined,
+ marginRight?: number,
+ marginBottom?: number,
+ marginLeft?: number,
+ marginTop?: number,
) {
if (marginTop === undefined) {
marginTop = 22;
@@ -135,7 +138,7 @@ export function updateScale_center(
marginLeft = 32;
}
if (marginRight === undefined) {
- marginRight = d3.max(Object.values(legendLeft)) + 15;
+ marginRight = d3.max(Object.values(legendLeft))! + 15;
}
let vmax = math.max(math.abs(points), 0);
@@ -166,21 +169,7 @@ export function updateScale_center(
.range([0, 1]);
}
-export function toDataURL(url, callback) {
- var xhr = new XMLHttpRequest();
- xhr.onload = function () {
- var reader = new FileReader();
- reader.onloadend = function () {
- callback(reader.result);
- };
- reader.readAsDataURL(xhr.response);
- };
- xhr.open("GET", url);
- xhr.responseType = "blob";
- xhr.send();
-}
-
-export function embed(matrix, canvas) {
+export function embed(matrix: T[][], canvas: T[][]) {
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[0].length; j++) {
canvas[i][j] = matrix[i][j];
@@ -189,124 +178,48 @@ export function embed(matrix, canvas) {
return canvas;
}
-// huh: https://eslint.org/docs/rules/guard-for-in
-export function walkObject(obj, f) {
- for (let key in obj) {
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
- f(key);
- }
- }
-}
-
-export function scaleRows(matrix, isRowSelected, beta1, beta0) {
- let selectedCount = numeric.sum(isRowSelected);
- let res = matrix.map((row, i) => {
- row = row.slice();
- if (isRowSelected[i]) {
- row = numeric.mul(row, beta1 / selectedCount);
- } else {
- row = numeric.mul(row, beta0 / (matrix.length - selectedCount));
- }
- return row;
- });
- return res;
-}
-
-export function setDataset(datasetName, callback0) {
- this.dataset = datasetName;
- for (let callback of datasetListener) {
- callback(datasetName);
- }
- if (callback0 !== undefined) {
- callback0();
- }
- // }
-}
-
export function getDataset() {
return dataset;
}
-export function addDatasetListener(callback) {
- datasetListener.push(callback);
-}
-
-export function clearDatasetListener() {
- for (let i = 0; i < datasetListener.length; i++) {
- datasetListener.pop();
- }
-}
-
-export function getLabelNames(adversarial = false, dataset = undefined) {
+export function getLabelNames(_adversarial = false, dataset?: string) {
if (dataset === undefined) {
dataset = getDataset();
}
let res;
if (dataset == "mnist") {
res = ["A0", "A1", "B0", "B1", "B2"];
- } else if (dataset == "fashion-mnist") {
- res = [
- "T-shirt/top",
- "Trouser",
- "Pullover",
- "Dress",
- "Coat",
- "Sandal",
- "Shirt",
- "Sneaker",
- "Bag",
- "Ankle boot",
- ];
- } else if (dataset == "cifar10") {
- res = [
- "Airplane",
- "Automobile",
- "Bird",
- "Cat",
- "Deer",
- "Dog",
- "Frog",
- "Horse",
- "Ship",
- "Truck",
- ];
} else {
throw new Error("Unrecognized dataset " + dataset);
}
- if (adversarial) {
- res.push("adversarial");
- }
return res;
}
-export function getChromTeaserDataURL() {
- return [
- new URL('../data/eigs.arrow', import.meta.url).href
- ]
-}
-
export function getTextureURL(dataset = getDataset(), datasetType = "test") {
return "data/softmax/" + dataset + "/input-" + datasetType + ".png";
}
-export function initGL(canvasid: string, fs: string, vs: string) {
- let canvas = document.getElementById(canvasid.slice(1)) as HTMLCanvasElement;
+export function initGL(canvas: HTMLCanvasElement, fs: string, vs: string) {
let gl = canvas.getContext("webgl", { premultipliedAlpha: false })!;
let program = initShaders(gl, fs, vs);
return { gl, program };
}
-export function loadDataWithCallback(urls, callback) {
- for (let i = 0; i < urls.length; i++) {
- loadDataBin(urls[i], (buffer, url) => {
- callback(buffer, url, i, urls.length);
- });
- }
-}
+export async function loadDataToRenderer(urls: string[], renderer: Renderer) {
+ let banner = renderer.overlay.figure.selectAll(".banner")
+ .data([0])
+ .enter()
+ .append("div")
+ .attr("class", "banner");
-function bannerAnimation(renderer) {
- let banner = renderer.overlay.banner;
- let bannerText = renderer.overlay.bannerText;
+ let bannerText = banner
+ .selectAll(".bannerText")
+ .data([0])
+ .enter()
+ .append("p")
+ .attr("class", "bannerText");
+
+ // render loop
function repeat() {
bannerText
.text("Loading")
@@ -322,47 +235,33 @@ function bannerAnimation(renderer) {
.on("end", repeat);
}
repeat();
-}
-function createBanner(renderer) {
- let overlay = renderer.overlay;
- if (overlay.figure) {
- overlay.banner = overlay.figure.selectAll(".banner")
- .data([0])
- .enter()
- .append("div")
- .attr("class", "banner");
- overlay.banner = overlay.figure.selectAll(".banner");
- overlay.bannerText = overlay.banner
- .selectAll(".bannerText")
- .data([0])
- .enter()
- .append("p")
- .attr("class", "bannerText");
- overlay.bannerText = overlay.banner.selectAll(".bannerText");
- }
-}
-
-export function loadDataToRenderer(urls, renderer, onReadyCallback) {
- if (renderer.overlay) {
- createBanner(renderer);
- bannerAnimation(renderer);
- }
+ await Promise.all(
+ urls.map(async (url, i) => {
+ let buffer = await loadDataBin(url);
+ return renderer.initData(buffer, url, i, urls.length);
+ }),
+ );
- for (let i = 0; i < urls.length; i++) {
- loadDataBin(urls[i], (buffer, url) => {
- renderer.initData(buffer, url, i, urls.length, onReadyCallback);
- });
- }
- return renderer;
+ banner.remove();
}
-export function reshape(array, shape) {
+// TODO: fail when shape isn't tuple... Right now returns `number`.
+type NestedArray = Shape extends
+ [infer _, ...infer Rest]
+ ? Rest extends number[] ? NestedArray[] : never
+ : T;
+
+export function reshape- (
+ array: ArrayLike
- ,
+ shape: Shape,
+): NestedArray
- {
let res = [];
if (shape.length == 2) {
for (let row = 0; row < shape[0]; row++) {
res.push([]);
for (let col = 0; col < shape[1]; col++) {
+ // @ts-expect-error
res[res.length - 1].push(array[shape[1] * row + col]);
}
}
@@ -371,37 +270,32 @@ export function reshape(array, shape) {
for (let i = 0; i < shape[0]; i++) {
res.push(
reshape(
+ // @ts-expect-error
array.slice(i * blocksize, (i + 1) * blocksize),
shape.slice(1),
),
);
}
}
- return res;
+ return res as any;
}
-export function cacheAll(urls) {
- for (let url of urls) loadDataBin(url, () => {});
+export async function cacheAll(urls: string[]) {
+ await Promise.all(urls.map(loadDataBin));
}
-const cache = {};
-export async function loadDataBin(url, callback) {
- if (!(url in cache)) {
+const cache = new Map();
+export async function loadDataBin(url: string) {
+ let buffer = cache.get(url);
+ if (!buffer) {
let response = await fetch(url);
- cache[url] = await response.arrayBuffer();
+ buffer = await response.arrayBuffer();
+ cache.set(url, buffer);
}
- callback(cache[url], url);
+ return buffer;
}
-export function loadDataCsv(fns, renderer) {
- let promises = fns.map((fn) => d3.text(fn));
- Promise.all(promises).then(function (dataRaw) {
- renderer.initData(dataRaw);
- renderer.play();
- });
-}
-
-export function resizeCanvas(canvas) {
+export function resizeCanvas(canvas: HTMLCanvasElement) {
let DPR = window.devicePixelRatio;
let displayWidth = DPR * canvas.clientWidth;
@@ -415,21 +309,21 @@ export function resizeCanvas(canvas) {
canvas.width = displayWidth;
canvas.height = displayHeight;
}
- canvas.style.width = canvas.clientWidth;
- canvas.style.height = canvas.clientHeight;
+ canvas.style.width = String(canvas.clientWidth);
+ canvas.style.height = String(canvas.clientHeight);
}
export const baseColorsHex = [...d3.schemeCategory10];
baseColorsHex.push("#444444");
baseColorsHex.push("#444444");
-function hexToRgb(hex: string) {
+function hexToRgb(hex: string): ColorRGB {
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
return [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16),
- ] as const;
+ ];
}
export const baseColors = baseColorsHex.map(hexToRgb);
@@ -439,62 +333,72 @@ export const bgColors = numeric.add(
0.95 * 255 * 0.4,
);
-export function createAxisPoints(ndim) {
- let res = math.identity(ndim)._data;
+export function createAxisPoints(ndim: number) {
+ let res = (math.identity(ndim) as math.Matrix).toArray();
for (let i = ndim - 1; i >= 0; i--) {
- res.splice(i, 0, math.zeros(ndim)._data);
+ let zeros = (math.zeros(ndim) as math.Matrix).toArray() as number[];
+ res.splice(i, 0, zeros);
}
- return res;
+ return res as number[][];
}
-export function createAxisColors(ndim) {
+export function createAxisColors(ndim: number) {
return d3.range(ndim * 2).map(
(_, i) => baseColors[Math.floor(i / 2) % baseColors.length],
);
}
-export function linearInterpolate(data1, data2, p) {
+export function linearInterpolate(
+ data1: T,
+ data2: T,
+ p: number,
+) {
// let res = math.zeros(data1.length, data1[0].length)._data;
// for (let i=0; i(data1: T, data2: T, p: number) {
return linearInterpolate(data1, data2, p);
}
-export function orthogonalize(matrix, priorityRowIndex = 0) {
+export function orthogonalize(
+ matrix: M,
+ priorityRowIndex = 0,
+): M {
// make row vectors in matrix pairwise orthogonal;
- function proj(u, v) {
+ function proj(u: M[number], v: M[number]): M[number] {
// @ts-expect-error
return numeric.mul(numeric.dot(u, v) / numeric.dot(u, u), u);
}
- function normalize(v, unitlength = 1) {
+ function normalize(v: M[number], unitlength = 1): M[number] {
if (numeric.norm2(v) <= 0) {
return v;
} else {
+ // @ts-expect-error
return numeric.div(v, numeric.norm2(v) / unitlength);
}
}
// Gram–Schmidt orthogonalization
let priorityRow = matrix[priorityRowIndex];
- let firstRow = matrix[0];
+ let firstRow = matrix[0] as M[number];
matrix[0] = priorityRow;
matrix[priorityRowIndex] = firstRow;
matrix[0] = normalize(matrix[0]);
for (let i = 1; i < matrix.length; i++) {
for (let j = 0; j < i; j++) {
+ // @ts-expect-error
matrix[i] = numeric.sub(matrix[i], proj(matrix[j], matrix[i]));
}
matrix[i] = normalize(matrix[i]);
@@ -505,7 +409,12 @@ export function orthogonalize(matrix, priorityRowIndex = 0) {
return matrix;
}
-export function point2rect(points, npoint, sideLength, yUp = false) {
+export function point2rect(
+ points: Point[],
+ npoint: number,
+ sideLength: number,
+ yUp = false,
+) {
let res = [];
//points
@@ -538,7 +447,11 @@ export function point2rect(points, npoint, sideLength, yUp = false) {
return res;
}
-export function color2rect(colors, npoint, ndim) {
+export function color2rect(
+ colors: Color[],
+ npoint: number,
+ ndim: number,
+) {
let pointColors = colors.slice(0, npoint)
.map((c) => [c, c, c, c, c, c])
.reduce((a, b) => a.concat(b), []);
@@ -546,47 +459,8 @@ export function color2rect(colors, npoint, ndim) {
return pointColors.concat(axisColors);
}
-export function getTextureCoord(
- i,
- nRow = 10,
- nCol = 100,
- isAdversarial = false,
- epoch = 99,
- nepoch = 100,
-) {
- let nRow0 = nRow;
- let npoint;
- if (isAdversarial) {
- npoint = nRow * nCol;
- nRow = nRow + nepoch;
- }
-
- let ul, ur, ll, lr;
- let numPerRow = nCol;
- let numPerCol = nRow;
- let dx = 1 / numPerRow;
- let dy = 1 / numPerCol;
- if (isAdversarial && i >= npoint - 89) { // hardcoded: last 89 are adversarial examples
- ul = [
- dx * ((i - (npoint - 89)) % numPerRow),
- dy * Math.floor(nRow0 + epoch),
- ];
- } else {
- ul = [dx * (i % numPerRow), dy * Math.floor(i / numPerRow)];
- }
- ur = ul.slice();
- ur[0] += dx;
- ll = ul.slice();
- ll[1] += dy;
- lr = ul.slice();
- lr[0] += dx;
- lr[1] += dy;
-
- return [ur, ul, ll, ur, ll, lr];
-}
-
-export function loadTexture(gl, url) {
- function isPowerOf2(x) {
+export function loadTexture(gl: WebGLRenderingContext, url: string) {
+ function isPowerOf2(x: number) {
// @ts-expect-error
return x & (x - 1) == 0;
}
@@ -638,41 +512,6 @@ export function loadTexture(gl, url) {
return texture;
}
-export function setTeaser(
- renderer,
- datasetname,
- epochIndex,
- classes,
- shouldAutoNextEpoch = true,
- timeout = 0,
- callback = undefined,
-) {
- setDataset(datasetname, () => {
- renderer.setEpochIndex(epochIndex);
- if (classes.length > 0) {
- renderer.overlay.selectedClasses = new Set(classes);
- renderer.overlay.onSelectLegend(classes);
- } else {
- renderer.overlay.selectedClasses = new Set();
- renderer.overlay.restoreAlpha();
- }
-
- renderer.shouldAutoNextEpoch = shouldAutoNextEpoch;
- d3.select(renderer.overlay.svg.node().parentElement)
- .select(".play-button")
- .attr("class", () => {
- if (renderer.shouldAutoNextEpoch) {
- return "tooltip play-button fa fa-pause";
- } else {
- return "tooltip play-button fa fa-play";
- }
- });
- if (callback) {
- callback();
- }
- });
-}
-
export function* iterN(it: Iterable, n: number) {
let i = 0;
for (let x of it) {
@@ -681,7 +520,11 @@ export function* iterN(it: Iterable, n: number) {
}
}
-function getShader(gl: WebGLRenderingContext, shaderScript: string, type: number) {
+function getShader(
+ gl: WebGLRenderingContext,
+ shaderScript: string,
+ type: number,
+) {
var shader = gl.createShader(type)!;
gl.shaderSource(shader, shaderScript);
gl.compileShader(shader);
@@ -702,12 +545,14 @@ export function initShaders(gl: WebGLRenderingContext, fs: string, vs: string) {
return program;
}
-export function transpose(m) {
+type Matrix = number[][] & { matrix?: true };
+
+export function transpose(m: Matrix) {
if (!m.matrix) {
- return "transpose(): trying to transpose a non-matrix";
+ throw new Error("transpose(): trying to transpose a non-matrix");
}
- var result = [];
+ var result = [] as unknown as Matrix;
for (var i = 0; i < m.length; ++i) {
result.push([]);
for (var j = 0; j < m[i].length; ++j) {
@@ -720,7 +565,7 @@ export function transpose(m) {
return result;
}
-export function flatten(v) {
+export function flatten(v: Matrix) {
if (v.matrix === true) {
v = transpose(v);
}
@@ -744,6 +589,7 @@ export function flatten(v) {
}
} else {
for (var i = 0; i < v.length; ++i) {
+ // @ts-expect-error
floats[i] = v[i];
}
}