diff --git a/editor/example/index.ts b/editor/example/index.ts index 742e128b..dd914725 100644 --- a/editor/example/index.ts +++ b/editor/example/index.ts @@ -36,6 +36,7 @@ export type ExampleGroup = | 'Coordinated Multiple Views' | 'Applications' | 'Track Templates' + | 'Experimental' | 'Doc' | 'Unassigned'; @@ -75,6 +76,10 @@ export const ExampleGroups: { name: 'Track Templates', description: 'Built-in track templates that allow creating common tracks, like ideograms and gene annotations.' }, + { + name: 'Experimental', + description: 'Examples that include experimental features, such as performance improvements.' + }, { name: 'Doc', description: 'Examples used in the official documentation.' @@ -412,6 +417,12 @@ export const editorExampleObj: { spec: JsonExampleSpecs.EX_SPEC_ALIGNMENT_CHART, image: THUMBNAILS.ALIGNMENT }, + PERF_ALIGNMENT: { + group: 'Experimental', + name: 'Performance Comparison: Stretching Tiles', + spec: JsonExampleSpecs.EX_SPEC_PERF_ALIGNMENT, + image: THUMBNAILS.PERF_ALIGNMENT + }, CORCES_ET_AL: { group: 'Coordinated Multiple Views', name: 'Corces et al. 2020', diff --git a/editor/example/json-spec/index.ts b/editor/example/json-spec/index.ts index 835c69e0..3fd03232 100644 --- a/editor/example/json-spec/index.ts +++ b/editor/example/json-spec/index.ts @@ -24,6 +24,7 @@ import { EX_SPEC_CYTOBANDS } from './ideograms'; import { EX_SPEC_PILEUP } from './pileup'; import { EX_SPEC_TEMPLATE } from './track-template'; import { EX_SPEC_MOUSE_EVENT } from './mouse-event'; +import { EX_SPEC_PERF_ALIGNMENT } from './perf-alignment' import { EX_SPEC_DEBUG } from './debug'; export const JsonExampleSpecs = { @@ -42,6 +43,7 @@ export const JsonExampleSpecs = { EX_SPEC_RESPONSIVE_TRACK_WISE_COMPARISON, EX_SPEC_ALIGNMENT_CHART, EX_SPEC_RESPONSIVE_ALIGNMENT_CHART, + EX_SPEC_PERF_ALIGNMENT, EX_SPEC_MARK_DISPLACEMENT, EX_SPEC_CIRCULAR_OVERVIEW_LINEAR_DETAIL, EX_SPEC_SARS_COV_2, diff --git a/editor/example/json-spec/perf-alignment.ts b/editor/example/json-spec/perf-alignment.ts new file mode 100644 index 00000000..a689a3c0 --- /dev/null +++ b/editor/example/json-spec/perf-alignment.ts @@ -0,0 +1,30 @@ +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; +import { alignmentWithText } from './responsive-alignment'; + +const commonProps = { width: 800, height: 400, xAxis: false, rowLegend: false, colorLegend: false }; +export const EX_SPEC_PERF_ALIGNMENT: GoslingSpec = { + zoomLimits: [1, 396], + xDomain: { interval: [350, 396] }, + assembly: 'unknown', + title: 'Smoother Zoom', + subtitle: 'Rather than redrawing every element at every frame, we can scale existing elements', + views: [ + { + tracks: [ + { + ...alignmentWithText(commonProps), + title: 'New Approach: Stretching Tiles', + experimental: { stretchGraphics: true } + } + ] + }, + { + tracks: [ + { + ...alignmentWithText(commonProps), + title: 'Original Approach' + } + ] + } + ] +}; diff --git a/editor/example/thumbnails.ts b/editor/example/thumbnails.ts index 29e6e3bc..05d499d3 100644 --- a/editor/example/thumbnails.ts +++ b/editor/example/thumbnails.ts @@ -15,6 +15,7 @@ import MARK_DISPLACEMENT from './thumbnails/MARK_DISPLACEMENT.png'; import MATRIX_HFFC6 from './thumbnails/MATRIX_HFFC6.gif'; import MATRIX from './thumbnails/MATRIX.png'; import MOUSE_EVENT from './thumbnails/MOUSE_EVENT.png'; +import PERF_ALIGNMENT from './thumbnails/PERF_ALIGNMENT.png'; import RESPONSIVE_COMPARATIVE_MATRICES from './thumbnails/RESPONSIVE_COMPARATIVE_MATRICES.gif'; import RESPONSIVE_IDEOGRAM from './thumbnails/RESPONSIVE_IDEOGRAM.gif'; import RESPONSIVE_MULTIVEC from './thumbnails/RESPONSIVE_MULTIVEC.gif'; @@ -48,6 +49,7 @@ export const THUMBNAILS = { MATRIX_HFFC6, MATRIX, MOUSE_EVENT, + PERF_ALIGNMENT, RESPONSIVE_COMPARATIVE_MATRICES, RESPONSIVE_IDEOGRAM, RESPONSIVE_MULTIVEC, diff --git a/editor/example/thumbnails/PERF_ALIGNMENT.png b/editor/example/thumbnails/PERF_ALIGNMENT.png new file mode 100644 index 00000000..4403b62a Binary files /dev/null and b/editor/example/thumbnails/PERF_ALIGNMENT.png differ diff --git a/src/gosling-schema/gosling.schema.json b/src/gosling-schema/gosling.schema.json index e1bbed90..4a1c1eba 100644 --- a/src/gosling-schema/gosling.schema.json +++ b/src/gosling-schema/gosling.schema.json @@ -1272,6 +1272,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -1424,6 +1433,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -2040,6 +2058,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -2192,6 +2219,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -2865,6 +2901,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -4106,6 +4151,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -4424,6 +4478,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -4780,6 +4843,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -5154,6 +5226,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -5488,6 +5569,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -6033,6 +6123,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -6392,6 +6491,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -6540,6 +6648,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -7153,6 +7270,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -7301,6 +7427,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" @@ -7967,6 +8102,15 @@ "default": false, "description": "Render visual marks with less smooth curves to increase rendering performance. Only supported for `elliptical` `linkStyle` `withinLink` currently.", "type": "boolean" + }, + "stretchGraphics": { + "description": "Performance rendering option. By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve rendering performance. No marks will be stretched in circular layouts.\n\nWhen this option is set to true, all marks will be stretched when zooming in/out. When this option is set to false, all marks will be rerendered when zooming in/out.", + "type": "boolean" + }, + "stretchGraphicsThreshold": { + "default": 1.5, + "description": "Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will be rerendered. This is to prevent the graphics from being stretched too much.", + "type": "number" } }, "type": "object" diff --git a/src/gosling-schema/gosling.schema.ts b/src/gosling-schema/gosling.schema.ts index ebd4be87..d36ff29b 100644 --- a/src/gosling-schema/gosling.schema.ts +++ b/src/gosling-schema/gosling.schema.ts @@ -431,6 +431,25 @@ interface SingleTrackBase extends CommonTrackDef { * @default false */ performanceMode?: boolean; + + /** + * Performance rendering option. + * By default, certain marks ('bar', 'line', 'rect', 'area') are stretched when zooming in/out to improve + * rendering performance. No marks will be stretched in circular layouts. + * + * When this option is set to true, all marks will be stretched when zooming in/out. + * When this option is set to false, all marks will be rerendered when zooming in/out. + * + */ + stretchGraphics?: boolean; + + /** + * Threshold for stretching graphics. If the graphics are scaled larger than the threshold, then the graphic + * will be rerendered. If the graphics are scaled smaller than 1/threshold (e.g., 1/2), then the graphic will + * be rerendered. This is to prevent the graphics from being stretched too much. + * @default 1.5 + */ + stretchGraphicsThreshold?: number; }; // Mark diff --git a/src/tracks/gosling-track/gosling-track.ts b/src/tracks/gosling-track/gosling-track.ts index aebdf343..1a1549f7 100644 --- a/src/tracks/gosling-track/gosling-track.ts +++ b/src/tracks/gosling-track/gosling-track.ts @@ -333,14 +333,44 @@ const factory: PluginTrackFactory = (HGC, context, op override drawTile(tile: Tile) { if (PRINT_RENDERING_CYCLE) console.warn('drawTile(tile)'); - tile.drawnAtScale = this._xScale.copy(); // being used in `super.draw()` - + /** + * If we don't have info about the tile, we can't draw anything. + */ const tileInfo = this.#processedTileInfo[tile.tileId]; if (!tileInfo) { // We do not have a track model prepared to visualize return; } + /** + * Add a copy of the track scale to the tile. The tile needs its own scale because we will use it to + * determine how much the tile has been stretched (if we are stretching the graphics) + */ + if (!tile.drawnAtScale) { + // This is the first time this tile is being drawn + tile.drawnAtScale = this._xScale.copy(); + } + + /** + * For certain types of marks and layouts (linear), we can stretch the graphics to avoid redrawing + * This is much more performant than redrawing everything at every frame + */ + const [graphicsXScale, graphicsXPos] = this.getXScaleAndOffset(tile.drawnAtScale); + const isFirstRender = graphicsXScale === 1; // The graphicsXScale is 1 if first time the tile is being drawn + if (!this.#isTooStretched(graphicsXScale) && this.#hasStretchableGraphics() && !isFirstRender) { + // Stretch the graphics + tile.graphics.scale.x = graphicsXScale; + tile.graphics.position.x = graphicsXPos; + return; + } + + /** + * If we can't stretch the graphics, we need to redraw everything! + */ + + // We need the tile scale to match the scale of the track + tile.drawnAtScale = this._xScale.copy(); + // Clear the graphics and redraw everything tile.graphics?.clear(); tile.graphics?.removeChildren(); @@ -1472,6 +1502,43 @@ const factory: PluginTrackFactory = (HGC, context, op } }); } + + /** + * Used in drawTile() + * Checks if the track has marks which are stretchable. Stretching + * is not supported for circular layouts or 2D tracks + */ + #hasStretchableGraphics() { + const hasStretchOption = this.options.spec.experimental?.stretchGraphics; + if (hasStretchOption === true) { + return true; + } else if (hasStretchOption === false) { + return false; + } + // The default behavior is that we stretch when stretching looks acceptable + const isFirstTrack1D = !Is2DTrack(this.getResolvedTracks()[0]); + const isNotCircularLayout = this.options.spec.layout !== 'circular'; + const stretchableMarks = ['bar', 'line', 'rect', 'area']; + const hasStretchableMark = this.getResolvedTracks().reduce( + (acc, spec) => acc && stretchableMarks.includes(spec.mark), + true + ); + const noMouseInteractions = !this.options.spec.experimental?.mouseEvents; + + return isFirstTrack1D && isNotCircularLayout && hasStretchableMark && noMouseInteractions; + } + + /** + * 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 + */ + #isTooStretched(stretchFactor: number) { + const defaultThreshold = 1.5; + const threshold = this.options.spec.experimental?.stretchGraphicsThreshold ?? defaultThreshold; + return stretchFactor > threshold || stretchFactor < 1 / threshold; + } } return new GoslingTrackClass(); };