Skip to content
Open
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ dist/tiles
test-results/
playwright-report/
dist/tiles/*

tiles
tmp.csv
*.feather
16 changes: 12 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"d3-array": "^3.2.0",
"d3-color": "^3.1.0",
"d3-contour": "^4.0.0",
"d3-drag": "^3.0.0",
"d3-ease": "^3.0.1",
"d3-fetch": "^3.0.1",
"d3-format": "^3.1.0",
Expand All @@ -64,6 +65,7 @@
"devDependencies": {
"@playwright/test": "^1.25.0",
"@types/d3": "^7.4.0",
"@types/d3-drag": "^3.0.1",
"@types/d3-geo": "^3.0.2",
"@types/d3-selection": "^3.0.3",
"@types/lodash.merge": "^4.6.7",
Expand Down
7 changes: 4 additions & 3 deletions src/Dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import TileWorker from './tileworker.worker.js?worker&inline';

import { APICall } from './types';
import Scatterplot from './deepscatter';
import { StructRowProxy, Table } from 'apache-arrow';
import { Float32, makeVector, StructRowProxy, Table, Vector } from 'apache-arrow';
import { assert } from './util';
type Key = string;

export abstract class Dataset<T extends Tile> {
Expand Down Expand Up @@ -85,6 +86,7 @@ export abstract class Dataset<T extends Tile> {
}
}
}

/**
*
* @param ix The index of the point to get.
Expand All @@ -105,7 +107,6 @@ export abstract class Dataset<T extends Tile> {
return matches;
}


get tileWorker() {
const NUM_WORKERS = 4;
if (this._tileworkers.length > 0) {
Expand Down Expand Up @@ -248,4 +249,4 @@ function check_overlap(tile : Tile, bbox : Rectangle) : number {
return disqualify;
}
return area(intersection) / area(bbox);
}
}
26 changes: 25 additions & 1 deletion src/deepscatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default class Scatterplot {
public prefs : APICall;
ready : Promise<void>;
public click_handler : ClickFunction;
public drag_handler : DragFunction;
public tooltip_handler : TooltipHTML;

constructor(selector : string, width : number, height: number) {
Expand All @@ -53,6 +54,7 @@ export default class Scatterplot {
// Unresolvable.
this.ready = Promise.resolve();
this.click_handler = new ClickFunction(this);
this.drag_handler = new DragFunction(this);
this.tooltip_handler = new TooltipHTML(this);
this.prefs = {
zoom_balance: 0.35,
Expand Down Expand Up @@ -260,19 +262,34 @@ export default class Scatterplot {
/* PUBLIC see set tooltip_html */
return this.tooltip_handler.f;
}

set click_function(func) {
this.click_handler.f = func;
}

get click_function() {
/* PUBLIC see set click_function */
return this.click_handler.f;
}

set drag_function(func) {
this.drag_handler.f = func;
}

get drag_function() {
return this.drag_handler.f;
}

async plotAPI(prefs : APICall) {

if (prefs.click_function) {
this.click_function = Function('datum', prefs.click_function);
}

if (prefs.drag_function) {
this.drag_function = Function('datum', prefs.drag_function);
}

if (prefs.tooltip_html) {
this.tooltip_html = Function('datum', prefs.tooltip_html);
}
Expand Down Expand Up @@ -448,6 +465,13 @@ class ClickFunction extends SettableFunction<void> {
}
}

class DragFunction extends SettableFunction<void> {
//@ts-ignore bc https://github.com/microsoft/TypeScript/issues/48125
default(datum : StructRowProxy) {
console.log({ ...datum });
}
}

class TooltipHTML extends SettableFunction<string> {
//@ts-ignore bc https://github.com/microsoft/TypeScript/issues/48125
default(point : StructRowProxy) {
Expand All @@ -469,4 +493,4 @@ class TooltipHTML extends SettableFunction<string> {
}
return `${output}</dl>\n`;
}
}
}
66 changes: 58 additions & 8 deletions src/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { select } from 'd3-selection';
import { timer } from 'd3-timer';
import { zoom, zoomIdentity } from 'd3-zoom';
import { mean } from 'd3-array';
import { D3DragEvent, drag } from 'd3-drag';
import { ScaleLinear, scaleLinear } from 'd3-scale';
import { APICall, Encoding } from './types';
// import { annotation, annotationLabel } from 'd3-svg-annotation';
import type { Renderer } from './rendering';
import type QuadtreeRoot from './tile';
import { ReglRenderer } from './regl_rendering';
import Scatterplot from './deepscatter';
import { StructRow } from 'apache-arrow';
import { Float32, makeData, StructRow, StructRowProxy } from 'apache-arrow';
import type { Dataset } from './Dataset';
import type { QuadTile, Tile } from './tile';


