diff --git a/src/aesthetics/ScaledAesthetic.ts b/src/aesthetics/ScaledAesthetic.ts index a01c59567..d879e8956 100644 --- a/src/aesthetics/ScaledAesthetic.ts +++ b/src/aesthetics/ScaledAesthetic.ts @@ -32,7 +32,8 @@ export abstract class ScaledAesthetic< > extends Aesthetic { protected _scale: | ScaleContinuousNumeric - | ScaleOrdinal; + | ScaleOrdinal + | null = null; public default_transform: DS.Transform = 'linear'; abstract default_range: [Output['rangeType'], Output['rangeType']]; protected categorical; // Whether this is built on a dictionary variable. diff --git a/src/glsl/general.vert b/src/glsl/general.vert index a0af3c579..626f70d1e 100644 --- a/src/glsl/general.vert +++ b/src/glsl/general.vert @@ -352,10 +352,21 @@ highp float ix_to_random(in float ix, in float seed) { highp float b = 78.233; highp float c = 43758.5453; highp float dt = dot(co.xy, vec2(a, b)); - highp float sn = mod(dt, 3.14); + highp float sn = mod(dt, 3.141592654); return fract(sin(sn) * c); } +highp float tix_rix_to_random_seed(in float tix, in float rix) { + // For high numbers, taking the log avoids coincidence. + highp float seed2 = log(ix + 2.) + 1.; + vec2 co = vec2(seed2, seed); + highp float a = 12.9898; + highp float b = 78.233; + highp float c = 43758.5453; + highp float dt = dot(co.xy, vec2(a, b)); + highp float sn = mod(dt, 3.14); + return fract(sin(sn) * c); +} // The fill color. @@ -374,7 +385,6 @@ vec4 discard_me = vec4(100.0, 100.0, 1.0, 1.0); // Initialized in the main loop // mat3 from_coord_to_gl; - const float e = 1.618282; // I've been convinced. const float tau = 2. * 3.14159265359; diff --git a/src/scatterplot.ts b/src/scatterplot.ts index 8812e90b0..bc516e5d0 100644 --- a/src/scatterplot.ts +++ b/src/scatterplot.ts @@ -79,6 +79,7 @@ export class Scatterplot { ready: Promise; public click_handler: ClickFunction; + public mouseover_handler: PointMouseoverFunction; private hooks: Record = {}; public tooltip_handler: TooltipHTML; public label_click_handler: LabelClick; @@ -585,7 +586,7 @@ export class Scatterplot { */ public dim(dimension: DS.Dimension): ConcreteAesthetic { - return this._renderer!.aes.dim(dimension)!.current; + return this._renderer.aes.dim(dimension).current; } set tooltip_html(func) { @@ -597,6 +598,14 @@ export class Scatterplot { return this.tooltip_handler.f; } + get mouseover_callback() { + return this.mouseover_handler.f; + } + + set mouseover_callback(func) { + this.mouseover_handler.f = func; + } + set label_click( func: (d: Record, scatterplot: Scatterplot) => void, ) { @@ -960,6 +969,17 @@ class ChangeToHighlitPointFunction extends SettableFunction< } } +class PointMouseoverFunction extends SettableFunction { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + default(points: StructRowProxy[], plot: Scatterplot | undefined = undefined) { + return; + } +} + +/** + * A holder for a function that returns the HTML that should appear in a tooltip next to a point. + */ + class TooltipHTML extends SettableFunction { // eslint-disable-next-line @typescript-eslint/no-unused-vars default(point: StructRowProxy, plot: Scatterplot | undefined = undefined) { diff --git a/src/selection.ts b/src/selection.ts index dc318d518..919ab6aed 100644 --- a/src/selection.ts +++ b/src/selection.ts @@ -714,17 +714,15 @@ export class DataSelection { * * @param i the index of the row to get */ - get(i: number | undefined): StructRowProxy { + get(i: number | undefined): StructRowProxy | undefined { if (i === undefined) { i = this.cursor; } if (i > this.selectionSize) { - throw new Error( - `Index ${i} out of bounds for selection of size ${this.selectionSize}`, - ); + undefined; } let currentOffset = 0; - let relevantTile: Tile = undefined; + let relevantTile: Tile | undefined = undefined; let current_tile_ix = 0; for (const match_length of this.match_count) { if (i < currentOffset + match_length) { @@ -735,7 +733,7 @@ export class DataSelection { currentOffset += match_length; } if (relevantTile === undefined) { - return null; + return undefined; } const column = relevantTile.record_batch.getChild( this.name, @@ -745,7 +743,7 @@ export class DataSelection { for (let j = 0; j < column.length; j++) { if (column.get(j)) { if (ix_in_match === offset) { - return relevantTile.record_batch.get(j); + return relevantTile.record_batch.get(j) || undefined; } ix_in_match++; } @@ -769,7 +767,7 @@ export class DataSelection { name: string, codes: string[] | bigint[] | number[], key_field: string, - options: IdentifierOptions = {}, + // options: IdentifierOptions = {}, ): Promise { if (this.dataset.has_column(name)) { throw new Error(`Column ${name} already exists, can't create`); @@ -788,10 +786,6 @@ export class DataSelection { console.error('Unable to match type', typeof codes[0]); } } - - async add_boolean_column(name: string, field: string): Promise { - throw new Error('Method not implemented.'); - } } function bigintmatcher(field: string, matches: bigint[]) { diff --git a/src/tile.ts b/src/tile.ts index a917f687d..9bf988a1a 100644 --- a/src/tile.ts +++ b/src/tile.ts @@ -75,14 +75,20 @@ export class Tile { url: string; //public child_locations: string[] = []; + /** + * + * @param key Either the string identifier of the tile, + * OR a `TileManifest` object including an identifier. * + * @param parent The parent tile -- used to navigate through the tree. + * @param dataset The full atlas dataset of which this tile is a part. + */ constructor( - key: string | Partial, + key: string | (Partial & { key: string }), parent: Tile | null, dataset: Dataset, - base_url: string, ) { // If it's just initiated with a key, build that into a minimal manifest. - let manifest: Partial; + let manifest: Partial & { key: string }; if (typeof key === 'string') { manifest = { key }; } else { @@ -104,7 +110,6 @@ export class Tile { // Grab the next identifier off the queue. This should be async safe with the current setup, but // the logic might fall apart in truly parallel situations. this.numeric_id = tile_identifier++; - this.url = base_url; if (isCompleteManifest(manifest)) this.manifest = manifest; @@ -290,7 +295,7 @@ export class Tile { throw new Error('Attempted to set an incomplete manifest.'); } this._children = manifest.children.map((k: TileManifest | string) => { - return new Tile(k, this, this.dataset, this.url); + return new Tile(k, this, this.dataset); }); this.highest_known_ix = manifest.max_ix; this._completeManifest = manifest; @@ -402,7 +407,7 @@ export class Tile { return existing.batch; } - let url = `${this.url}/${this.key}.feather`; + let url = `${this.dataset.base_url}/${this.key}.feather`; if (suffix !== null) { // 3/4/3 // suffix: 'text' @@ -602,20 +607,25 @@ export class Tile { return this._children; } + // Asynchronously forces generation of all child + // tiles, and then returns them. async allChildren(): Promise> { if (this._children.length) { return this._children; } if (this._partialManifest?.children) { for (const child of this.manifest.children) { - const childTile = new Tile(child, this, this.dataset, this.url); + const childTile = new Tile(child, this, this.dataset); this._children.push(childTile); } return this._children; } await this.populateManifest(); + return this._children; } + // Quadtree tiles can have their limits calculated based on the structure. + // There are cases where these may not be exact. get theoretical_extent(): Rectangle { if (this.dataset.tileStucture === 'other') { // Only three-length-keys are treated as quadtrees. @@ -649,6 +659,16 @@ export function p_in_rect(p: Point, rect: Rectangle | undefined) { ); } +/** + * Returns the coordinates of a 'macrotile' for any given tile. + * This is an experimental feature that is not part of the public API. + * @deprecated + * + * @param key The key to get the macrotile of + * @param size The size of each macrotile, in z. + * @param parents The implicit parents of each macrotile. (If 2, then the macrotile 0/0/0 would contain all tiles at depth 2 and 3) + * @returns + */ function macrotile(key: string, size = 2, parents = 2) { let [z, x, y] = key.split('/').map((d) => parseInt(d)); let moves = 0; diff --git a/src/typing.ts b/src/typing.ts index 730b43b63..e029fc499 100644 --- a/src/typing.ts +++ b/src/typing.ts @@ -45,7 +45,7 @@ export function isCompleteManifest( 'min_ix', 'max_ix', 'extent', - ]) { + ] as const) { if (manifest[k] === undefined) { return false; }