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

perf(core): optimized mark #1039

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/core/mark/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export function drawMark(HGC: import('@higlass/types').HGC, trackInfo: any, tile
model.setChannelScale(d, yScale);
});
}

// Size of a track
const [trackWidth, trackHeight] = trackInfo.dimensions;

Expand Down
69 changes: 46 additions & 23 deletions src/core/mark/point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@
import colorToHex from '../utils/color-to-hex';
import { cartesianToPolar } from '../utils/polar';
import type { PIXIVisualProperty } from '../visual-property.schema';
import { uuid } from '../utils/uuid';

function calculateOpacity(
model: GoslingTrackModel,
datum: {
[k: string]: string | number;
},
radius: number,
zoomLevel: number
) {
const opacity = model.encodedPIXIProperty('opacity', datum);
const alphaTransition = model.markVisibility(datum, { width: radius, zoomLevel });
return Math.min(alphaTransition, opacity);
}

export function drawPoint(track: any, g: PIXI.Graphics, model: GoslingTrackModel) {
/* track spec */
Expand Down Expand Up @@ -36,7 +50,6 @@
/* row separation */
const rowCategories = (model.getChannelDomainArray('row') as string[]) ?? ['___SINGLE_ROW___'];
const rowHeight = trackHeight / rowCategories.length;

/* render */
rowCategories.forEach(rowCategory => {
const rowPosition = model.encodedValue('row', rowCategory);
Expand All @@ -46,45 +59,52 @@
!getValueUsingChannel(d, spec.row as Channel) ||
(getValueUsingChannel(d, spec.row as Channel) as string) === rowCategory
).forEach(d => {
// const cx = model.xScale(d.position);
const cx = model.encodedPIXIProperty('x-center', d);
const cy = model.encodedPIXIProperty('y-center', d);
const color = model.encodedPIXIProperty('color', d);
const radius = model.encodedPIXIProperty('p-size', d);
const strokeWidth = model.encodedPIXIProperty('strokeWidth', d);
const stroke = model.encodedPIXIProperty('stroke', d);
const opacity = model.encodedPIXIProperty('opacity', d);

const alphaTransition = model.markVisibility(d, { width: radius, zoomLevel });
const actualOpacity = Math.min(alphaTransition, opacity);
const cy = d.cy ?? model.encodedPIXIProperty('y-center', d);
const color = (d.color as number) ?? (colorToHex(model.encodedPIXIProperty('color', d)) as number);
const radius = d.radius ?? model.encodedPIXIProperty('p-size', d);
const strokeWidth = d.strokeWidth ?? model.encodedPIXIProperty('strokeWidth', d);
const strokeColor = d.stroke ?? colorToHex(model.encodedPIXIProperty('stroke', d));
const actualOpacity = d.opacity ?? calculateOpacity(model, d, radius, zoomLevel);

Check failure on line 69 in src/core/mark/point.ts

View workflow job for this annotation

GitHub Actions / Test (20.x)

Argument of type 'string | number' is not assignable to parameter of type 'number'.

if (!d.radius) {
d.cy = cy;
d.color = color;
d.radius = radius;
d.strokeWidth = strokeWidth;
d.stroke = strokeColor;
d.opacity = actualOpacity;
d.uuid = uuid();
}

if (radius <= 0.1 || actualOpacity === 0 || cx + radius < 0 || cx - radius > trackWidth) {

Check failure on line 81 in src/core/mark/point.ts

View workflow job for this annotation

GitHub Actions / Test (20.x)

Operator '<=' cannot be applied to types 'string | number' and 'number'.

Check failure on line 81 in src/core/mark/point.ts

View workflow job for this annotation

GitHub Actions / Test (20.x)

The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
// Don't draw invisible marks
return;
}

// stroke
g.lineStyle(
strokeWidth,

Check failure on line 88 in src/core/mark/point.ts

View workflow job for this annotation

GitHub Actions / Test (20.x)

Argument of type 'string | number' is not assignable to parameter of type 'number'.
colorToHex(stroke),
strokeColor,
actualOpacity, // alpha
1 // alignment of the line to draw, (0 = inner, 0.5 = middle, 1 = outter)
);

let pos: { x: number; y: number };
if (circular) {
const r = trackOuterRadius - ((rowPosition + rowHeight - cy) / trackHeight) * trackRingSize;

Check failure on line 96 in src/core/mark/point.ts

View workflow job for this annotation

GitHub Actions / Test (20.x)

The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
const pos = cartesianToPolar(cx, trackWidth, r, tcx, tcy, startAngle, endAngle);
g.beginFill(colorToHex(color), actualOpacity);
g.drawCircle(pos.x, pos.y, radius);

pos = cartesianToPolar(cx, trackWidth, r, tcx, tcy, startAngle, endAngle);
/* Mouse Events */
model.getMouseEventModel().addPointBasedEvent(d, [pos.x, pos.y, radius]);
} else {
g.beginFill(colorToHex(color), actualOpacity);
g.drawCircle(cx, rowPosition + rowHeight - cy, radius);

/* Mouse Events */
model.getMouseEventModel().addPointBasedEvent(d, [cx, rowPosition + rowHeight - cy, radius]);
pos = {
x: cx,
y: rowPosition + rowHeight - cy

Check failure on line 102 in src/core/mark/point.ts

View workflow job for this annotation

GitHub Actions / Test (20.x)

The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
};
}
g.beginFill(color, actualOpacity);

Check failure on line 105 in src/core/mark/point.ts

View workflow job for this annotation

GitHub Actions / Test (20.x)

Argument of type 'string | number' is not assignable to parameter of type 'number | undefined'.
g.drawCircle(pos.x, pos.y, radius);

Check failure on line 106 in src/core/mark/point.ts

View workflow job for this annotation

GitHub Actions / Test (20.x)

Argument of type 'string | number' is not assignable to parameter of type 'number'.
model.getMouseEventModel().addPointBasedEvent(d, [pos.x, pos.y, radius], d.uuid);

Check failure on line 107 in src/core/mark/point.ts

View workflow job for this annotation

GitHub Actions / Test (20.x)

Type 'string | number' is not assignable to type 'number'.
});
});
}
Expand All @@ -96,7 +116,8 @@
) {
const xe = model.visualPropertyByChannel('xe', datum);
const x = model.visualPropertyByChannel('x', datum);
const size = model.visualPropertyByChannel('size', datum);

// console.warn('pointProperty', propertyKey);

// priority of channels
switch (propertyKey) {
Expand All @@ -107,8 +128,10 @@
const y = model.visualPropertyByChannel('y', datum);
return ye ? (ye + y) / 2.0 : y;
}
case 'p-size':
case 'p-size': {
const size = model.visualPropertyByChannel('size', datum);
return xe && model.spec().stretch ? (xe - x) / 2.0 : size;
}
default:
return undefined;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ export class MouseEventModel {
/**
* Add a new mouse event that is point-based.
*/
public addPointBasedEvent(value: Datum, pointAndRadius: [number, number, number]) {
this.data.push({ uid: uuid(), type: 'point', value, polygon: pointAndRadius });
public addPointBasedEvent(value: Datum, pointAndRadius: [number, number, number], presetUID?: string) {
this.data.push({ uid: presetUID ?? uuid(), type: 'point', value, polygon: pointAndRadius });
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/tracks/gosling-track/gosling-track-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export class GoslingTrackModel {
/* mouse events */
private mouseEventModel: MouseEventModel;

public xScale: ScaleType | undefined;
public yScale: ScaleType | undefined;

constructor(spec: SingleTrack, data: { [k: string]: number | string }[], theme: Required<CompleteThemeDeep>) {
this.id = uuid();

Expand Down
27 changes: 19 additions & 8 deletions src/tracks/gosling-track/gosling-track.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
#loadingText = new HGC.libraries.PIXI.Text('', loadingTextStyle);
prevVisibleAndFetchedTiles?: Tile[];
resolvedTracks: SingleTrack[] | undefined;
// This is used to persist processed tile data across draw() calls.
#processedTileMap: WeakMap<Tile, boolean> = new WeakMap();

/* *
*
Expand Down Expand Up @@ -396,8 +398,6 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
drawMark(HGC, this, tile, model);
drawPostEmbellishment(HGC, this, tile, model, this.options.theme);
});

this.forceDraw();
}

/**
Expand All @@ -416,10 +416,12 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
this.getResolvedTracks(true); // force update
this.clearMouseEventData();
this.textsBeingUsed = 0;
// Without this, tracks with the same ID between specs will not be redrawn
this.#processedTileMap = new WeakMap();

this.processAllTiles(true);
this.draw();
this.forceDraw();
this.forceAnimate();
}
/**
* Clears MouseEventModel from each GoslingTrackModel. Must be a public method because it is called from draw()
Expand Down Expand Up @@ -475,7 +477,7 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
* A function to redraw this track. Typically called when an asynchronous event occurs (i.e. tiles loaded)
* (Refer to https://github.com/higlass/higlass/blob/54f5aae61d3474f9e868621228270f0c90ef9343/app/scripts/TiledPixiTrack.js#L71)
*/
forceDraw() {
forceAnimate() {
this.animate();
}

Expand All @@ -491,13 +493,12 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
this.mRangeBrush.updateRange(
range ? [newXScale(this._xScale.invert(range[0])), newXScale(this._xScale.invert(range[1]))] : null
);

console.warn(newXScale === this._xScale); // true
this.xScale(newXScale);
this.yScale(newYScale);

this.refreshTiles();
this.draw();
this.forceDraw();

// Publish the new genomic axis domain
const genomicRange = newXScale
Expand Down Expand Up @@ -556,6 +557,12 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op

const tiles = this.visibleAndFetchedTiles();

// If we have already processed all tiles, we don't need to do anything
// this.#processedTileMap contains all of data needed to draw
if (tiles.every(tile => this.#processedTileMap.get(tile) !== undefined)) {
return;
}

// generated tabular data
tiles.forEach(tile => this.#generateTabularData(tile, force));

Expand All @@ -571,6 +578,11 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
if (flatTileData.length !== 0) {
this.options.siblingIds.forEach(id => publish('rawData', { id, data: flatTileData }));
}

// Record processed tiles so that we don't process them again
tiles.forEach(tile => {
this.#processedTileMap.set(tile, true);
});
}

/**
Expand Down Expand Up @@ -609,7 +621,6 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op

for (const tile of tiles) {
const { tileWidth } = this.getTilePosAndDimensions(tile[0], [tile[1], tile[1]]);
this.forceDraw();
if (tileWidth > maxTileWith) {
return;
}
Expand Down Expand Up @@ -1250,7 +1261,7 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
});
}

this.forceDraw();
this.forceAnimate();
}

/**
Expand Down
Loading