export default class Zoom {
Expand All @@ -19,7 +22,7 @@ export default class Zoom {
public width : number;
public height : number;
public renderers : Map<string, Renderer>;
public tileSet? : QuadtreeRoot;
public tileSet? : Dataset<Tile>;
public _timer : d3.Timer;
public _scales : Record<string, d3.ScaleLinear<number, number>>;
public zoomer : d3.ZoomBehavior<Element, any>;
Expand All @@ -43,7 +46,7 @@ export default class Zoom {
this.renderers = new Map();
}

attach_tiles(tiles : QuadtreeRoot) {
attach_tiles(tiles : Dataset) {
this.tileSet = tiles;
this.tileSet._zoom = this;
return this;
Expand Down Expand Up @@ -146,6 +149,9 @@ export default class Zoom {

add_mouseover() {
let last_fired = 0;
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;

//@ts-ignore Not sure how to guarantee this formally.
const renderer : ReglRenderer = this.renderers.get('regl');
const x_aes = renderer.aes.dim('x').current;
Expand All @@ -162,6 +168,7 @@ export default class Zoom {

const d = data[0];

type CircleDragEvent = D3DragEvent<SVGCircleElement, StructRowProxy, any>;
type Annotation = {
x: number,
y: number,
Expand All @@ -180,27 +187,70 @@ export default class Zoom {
] : [];

const { x_, y_ } = this.scales();
const { scatterplot } = this;

this.html_annotation(annotations);

const labelSet = select('#deepscatter-svg')
.selectAll('circle.label')
.data(data, (d_) => d_.ix)
.join(

// Enter
(enter) => enter
.append('circle')
.call(drag<SVGCircleElement, StructRowProxy>()
// Drag Start
.on('start',
function on_drag_start(this: SVGCircleElement, event: CircleDragEvent, datum) {
select(this)
.attr('cursor', 'grabbing')
.raise();
})

// Dragging
.on('drag', function on_dragged(this: SVGCircleElement, event: CircleDragEvent, datum: StructRowProxy) {
let { dx, dy } = event;
if (dx === 0 && dy === 0) return;

// Map (dx, dy) from screen to data space.
dx = x_.invert(dx) - x_.invert(0);
dy = y_.invert(dy) - y_.invert(0);

datum.x += dx;
datum.y += dy;

select(this)
.attr('cx', x_(datum.x))
.attr('cy', y_(datum.y));

const tile = self.scatterplot._root.root_tile as QuadTile;
tile.visit_at_point(datum.ix, tile => tile.needs_rerendering('x', 'y'));
})

// Drag end
.on('end', function on_drag_end(this: SVGCircleElement, event: CircleDragEvent, datum: StructRowProxy) {
select(this).attr('cursor', 'grab');
renderer.render_all(renderer.props);
scatterplot.drag_function(datum);
}))
.attr('cursor', 'grab')
.attr('class', 'label')
.attr('stroke', '#110022')
.attr('r', 12)
.attr('fill', (dd) => this.renderers.get('regl').aes.dim('color').current.apply(dd))
.attr('fill', (dd) => renderer.aes.dim('color').current.apply(dd))
.attr('cx', (datum) => x_(x_aes.value_for(datum)))
.attr('cy', (datum) => y_(y_aes.value_for(datum))),

// Update
(update) => update
.attr('fill', (dd) => this.renderers.get('regl').aes.dim('color').current.apply(dd)),
.attr('fill', (dd) => renderer.aes.dim('color').current.apply(dd)),

// Exit
(exit) => exit.call((e) => e.remove())
)
.on('click', (ev, dd) => {
this.scatterplot.click_function(dd);
scatterplot.click_function(dd);
});
});
}
Expand Down Expand Up @@ -254,7 +304,7 @@ export default class Zoom {
return this._timer;
}

data(dataset) {
data(dataset: Dataset<Tile>) {
if (dataset === undefined) {
return this.tileSet;
}
Expand Down Expand Up @@ -399,4 +449,4 @@ export function window_transform(x_scale : ScaleLinear, y_scale) {
] */

return m1;
}
}
11 changes: 6 additions & 5 deletions src/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import { select } from 'd3-selection';
import { min } from 'd3-array';
import type Scatterplot from './deepscatter';
import type { Tileset } from './tile';
import type { Tile, Tileset } from './tile';
import type { APICall } from './types';
import type Zoom from './interaction';
import type { AestheticSet } from './AestheticSet';
import { timer, Timer } from 'd3-timer';
import type { Dataset } from './Dataset';

abstract class PlotSetting {
abstract start: number;
Expand Down Expand Up @@ -119,7 +120,7 @@ export class Renderer {
public _zoom : Zoom;
public _initializations : Promise<any>[];
public render_props : RenderProps;
constructor(selector, tileSet, scatterplot) {
constructor(selector: string, tileSet: Dataset<Tile>, scatterplot: Scatterplot) {
this.scatterplot = scatterplot;
this.holder = select(selector);
this.canvas = select(this.holder.node().firstElementChild);
Expand Down Expand Up @@ -190,13 +191,13 @@ export class Renderer {
return max_points * k * k / point_size_adjust / point_size_adjust;
}

visible_tiles() {
visible_tiles(): Tile[] {
// yield the currently visible tiles based on the zoom state
// and a maximum index passed manually.
const { max_ix } = this;
const { tileSet } = this;
// Materialize using a tileset method.
let all_tiles;
let all_tiles: Tile[];
const natural_display = this.aes.dim('x').current.field == 'x' &&
this.aes.dim('y').current.field == 'y' &&
this.aes.dim('x').last.field == 'x' &&
Expand All @@ -222,4 +223,4 @@ export class Renderer {
await this._initializations;
this.zoom.restart_timer(500_000);
}
}
}
Loading