Skip to content

Commit

Permalink
refactor(track): Make GoslingTrack extend TiledPixiTrack (#1054)
Browse files Browse the repository at this point in the history
* feat: extend HorizontalTiled1DPixiTrack

* feat: extend Tiled1DPixiTrack

* fix: drawnAtScale, redundant calls

* fix: remove relevantScale

* fix: comments
  • Loading branch information
etowahadams committed Apr 25, 2024
1 parent 15d3e85 commit f52ef3a
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 32 deletions.
7 changes: 4 additions & 3 deletions src/missing-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ declare module '@higlass/tracks' {
import type { TilesetInfo, ColorRGBA } from '@higlass/services';
import type { ChromInfo } from '@higlass/utils';

type Scale = d3.ScaleContinuousNumeric<number, number>;
export type Scale = d3.ScaleContinuousNumeric<number, number>;

type Handler = (data: any) => void;

Expand Down Expand Up @@ -194,6 +194,7 @@ declare module '@higlass/tracks' {
dimensions: [number, number];
options: Options;
pubSubs: Subscription[];
isLeftModified: boolean;
/* Constructor */
constructor(props: { id: string; pubSub: PubSub; getTheme?: () => string });
/* Methods */
Expand Down Expand Up @@ -315,7 +316,7 @@ declare module '@higlass/tracks' {
listeners: Record<string, unknown>;

pubSub: Pick<Context<TileData, Options>, 'pubSub'>;
animate: Pick<Context<TileData, Options>, 'animate'>;
animate: Context<TileData, Options>['animate'];
onValueScaleChanged: Pick<Context<TileData, Options>, 'onValueScaleChanged'>;

// store the server and tileset uid so they can be used in draw()
Expand Down Expand Up @@ -430,7 +431,7 @@ declare module '@higlass/tracks' {
}

export abstract class Tiled1DPixiTrack<TileData, Options> extends TiledPixiTrack<TileData, Options> {
onMouseMoveZoom: Pick<Context<TileData, Options>, 'onMouseMoveZoom'>;
onMouseMoveZoom: Context<TileData, Options>['onMouseMoveZoom'];
isValueScaleLocked: Pick<Context<TileData, Options>, 'isValueScaleLocked'>;
getLockGroupExtrema: Pick<Context<TileData, Options>, 'getLockGroupExtrema'>;
initTile(tile: TileData): void;
Expand Down
117 changes: 88 additions & 29 deletions src/tracks/gosling-track/gosling-track.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { HIGLASS_AXIS_SIZE } from '../../compiler/higlass-model';
import { flatArrayToPairArray } from '../../core/utils/array';
import { createPluginTrack, type PluginTrackFactory, type TrackConfig } from '../../core/utils/define-plugin-track';
import { uuid } from '../../core/utils/uuid';
import type { Scale, TilePosition } from '@higlass/tracks';

// Set `true` to print in what order each function is called
export const PRINT_RENDERING_CYCLE = false;
Expand Down Expand Up @@ -135,7 +136,7 @@ const config: TrackConfig<GoslingTrackOptions> = {
const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, options) => {
// Services
const { tileProxy } = HGC.services;
const { BarTrack } = HGC.tracks;
const { TiledPixiTrack } = HGC.tracks;

/* Custom loading label */
const loadingTextStyle = getTextStyle({ color: 'black', size: 12 });
Expand All @@ -144,7 +145,7 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
* The main plugin track in Gosling. This is a versetile plugin track for HiGlass which relies on GoslingTrackModel
* to keep track of mouse event and channel scales.
*/
class GoslingTrackClass extends BarTrack<Tile, typeof options> {
class GoslingTrackClass extends TiledPixiTrack<Tile, typeof options> {
/* *
*
* Properties
Expand Down Expand Up @@ -185,6 +186,7 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op

constructor() {
super(context, options);
const { isShowGlobalMousePosition } = context;

context.dataFetcher.track = this;
this.#processedTileInfo = {};
Expand Down Expand Up @@ -232,17 +234,13 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
this.pMask.on('mouseout', this.#onMouseOut.bind(this));
this.flipText = this.options.spec.orientation === 'vertical';

// Remove a mouse graphic if created by a parent, and draw ourselves.
// Draw the mouse position
// See https://github.com/higlass/higlass/blob/38f0c4415f0595c3b9d685a754d6661dc9612f7c/app/scripts/utils/show-mouse-position.js#L28
if (this.hideMousePosition) {
this.hideMousePosition();
this.hideMousePosition = undefined;
}
if (this.options?.showMousePosition && !this.hideMousePosition) {
this.hideMousePosition = HGC.utils.showMousePosition(
this,
Is2DTrack(this.getResolvedTracks()[0]),
this.isShowGlobalMousePosition()
isShowGlobalMousePosition()
);
}

Expand Down Expand Up @@ -291,6 +289,12 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
// This function calls `drawTile` on each tile.
super.draw();

// From BarTrack
Object.values(this.fetchedTiles).forEach(tile => {
if (!tile.drawnAtScale) return;
[tile.graphics.scale.x, tile.graphics.position.x] = this.getXScaleAndOffset(tile.drawnAtScale);
});

// Record tiles so that we ignore loading same tiles again
this.prevVisibleAndFetchedTiles = this.visibleAndFetchedTiles();
};
Expand All @@ -313,6 +317,20 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
}
}

/**
* Copied from BarTrack
*/
getXScaleAndOffset(drawnAtScale: Scale) {
const dA = drawnAtScale.domain();
const dB = this._xScale.domain();

// scaling between tiles
const tileK = (dA[1] - dA[0]) / (dB[1] - dB[0]);
const newRange = this._xScale.domain().map(drawnAtScale);
const posOffset = newRange[0];
return [tileK, -posOffset * tileK];
}

/*
* Do whatever is necessary before rendering a new tile. This function is called from `receivedTiles()`.
* Overrides initTile in BarTrack
Expand All @@ -326,7 +344,7 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
}

override updateTile(/* tile: Tile */) {} // Never mind about this function for the simplicity.
override renderTile(/* tile: Tile */) {} // Never mind about this function for the simplicity.
renderTile(/* tile: Tile */) {} // Never mind about this function for the simplicity.

/**
* Display a tile upon receiving a new one or when explicitly called by a developer, e.g., calling
Expand Down Expand Up @@ -471,6 +489,7 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
super.setPosition(newPosition); // This simply changes `this.position`

[this.pMain.position.x, this.pMain.position.y] = this.position;
[this.pMouseOver.position.x, this.pMouseOver.position.y] = this.position;

this.mRangeBrush.setOffset(...newPosition);
}
Expand Down Expand Up @@ -611,10 +630,10 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
}

/**
* Overrides method in Tiled1DPixiTrack. It is called in the constructor, `super(context, options)`.
* This method is called in the TiledPixiTrack constructor `super(context, options)`.
* So be aware to use defined variables.
*/
override calculateVisibleTiles() {
calculateVisibleTiles() {
if (!this.tilesetInfo) return;
if (isTabularDataFetcher(this.dataFetcher)) {
const tiles = HGC.utils.trackUtils.calculate1DVisibleTiles(this.tilesetInfo, this._xScale);
Expand Down Expand Up @@ -668,7 +687,7 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
} else {
const xTiles = tileProxy.calculateTiles(
zoomLevel,
this.relevantScale(),
this._xScale,
this.tilesetInfo.min_pos[0],
this.tilesetInfo.max_pos[0],
this.tilesetInfo.max_zoom,
Expand All @@ -694,6 +713,37 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
}
}
}
/**
* Copied from HorizontalTiled1DPixiTrack
*/
calculateZoomLevel() {
if (!this.tilesetInfo) {
throw Error('tilesetInfo not parsed');
}
// offset by 2 because 1D tiles are more dense than 2D tiles
// 1024 points per tile vs 256 for 2D tiles
if ('resolutions' in this.tilesetInfo) {
const zoomIndexX = tileProxy.calculateZoomLevelFromResolutions(
this.tilesetInfo.resolutions,
this._xScale
);

return zoomIndexX;
}
// the tileProxy calculateZoomLevel function only cares about the
// difference between the minimum and maximum position
const xZoomLevel = tileProxy.calculateZoomLevel(
this._xScale,
this.tilesetInfo.min_pos[0],
this.tilesetInfo.max_pos[0],
this.tilesetInfo.bins_per_dimension || this.tilesetInfo.tile_size
);

let zoomLevel = Math.min(xZoomLevel, this.maxZoom);
zoomLevel = Math.max(zoomLevel, 0);

return zoomLevel;
}
/**
* Convert tile positions to tile IDs
*/
Expand All @@ -711,11 +761,10 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
xTiles.forEach(x => yTiles.forEach(y => tiles.push([zoomLevel, x, y])));
return tiles;
}

/**
* Get the tile's position in its coordinate system. Overrides method in Tiled1DPixiTrack.
* Get the tile's position in its coordinate system. Based on method in Tiled1DPixiTrack
*/
override getTilePosAndDimensions(zoomLevel: number, tilePos: [number, number]) {
getTilePosAndDimensions(zoomLevel: number, tilePos: [number, number]) {
if (!this.tilesetInfo) {
throw Error('tilesetInfo not parsed');
}
Expand Down Expand Up @@ -773,11 +822,10 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
}
return maybeValue ?? 256;
}

/**
* Gets the indices of the visible data a tile. Overrides method in Tiled1DPixiTrack
* Gets the indices of the visible data a tile. Based on method in Tiled1DPixiTrack
*/
override getIndicesOfVisibleDataInTile(tile: Tile): [number, number] {
getIndicesOfVisibleDataInTile(tile: Tile): [number, number] {
const visible = this._xScale.range();

if (!this.tilesetInfo || !tile.tileData.tilePos || !('dense' in tile.tileData)) {
Expand All @@ -802,7 +850,6 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op

/**
* Overrides method in TiledPixiTrack
* @param loadedTiles
*/
override receivedTiles(loadedTiles: Record<string, Tile>) {
// https://github.com/higlass/higlass/blob/38f0c4415f0595c3b9d685a754d6661dc9612f7c/app/scripts/TiledPixiTrack.js#L637
Expand Down Expand Up @@ -873,6 +920,18 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
return includesDisplaceTransform && !hasDenseTiles() && !isBamDataFetcher;
}

/**
* Copied from Tiled1DPixiTrack. The ID of the local tile
*/
tileToLocalId(tile: TilePosition) {
return `${tile.join('.')}`;
}
/**
* Copied from Tiled1DPixiTrack. The ID of the tile on the server.
*/
tileToRemoteId(tile: TilePosition) {
return `${tile.join('.')}`;
}
/**
* Creates an array of SingleTracks if there are overlaid tracks.
* This method cannot be private because it is called by functions which are called by super.draw();
Expand Down Expand Up @@ -1053,6 +1112,13 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
*
* */

/**
* This is for the HiGlass mouseMoveZoom event. However, GoslingTrack has its own way of handling mouse events.
*/
mouseMoveZoomHandler() {
return;
}

#onMouseDown(mouseX: number, mouseY: number, isAltPressed: boolean) {
// Record these so that we do not triger click event when dragged.
this.#mouseDownX = mouseX;
Expand Down Expand Up @@ -1129,7 +1195,6 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
document.body.style.cursor = 'default';
this.pMouseHover.clear();
}

/**
* From all tiles and overlaid tracks, collect element(s) that are withing a mouse position.
*/
Expand Down Expand Up @@ -1305,9 +1370,9 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
hideMousePosition?: () => void; // set in HorizontalTiled1DPixiTrack

/**
* Overrides method in HorizontalLine1DPixiTrack
* Called by showHoverMenu() in HiGlassComponent
*/
override getMouseOverHtml(mouseX: number, mouseY: number) {
getMouseOverHtml(mouseX: number, mouseY: number) {
// `trackMouseOver` API
this.#publishTrackEvents('trackMouseOver', mouseX, mouseY);

Expand Down Expand Up @@ -1446,7 +1511,6 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
override exportSVG(): never {
throw new Error('exportSVG() not supported for gosling-track');
}

/**
* Show visual cue during waiting for visualizations being rendered. Also called by data fetchers
*/
Expand Down Expand Up @@ -1480,7 +1544,6 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
this.#loadingTextBg.visible = false;
}
}

/**
* Called in legend.ts
*/
Expand All @@ -1498,7 +1561,6 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
}
});
}

/**
* Called in legend.ts
*/
Expand All @@ -1515,7 +1577,6 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op
}
});
}

/**
* Used in drawTile()
* Checks if the track has marks which are stretchable. Stretching
Expand All @@ -1540,10 +1601,8 @@ const factory: PluginTrackFactory<Tile, GoslingTrackOptions> = (HGC, context, op

return isFirstTrack1D && isNotCircularLayout && hasStretchableMark && noMouseInteractions;
}

/**
* Used in drawTile()
* Checks if the tile Graphic is too stretched. If so, it returns true.
* Used in drawTile(). Checks if the tile Graphic is too stretched. If so, it returns true.
* @param stretchFactor The factor by which the tile is stretched
* @returns True if the tile is too stretched, false otherwise
*/
Expand Down

0 comments on commit f52ef3a

Please sign in to comment.