Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Points to be Dragged #52

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
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