From b6e22fee05e26937cb2bb0749de83def60c33bf3 Mon Sep 17 00:00:00 2001 From: Natan Muntean <33986780+munteannatan@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:39:02 +0300 Subject: [PATCH] Created a new Experimental Prerendering module (#1955) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Pull Request ## ๐Ÿคจ Rationale Created a new experimental prerendering module. The previous prerendering oversaw calculating the `fillstyle` of each die and it's label. We want to move these expensive operations to the web worker, and to make them faster. ## ๐Ÿ‘ฉโ€๐Ÿ’ป Implementation I removed the code which prepared the dies for rendering. I created a domain splitter for linear color scales. this will reduce the number of colors displayed but will increase the performance because we are no longer finding interpolations for each individual value in the whole domain. Open Issues: - given the problems with arquero, should we implement in-house highlight or leave it unimplemented until the issue gets fixed? ## ๐Ÿงช Testing ## โœ… Checklist - [x] I have updated the project documentation to reflect my changes or determined no changes are needed. --------- Co-authored-by: Milan Raj --- ...-ba1be791-36da-4c55-888b-5df015121520.json | 7 + .../nimble-components/src/wafer-map/index.ts | 67 +++-- .../modules/experimental/computations.ts | 6 +- .../modules/experimental/data-manager.ts | 50 +--- .../modules/experimental/hover-handler.ts | 24 +- .../modules/experimental/prerendering.ts | 86 ++++++ .../modules/experimental/worker-renderer.ts | 17 +- .../src/wafer-map/modules/hover-handler.ts | 9 +- .../src/wafer-map/tests/data-manager.spec.ts | 2 +- .../tests/experimantal-prerendering.spec.ts | 278 ++++++++++++++++++ .../tests/experimental-computations.spec.ts | 6 +- .../tests/experimental-data-manager.spec.ts | 83 +----- .../src/wafer-map/tests/utilities.ts | 80 ++++- .../src/wafer-map/tests/wafer-map.spec.ts | 19 +- .../nimble-components/src/wafer-map/types.ts | 7 + 15 files changed, 542 insertions(+), 199 deletions(-) create mode 100644 change/@ni-nimble-components-ba1be791-36da-4c55-888b-5df015121520.json create mode 100644 packages/nimble-components/src/wafer-map/modules/experimental/prerendering.ts create mode 100644 packages/nimble-components/src/wafer-map/tests/experimantal-prerendering.spec.ts diff --git a/change/@ni-nimble-components-ba1be791-36da-4c55-888b-5df015121520.json b/change/@ni-nimble-components-ba1be791-36da-4c55-888b-5df015121520.json new file mode 100644 index 0000000000..0802f3f6d4 --- /dev/null +++ b/change/@ni-nimble-components-ba1be791-36da-4c55-888b-5df015121520.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Created new Experimental Prerendering Module and tests", + "packageName": "@ni/nimble-components", + "email": "33986780+munteannatan@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/nimble-components/src/wafer-map/index.ts b/packages/nimble-components/src/wafer-map/index.ts index 4598885dbf..ca3b9e97ac 100644 --- a/packages/nimble-components/src/wafer-map/index.ts +++ b/packages/nimble-components/src/wafer-map/index.ts @@ -92,36 +92,20 @@ export class WaferMap< */ public readonly zoomContainer!: HTMLElement; - /** - * @internal - */ - public readonly stableDataManager: DataManager = new DataManager( - this.asRequiredFieldsWaferMap - ); - - /** - * @internal - */ public readonly experimentalDataManager: ExperimentalDataManager = new ExperimentalDataManager(this.asRequiredFieldsWaferMap); - public dataManager: DataManager | ExperimentalDataManager = this.stableDataManager; - - /** - * @internal - */ - public readonly mainRenderer = new RenderingModule( + public dataManager: DataManager = new DataManager( this.asRequiredFieldsWaferMap ); - /** - * @internal - */ public readonly workerRenderer = new WorkerRenderer( this.asRequiredFieldsWaferMap ); @observable - public renderer: RenderingModule | WorkerRenderer = this.mainRenderer; + public renderer: RenderingModule = new RenderingModule( + this.asRequiredFieldsWaferMap + ); /** * @internal @@ -214,6 +198,38 @@ export class WaferMap< this.resizeObserver.unobserve(this); } + /** + * @internal + * Experimental update function called when an update is queued. + */ + public experimentalUpdate(): void { + if (this.validity.invalidDiesTableSchema) { + return; + } + if (this.waferMapUpdateTracker.requiresEventsUpdate) { + // zoom translateExtent needs to be recalculated when canvas size changes + this.zoomHandler.disconnect(); + if ( + this.waferMapUpdateTracker.requiresContainerDimensionsUpdate + || this.waferMapUpdateTracker.requiresScalesUpdate + ) { + this.experimentalDataManager.updateComputations(); + this.workerRenderer.drawWafer(); + } else if ( + this.waferMapUpdateTracker.requiresLabelsFontSizeUpdate + || this.waferMapUpdateTracker.requiresDiesRenderInfoUpdate + ) { + this.experimentalDataManager.updatePrerendering(); + this.workerRenderer.drawWafer(); + } else if (this.waferMapUpdateTracker.requiresDrawnWaferUpdate) { + this.workerRenderer.drawWafer(); + } + this.zoomHandler.connect(); + } else if (this.waferMapUpdateTracker.requiresRenderHoverUpdate) { + this.workerRenderer.renderHover(); + } + } + /** * @internal * Update function called when an update is queued. @@ -224,18 +240,13 @@ export class WaferMap< */ public update(): void { this.validate(); - if (this.validity.invalidDiesTableSchema) { + if (this.isExperimentalUpdate()) { + this.experimentalUpdate(); return; } - this.renderer = this.isExperimentalRenderer() - ? this.workerRenderer - : this.mainRenderer; if (this.waferMapUpdateTracker.requiresEventsUpdate) { // zoom translateExtent needs to be recalculated when canvas size changes this.zoomHandler.disconnect(); - this.dataManager = this.isExperimentalRenderer() - ? this.experimentalDataManager - : this.stableDataManager; if (this.waferMapUpdateTracker.requiresContainerDimensionsUpdate) { this.dataManager.updateContainerDimensions(); this.renderer.updateSortedDiesAndDrawWafer(); @@ -264,7 +275,7 @@ export class WaferMap< /** * @internal */ - public isExperimentalRenderer(): boolean { + public isExperimentalUpdate(): boolean { return this.diesTable !== undefined; } diff --git a/packages/nimble-components/src/wafer-map/modules/experimental/computations.ts b/packages/nimble-components/src/wafer-map/modules/experimental/computations.ts index 340c41da86..b141bba94d 100644 --- a/packages/nimble-components/src/wafer-map/modules/experimental/computations.ts +++ b/packages/nimble-components/src/wafer-map/modules/experimental/computations.ts @@ -45,7 +45,7 @@ export class Computations { public constructor(private readonly wafermap: WaferMap) {} - public updateContainerDimensions(): void { + public update(): void { const canvasDimensions = { width: this.wafermap.canvasWidth, height: this.wafermap.canvasHeight @@ -71,10 +71,6 @@ export class Computations { canvasDimensions, this._margin ); - this.updateScales(); - } - - public updateScales(): void { const containerDiameter = Math.min( this._containerDimensions.width, this._containerDimensions.height diff --git a/packages/nimble-components/src/wafer-map/modules/experimental/data-manager.ts b/packages/nimble-components/src/wafer-map/modules/experimental/data-manager.ts index b9ec12f93c..8bb9b0cd3e 100644 --- a/packages/nimble-components/src/wafer-map/modules/experimental/data-manager.ts +++ b/packages/nimble-components/src/wafer-map/modules/experimental/data-manager.ts @@ -1,14 +1,8 @@ import type { ScaleLinear } from 'd3-scale'; import { Computations } from './computations'; -import { Prerendering } from '../prerendering'; +import { Prerendering } from './prerendering'; import type { WaferMap } from '../..'; -import type { - Dimensions, - Margin, - DieRenderInfo, - WaferMapDie, - PointCoordinates -} from '../../types'; +import type { ColorScale, Dimensions, Margin } from '../../types'; /** * Data Manager uses Computations and Prerendering modules in order and exposes the results @@ -38,50 +32,24 @@ export class DataManager { return this.prerendering.labelsFontSize; } - public get diesRenderInfo(): DieRenderInfo[] { - return this.prerendering.diesRenderInfo; - } - - public get data(): Map { - return this.dataMap; + public get colorScale(): ColorScale { + return this.prerendering.colorScale; } private readonly computations: Computations; private readonly prerendering: Prerendering; - private dataMap!: Map; public constructor(private readonly wafermap: WaferMap) { this.computations = new Computations(wafermap); this.prerendering = new Prerendering(wafermap); } - public updateContainerDimensions(): void { - this.computations.updateContainerDimensions(); - this.updateDataMap(); - this.updateLabelsFontSize(); - } - - public updateScales(): void { - this.computations.updateScales(); - this.updateDataMap(); - this.updateLabelsFontSize(); - } - - public updateLabelsFontSize(): void { - this.prerendering.updateLabelsFontSize(); - } - - public updateDiesRenderInfo(): void { - this.prerendering.updateDiesRenderInfo(); - } - - public getWaferMapDie(point: PointCoordinates): WaferMapDie | undefined { - return this.dataMap.get(`${point.x}_${point.y}`); + public updateComputations(): void { + this.computations.update(); + this.prerendering.update(); } - private updateDataMap(): void { - this.dataMap = new Map( - this.wafermap.dies.map(die => [`${die.x}_${die.y}`, die]) - ); + public updatePrerendering(): void { + this.prerendering.update(); } } diff --git a/packages/nimble-components/src/wafer-map/modules/experimental/hover-handler.ts b/packages/nimble-components/src/wafer-map/modules/experimental/hover-handler.ts index b3da77e01f..402c45a372 100644 --- a/packages/nimble-components/src/wafer-map/modules/experimental/hover-handler.ts +++ b/packages/nimble-components/src/wafer-map/modules/experimental/hover-handler.ts @@ -1,6 +1,5 @@ import type { WaferMap } from '../..'; import { PointCoordinates, WaferMapOriginLocation } from '../../types'; -import { DataManager } from './data-manager'; /** * HoverHandler deals with user interactions and events like hovering @@ -24,12 +23,8 @@ export class HoverHandler { this.wafermap.removeEventListener('mouseout', this.onMouseOut); } - /** - * @internal - * keep public for testing until data manager refactor - */ - public readonly onMouseMove = (event: MouseEvent): void => { - if (!this.wafermap.isExperimentalRenderer()) { + private readonly onMouseMove = (event: MouseEvent): void => { + if (!this.wafermap.isExperimentalUpdate()) { return; } // get original mouse position in case we are in zoom. @@ -76,10 +71,7 @@ export class HoverHandler { private calculateDieCoordinates( mousePosition: PointCoordinates ): PointCoordinates | undefined { - if ( - this.wafermap.isExperimentalRenderer() - && this.wafermap.dataManager instanceof DataManager - ) { + if (this.wafermap.isExperimentalUpdate()) { const originLocation = this.wafermap.originLocation; const xRoundFunction = originLocation === WaferMapOriginLocation.bottomLeft || originLocation === WaferMapOriginLocation.topLeft @@ -91,13 +83,15 @@ export class HoverHandler { : Math.floor; // go to x and y scale to get the x,y values of the die. const x = xRoundFunction( - this.wafermap.dataManager.horizontalScale.invert( - mousePosition.x - this.wafermap.dataManager.margin.left + this.wafermap.experimentalDataManager.horizontalScale.invert( + mousePosition.x + - this.wafermap.experimentalDataManager.margin.left ) ); const y = yRoundFunction( - this.wafermap.dataManager.verticalScale.invert( - mousePosition.y - this.wafermap.dataManager.margin.top + this.wafermap.experimentalDataManager.verticalScale.invert( + mousePosition.y + - this.wafermap.experimentalDataManager.margin.top ) ); return { x, y }; diff --git a/packages/nimble-components/src/wafer-map/modules/experimental/prerendering.ts b/packages/nimble-components/src/wafer-map/modules/experimental/prerendering.ts new file mode 100644 index 0000000000..b7b324f2f7 --- /dev/null +++ b/packages/nimble-components/src/wafer-map/modules/experimental/prerendering.ts @@ -0,0 +1,86 @@ +import { scaleLinear } from 'd3-scale'; +import { ticks } from 'd3-array'; +import { WaferMapColorScaleMode } from '../../types'; +import type { ColorScale, Dimensions } from '../../types'; +import type { WaferMap } from '../..'; + +/** + * Prerendering prepares render-ready dies data to be used by the rendering module + */ +export class Prerendering { + public get labelsFontSize(): number { + return this._labelsFontSize; + } + + public get colorScale(): ColorScale { + return this._colorScale; + } + + private _colorScale!: ColorScale; + + private _labelsFontSize!: number; + + private readonly fontSizeFactor = 0.8; + private readonly colorScaleResolution = 10; + + public constructor(private readonly wafermap: WaferMap) {} + + public update(): void { + this._labelsFontSize = this.calculateLabelsFontSize( + this.wafermap.experimentalDataManager.dieDimensions, + this.wafermap.maxCharacters + ); + this._colorScale = this.calculateColorScale(); + } + + private calculateColorScale(): ColorScale { + if (this.wafermap.colorScaleMode === WaferMapColorScaleMode.linear) { + const values = this.wafermap.colorScale.values.map(item => +item); + const d3ColorScale = scaleLinear() + .domain(values) + .range(this.wafermap.colorScale.colors); + let min = values[0]!; + let max = values[0]!; + values.forEach(value => { + if (value < min) { + min = value; + } + if (value > max) { + max = value; + } + }); + // the linear color scale will not be infinite but will be limited by the color scale resolution + const valueSamples = ticks( + min, + max, + values.length * this.colorScaleResolution + ); + return valueSamples.map(value => { + return { + color: d3ColorScale(value), + value + }; + }); + } + // ordinal color categories have to be sorted by value + return this.wafermap.colorScale.colors + .map((color, index) => { + return { + color, + value: +this.wafermap.colorScale.values[index]! + }; + }) + .sort((a, b) => a.value - b.value); + } + + private calculateLabelsFontSize( + dieDimensions: Dimensions, + maxCharacters: number + ): number { + return Math.min( + dieDimensions.height, + (dieDimensions.width / (Math.max(2, maxCharacters) * 0.5)) + * this.fontSizeFactor + ); + } +} diff --git a/packages/nimble-components/src/wafer-map/modules/experimental/worker-renderer.ts b/packages/nimble-components/src/wafer-map/modules/experimental/worker-renderer.ts index 5556c7bfe2..8078a03267 100644 --- a/packages/nimble-components/src/wafer-map/modules/experimental/worker-renderer.ts +++ b/packages/nimble-components/src/wafer-map/modules/experimental/worker-renderer.ts @@ -7,20 +7,15 @@ import { HoverDieOpacity } from '../../types'; export class WorkerRenderer { public constructor(private readonly wafermap: WaferMap) {} - public updateSortedDiesAndDrawWafer(): void { - // redundant function for backwards compatibility - this.drawWafer(); - } - public drawWafer(): void { // rendering will be implemented in a future PR this.renderHover(); } public renderHover(): void { - this.wafermap.hoverWidth = this.wafermap.dataManager.dieDimensions.width + this.wafermap.hoverWidth = this.wafermap.experimentalDataManager.dieDimensions.width * this.wafermap.transform.k; - this.wafermap.hoverHeight = this.wafermap.dataManager.dieDimensions.height + this.wafermap.hoverHeight = this.wafermap.experimentalDataManager.dieDimensions.height * this.wafermap.transform.k; this.wafermap.hoverOpacity = this.wafermap.hoverDie === undefined ? HoverDieOpacity.hide @@ -30,21 +25,21 @@ export class WorkerRenderer { private calculateHoverTransform(): string { if (this.wafermap.hoverDie !== undefined) { - const scaledX = this.wafermap.dataManager.horizontalScale( + const scaledX = this.wafermap.experimentalDataManager.horizontalScale( this.wafermap.hoverDie.x ); if (scaledX === undefined) { return ''; } - const scaledY = this.wafermap.dataManager.verticalScale( + const scaledY = this.wafermap.experimentalDataManager.verticalScale( this.wafermap.hoverDie.y ); if (scaledY === undefined) { return ''; } const transformedPoint = this.wafermap.transform.apply([ - scaledX + this.wafermap.dataManager.margin.left, - scaledY + this.wafermap.dataManager.margin.top + scaledX + this.wafermap.experimentalDataManager.margin.left, + scaledY + this.wafermap.experimentalDataManager.margin.top ]); return `translate(${transformedPoint[0]}, ${transformedPoint[1]})`; } diff --git a/packages/nimble-components/src/wafer-map/modules/hover-handler.ts b/packages/nimble-components/src/wafer-map/modules/hover-handler.ts index da3b5dc336..2d22cab643 100644 --- a/packages/nimble-components/src/wafer-map/modules/hover-handler.ts +++ b/packages/nimble-components/src/wafer-map/modules/hover-handler.ts @@ -1,6 +1,5 @@ import type { WaferMap } from '..'; import { PointCoordinates, WaferMapOriginLocation } from '../types'; -import { DataManager } from './data-manager'; /** * HoverHandler deals with user interactions and events like hovering @@ -25,7 +24,7 @@ export class HoverHandler { } private readonly onMouseMove = (event: MouseEvent): void => { - if (this.wafermap.isExperimentalRenderer()) { + if (this.wafermap.isExperimentalUpdate()) { return; } const mousePosition: PointCoordinates = { @@ -51,7 +50,6 @@ export class HoverHandler { this.wafermap.hoverDie = undefined; return; } - this.wafermap.hoverDie = this.wafermap.dataManager.getWaferMapDie(dieCoordinates); }; @@ -62,10 +60,7 @@ export class HoverHandler { private calculateDieCoordinates( mousePosition: PointCoordinates ): PointCoordinates | undefined { - if ( - !this.wafermap.isExperimentalRenderer() - && this.wafermap.dataManager instanceof DataManager - ) { + if (!this.wafermap.isExperimentalUpdate()) { const originLocation = this.wafermap.originLocation; const xRoundFunction = originLocation === WaferMapOriginLocation.bottomLeft || originLocation === WaferMapOriginLocation.topLeft diff --git a/packages/nimble-components/src/wafer-map/tests/data-manager.spec.ts b/packages/nimble-components/src/wafer-map/tests/data-manager.spec.ts index d28a4a951e..454c0f4a79 100644 --- a/packages/nimble-components/src/wafer-map/tests/data-manager.spec.ts +++ b/packages/nimble-components/src/wafer-map/tests/data-manager.spec.ts @@ -56,7 +56,7 @@ describe('Wafermap Data Manager', () => { processUpdates(); - dataManagerModule = element.stableDataManager; + dataManagerModule = element.dataManager; }); afterEach(async () => { diff --git a/packages/nimble-components/src/wafer-map/tests/experimantal-prerendering.spec.ts b/packages/nimble-components/src/wafer-map/tests/experimantal-prerendering.spec.ts new file mode 100644 index 0000000000..0af5c5f342 --- /dev/null +++ b/packages/nimble-components/src/wafer-map/tests/experimantal-prerendering.spec.ts @@ -0,0 +1,278 @@ +import { Prerendering } from '../modules/experimental/prerendering'; +import { WaferMapColorScaleMode } from '../types'; +import { + getExperimentalDataManagerMock, + defaultExperimentalHorizontalScale, + defaultExperimentalVerticalScale, + getWaferMapDies, + getExperimentalWaferMapMockPrerendering +} from './utilities'; + +describe('Wafermap Experimental Prerendering module', () => { + let prerenderingModule: Prerendering; + + it('with die input and small die height should not have labelsFontSize larger than the die height', () => { + const dieDimensions = { width: 10, height: 1 }; + const dieLabelsSuffix = ''; + const dieLabelsHidden = false; + const maxCharacters = 2; + const highlightedTags: string[] = []; + const margin = { top: 0, right: 0, bottom: 0, left: 0 }; + + const dataManagerMock = getExperimentalDataManagerMock( + dieDimensions, + margin, + defaultExperimentalHorizontalScale, + defaultExperimentalVerticalScale + ); + const waferMock = getExperimentalWaferMapMockPrerendering( + getWaferMapDies(), + { colors: [], values: [] }, + highlightedTags, + WaferMapColorScaleMode.linear, + dieLabelsHidden, + dieLabelsSuffix, + maxCharacters, + dataManagerMock + ); + prerenderingModule = new Prerendering(waferMock); + prerenderingModule.update(); + + expect(prerenderingModule.labelsFontSize).toBeLessThanOrEqual( + dieDimensions.height + ); + }); + + it('with small width and one character at maximum should not have labelsFontSize larger than the die width', () => { + const dieDimensions = { width: 1, height: 10 }; + const dieLabelsSuffix = ''; + const dieLabelsHidden = false; + const maxCharacters = 1; + const highlightedTags: string[] = []; + const margin = { top: 0, right: 0, bottom: 0, left: 0 }; + + const dataManagerMock = getExperimentalDataManagerMock( + dieDimensions, + margin, + defaultExperimentalHorizontalScale, + defaultExperimentalVerticalScale + ); + const waferMock = getExperimentalWaferMapMockPrerendering( + getWaferMapDies(), + { colors: [], values: [] }, + highlightedTags, + WaferMapColorScaleMode.linear, + dieLabelsHidden, + dieLabelsSuffix, + maxCharacters, + dataManagerMock + ); + prerenderingModule = new Prerendering(waferMock); + prerenderingModule.update(); + + expect(prerenderingModule.labelsFontSize).toBeLessThan( + dieDimensions.width + ); + }); + + describe('with linear color scale', () => { + const colorScaleMode = WaferMapColorScaleMode.linear; + + it('and only one color value pair should have undefined color category', () => { + const dieDimensions = { width: 10, height: 10 }; + const dieLabelsSuffix = ''; + const dieLabelsHidden = true; + const maxCharacters = 2; + const highlightedTags: string[] = []; + const margin = { top: 0, right: 0, bottom: 0, left: 0 }; + + const dataManagerMock = getExperimentalDataManagerMock( + dieDimensions, + margin, + defaultExperimentalHorizontalScale, + defaultExperimentalVerticalScale + ); + const waferMock = getExperimentalWaferMapMockPrerendering( + getWaferMapDies(), + { colors: ['red'], values: ['1'] }, + highlightedTags, + colorScaleMode, + dieLabelsHidden, + dieLabelsSuffix, + maxCharacters, + dataManagerMock + ); + prerenderingModule = new Prerendering(waferMock); + prerenderingModule.update(); + const expectedValues = Array(1).fill(undefined); + + const actualValues = prerenderingModule.colorScale.map( + colorCategory => colorCategory.color + ); + + expect(actualValues).toEqual( + jasmine.arrayWithExactContents(expectedValues) + ); + }); + + it('and only one duplicated color value pair should have a single color category', () => { + const dieDimensions = { width: 10, height: 10 }; + const dieLabelsSuffix = ''; + const dieLabelsHidden = true; + const maxCharacters = 2; + const highlightedTags: string[] = []; + const margin = { top: 0, right: 0, bottom: 0, left: 0 }; + + const dataManagerMock = getExperimentalDataManagerMock( + dieDimensions, + margin, + defaultExperimentalHorizontalScale, + defaultExperimentalVerticalScale + ); + const waferMock = getExperimentalWaferMapMockPrerendering( + getWaferMapDies(), + { + colors: ['red', 'red'], + values: ['1', '1'] + }, + highlightedTags, + colorScaleMode, + dieLabelsHidden, + dieLabelsSuffix, + maxCharacters, + dataManagerMock + ); + prerenderingModule = new Prerendering(waferMock); + prerenderingModule.update(); + + const expectedValues = Array(1).fill('rgb(255, 0, 0)'); + const actualValues = prerenderingModule.colorScale.map( + colorCategory => colorCategory.color + ); + expect(actualValues).toEqual( + jasmine.arrayWithExactContents(expectedValues) + ); + }); + + it('and color value pairs for the scale ends should have the colors equally distributed', () => { + const dieDimensions = { width: 10, height: 10 }; + const dieLabelsSuffix = ''; + const dieLabelsHidden = true; + const maxCharacters = 2; + const highlightedTags: string[] = []; + const margin = { top: 0, right: 0, bottom: 0, left: 0 }; + + const dataManagerMock = getExperimentalDataManagerMock( + dieDimensions, + margin, + defaultExperimentalHorizontalScale, + defaultExperimentalVerticalScale + ); + const waferMock = getExperimentalWaferMapMockPrerendering( + getWaferMapDies(), + { + colors: ['black', 'red'], + values: ['1', '18'] + }, + highlightedTags, + colorScaleMode, + dieLabelsHidden, + dieLabelsSuffix, + maxCharacters, + dataManagerMock + ); + prerenderingModule = new Prerendering(waferMock); + prerenderingModule.update(); + const waferMapDies = getWaferMapDies(); + const expectedValues = waferMapDies + .sort((a, b) => +a.value - +b.value) + .map(waferMapDie => { + return `rgb(${(+waferMapDie.value - 1) * 15}, 0, 0)`; + }); + const actualValues = prerenderingModule.colorScale.map( + colorCategory => colorCategory.color + ); + expect(actualValues).toEqual( + jasmine.arrayWithExactContents(expectedValues) + ); + }); + }); + + describe('with ordinal color scale', () => { + const colorScaleMode = WaferMapColorScaleMode.ordinal; + + it('and only one color value pair should have a single color category', () => { + const dieDimensions = { width: 10, height: 10 }; + const dieLabelsSuffix = ''; + const dieLabelsHidden = true; + const maxCharacters = 2; + const highlightedTags: string[] = []; + const margin = { top: 0, right: 0, bottom: 0, left: 0 }; + + const dataManagerMock = getExperimentalDataManagerMock( + dieDimensions, + margin, + defaultExperimentalHorizontalScale, + defaultExperimentalVerticalScale + ); + const waferMock = getExperimentalWaferMapMockPrerendering( + getWaferMapDies(), + { colors: ['red'], values: ['1'] }, + highlightedTags, + colorScaleMode, + dieLabelsHidden, + dieLabelsSuffix, + maxCharacters, + dataManagerMock + ); + prerenderingModule = new Prerendering(waferMock); + prerenderingModule.update(); + const expectedValues = Array(1).fill('red'); + const actualValues = prerenderingModule.colorScale.map( + colorCategory => colorCategory.color + ); + expect(actualValues).toEqual( + jasmine.arrayWithExactContents(expectedValues) + ); + }); + + it('and two colors should have two color categories', () => { + const dieDimensions = { width: 10, height: 10 }; + const dieLabelsSuffix = ''; + const dieLabelsHidden = true; + const maxCharacters = 2; + const highlightedTags: string[] = []; + const margin = { top: 0, right: 0, bottom: 0, left: 0 }; + + const dataManagerMock = getExperimentalDataManagerMock( + dieDimensions, + margin, + defaultExperimentalHorizontalScale, + defaultExperimentalVerticalScale + ); + const waferMock = getExperimentalWaferMapMockPrerendering( + getWaferMapDies(), + { + colors: ['black', 'red'], + values: [] + }, + highlightedTags, + colorScaleMode, + dieLabelsHidden, + dieLabelsSuffix, + maxCharacters, + dataManagerMock + ); + prerenderingModule = new Prerendering(waferMock); + prerenderingModule.update(); + + const expectedValues = ['black', 'red']; + const actualValues = prerenderingModule.colorScale.map( + colorCategory => colorCategory.color + ); + expect(actualValues).toEqual( + jasmine.arrayWithExactContents(expectedValues) + ); + }); + }); +}); diff --git a/packages/nimble-components/src/wafer-map/tests/experimental-computations.spec.ts b/packages/nimble-components/src/wafer-map/tests/experimental-computations.spec.ts index c4e1aa1556..481dfe4587 100644 --- a/packages/nimble-components/src/wafer-map/tests/experimental-computations.spec.ts +++ b/packages/nimble-components/src/wafer-map/tests/experimental-computations.spec.ts @@ -24,7 +24,7 @@ describe('Wafermap Experimental Computations module', () => { 100 ); computationsModule = new Computations(waferMock); - computationsModule.updateContainerDimensions(); + computationsModule.update(); }); it('should have expected square container', () => { @@ -66,7 +66,7 @@ describe('Wafermap Experimental Computations module', () => { 100 ); computationsModule = new Computations(waferMock); - computationsModule.updateContainerDimensions(); + computationsModule.update(); }); it('should have adjusted square container', () => { @@ -131,7 +131,7 @@ describe('Wafermap Experimental Computations module', () => { 100 ); computationsModule = new Computations(waferMock); - computationsModule.updateContainerDimensions(); + computationsModule.update(); expect(computationsModule.horizontalScale.range()).toEqual( value.horizontalRange ); diff --git a/packages/nimble-components/src/wafer-map/tests/experimental-data-manager.spec.ts b/packages/nimble-components/src/wafer-map/tests/experimental-data-manager.spec.ts index d50c4d4ce8..b6028def25 100644 --- a/packages/nimble-components/src/wafer-map/tests/experimental-data-manager.spec.ts +++ b/packages/nimble-components/src/wafer-map/tests/experimental-data-manager.spec.ts @@ -5,17 +5,11 @@ import { processUpdates } from '../../testing/async-helpers'; import type { DataManager } from '../modules/experimental/data-manager'; import type { WaferMap } from '..'; import { - Dimensions, Margin, WaferMapColorScaleMode, WaferMapOriginLocation } from '../types'; -import { - getColorScale, - getHighlightedTags, - getWaferMapDies, - getWaferMapDiesTable -} from './utilities'; +import { getColorScale, getWaferMapDiesTable } from './utilities'; async function setup(): Promise> { return fixture(html``); @@ -26,10 +20,6 @@ describe('Wafermap Experimental Data Manager', () => { const dieLabelsSuffix = '%'; const canvasWidth = 200; const canvasHeight = 100; - const canvasDimensions: Dimensions = { - width: canvasWidth, - height: canvasHeight - }; const expectedMargin: Margin = { top: 4, right: 54, @@ -106,72 +96,13 @@ describe('Wafermap Experimental Data Manager', () => { ); }); - // skipped until prerendering is refactored - xit('should have as many dies as provided', () => { - expect(dataManagerModule.diesRenderInfo.length).toEqual( - getWaferMapDies().length + it('should have the same color categories', () => { + const expectedValues = getColorScale().colors; + const actualValues = dataManagerModule.colorScale.map( + colorCategory => colorCategory.color ); - }); - - // skipped until prerendering is refactored - xit('should have label with suffix for each die', () => { - const actualValues = dataManagerModule.diesRenderInfo.map( - dieRenderInfo => dieRenderInfo.text - ); - expect(actualValues).not.toHaveSize(0); - for (const value of actualValues) { - expect(value).toContain(dieLabelsSuffix); - } - }); - - // skipped until prerendering is refactored - xit('should have all dies with full opacity from the highlighted list', () => { - const highlightedTags = getHighlightedTags(); - const dies = getWaferMapDies().filter(die => die.tags?.some(dieTag => highlightedTags.some( - highlightedTag => dieTag === highlightedTag - ))); - const diesWithFullOpacity = dataManagerModule.diesRenderInfo.filter(x => x.fillStyle.endsWith(',1)')); - expect(dies.length).toEqual(diesWithFullOpacity.length); - }); - - // skipped until prerendering is refactored - xit('should not have any dies with partial opacity from the highlighted list', () => { - const highlightedTags = getHighlightedTags(); - const dies = getWaferMapDies().filter( - die => !die.tags?.some(dieTag => highlightedTags.some( - highlightedTag => dieTag === highlightedTag - )) - ); - const diesWithPartialOpacity = dataManagerModule.diesRenderInfo.filter( - x => !x.fillStyle.endsWith(',1)') - ); - expect(dies.length).toEqual(diesWithPartialOpacity.length); - }); - - // skipped until prerendering is refactored - xit('should have all dies inside the canvas with margins', () => { - const actualValues = dataManagerModule.diesRenderInfo.map( - dieRenderInfo => { - return { - x: dieRenderInfo.x, - y: dieRenderInfo.y - }; - } + expect(actualValues).toEqual( + jasmine.arrayWithExactContents(expectedValues) ); - expect(actualValues).not.toHaveSize(0); - for (const value of actualValues) { - expect(value.x).toBeGreaterThanOrEqual(0); - expect(value.y).toBeGreaterThanOrEqual(0); - expect(value.x).toBeLessThanOrEqual( - canvasDimensions.width - - dataManagerModule.dieDimensions.width - - expectedMargin.left - ); - expect(value.y).toBeLessThanOrEqual( - canvasDimensions.height - - dataManagerModule.dieDimensions.height - - expectedMargin.bottom - ); - } }); }); diff --git a/packages/nimble-components/src/wafer-map/tests/utilities.ts b/packages/nimble-components/src/wafer-map/tests/utilities.ts index 30f100c3ff..5db3f3ebd0 100644 --- a/packages/nimble-components/src/wafer-map/tests/utilities.ts +++ b/packages/nimble-components/src/wafer-map/tests/utilities.ts @@ -1,4 +1,11 @@ -import { ScaleBand, ScaleQuantile, scaleBand, scaleQuantile } from 'd3-scale'; +import { + ScaleBand, + ScaleLinear, + ScaleQuantile, + scaleBand, + scaleLinear, + scaleQuantile +} from 'd3-scale'; import { type Table, tableFromArrays } from 'apache-arrow'; import type { ZoomTransform } from 'd3-zoom'; import { @@ -13,6 +20,7 @@ import { WaferRequiredFields } from '../types'; import type { DataManager } from '../modules/data-manager'; +import type { DataManager as ExperimentalDataManager } from '../modules/experimental/data-manager'; import type { WaferMap } from '..'; export function getWaferMapDies(): WaferMapDie[] { @@ -73,6 +81,13 @@ export function getHighlightedTags(): string[] { return ['5', '10', '15']; } +export function getScaleLinear( + domain: number[] = [], + range: number[] = [] +): ScaleLinear { + return scaleLinear().domain(domain).range(range); +} + export function getScaleBand( domain: number[] = [], range: number[] = [] @@ -94,6 +109,14 @@ export const defaultVerticalScale = scaleBand() .domain([1, 2, 3, 4, 5, 6]) .range([1, 7]); +export const defaultExperimentalHorizontalScale = scaleLinear() + .domain([2, 6]) + .range([2, 7]); + +export const defaultExperimentalVerticalScale = scaleLinear() + .domain([1, 6]) + .range([1, 7]); + export function getDataManagerMock( dieDimensions: Dimensions, margin: Margin, @@ -111,6 +134,23 @@ export function getDataManagerMock( }; return dataManagerMock as DataManager; } +export function getExperimentalDataManagerMock( + dieDimensions: Dimensions, + margin: Margin, + horizontalScale: ScaleLinear = getScaleLinear([], []), + verticalScale: ScaleLinear = getScaleLinear([], []) +): ExperimentalDataManager { + const dataManagerMock: Pick< + ExperimentalDataManager, + 'horizontalScale' | 'verticalScale' | 'dieDimensions' | 'margin' + > = { + horizontalScale, + verticalScale, + dieDimensions, + margin + }; + return dataManagerMock as ExperimentalDataManager; +} export function getDataManagerMockForHover( margin: Margin, @@ -134,6 +174,38 @@ export function getDataManagerMockForHover( return dataManagerMock as DataManager; } +export function getExperimentalWaferMapMockPrerendering( + dies: WaferMapDie[] = getWaferMapDies(), + colorScale: WaferMapColorScale = { colors: [], values: [] }, + highlightedTags: string[] = [], + colorScaleMode: WaferMapColorScaleMode = WaferMapColorScaleMode.linear, + dieLabelsHidden = true, + dieLabelsSuffix = '', + maxCharacters = 4, + experimentalDataManager = {} as ExperimentalDataManager +): WaferMap { + const waferMapMock: Pick< + WaferMap, + | 'dies' + | 'colorScale' + | 'highlightedTags' + | 'colorScaleMode' + | 'dieLabelsHidden' + | 'dieLabelsSuffix' + | 'maxCharacters' + | 'experimentalDataManager' + > = { + dies, + colorScale, + highlightedTags, + colorScaleMode, + dieLabelsHidden, + dieLabelsSuffix, + maxCharacters, + experimentalDataManager + }; + return waferMapMock as WaferMap; +} export function getWaferMapMockPrerendering( dies: WaferMapDie[] = getWaferMapDies(), colorScale: WaferMapColorScale = { colors: [], values: [] }, @@ -173,7 +245,7 @@ export function getWaferMapMockHover( originLocation: WaferMapOriginLocation, hoverDie: HoverDie | undefined, dataManager: DataManager, - isExperimentalRenderer: boolean + isExperimentalUpdate: boolean ): WaferMap { const waferMapMock: Pick< WaferMap, @@ -182,14 +254,14 @@ export function getWaferMapMockHover( | 'originLocation' | 'hoverDie' | 'dataManager' - | 'isExperimentalRenderer' + | 'isExperimentalUpdate' > = { diesTable, transform, originLocation, hoverDie, dataManager, - isExperimentalRenderer: () => isExperimentalRenderer + isExperimentalUpdate: () => isExperimentalUpdate }; return waferMapMock as WaferMap; } diff --git a/packages/nimble-components/src/wafer-map/tests/wafer-map.spec.ts b/packages/nimble-components/src/wafer-map/tests/wafer-map.spec.ts index dbb9cb7e0f..ad682ba0ac 100644 --- a/packages/nimble-components/src/wafer-map/tests/wafer-map.spec.ts +++ b/packages/nimble-components/src/wafer-map/tests/wafer-map.spec.ts @@ -8,8 +8,6 @@ import { WaferMapOrientation, WaferMapOriginLocation } from '../types'; -import { RenderingModule } from '../modules/rendering'; -import { WorkerRenderer } from '../modules/experimental/worker-renderer'; async function setup(): Promise> { return fixture(html``); @@ -121,8 +119,7 @@ describe('WaferMap', () => { drawWaferSpy = spyOn(element.workerRenderer, 'drawWafer'); }); - // skipped until prerendering is refactored - xit('will call drawWafer after supported diesTable change', () => { + it('will call drawWafer after supported diesTable change', () => { element.diesTable = tableFromArrays({ colIndex: Int32Array.from([]), rowIndex: Int32Array.from([]), @@ -143,14 +140,19 @@ describe('WaferMap', () => { describe('worker renderer flow', () => { let renderHoverSpy: jasmine.Spy; + let experimentalUpdateSpy: jasmine.Spy; beforeEach(() => { renderHoverSpy = spyOn(element.workerRenderer, 'renderHover'); + experimentalUpdateSpy = spyOn( + element, + 'experimentalUpdate' + ).and.callThrough(); }); it('will use RenderingModule after dies change', () => { element.dies = [{ x: 1, y: 1, value: '1' }]; processUpdates(); - expect(element.renderer instanceof RenderingModule).toBeTrue(); + expect(experimentalUpdateSpy).toHaveBeenCalledTimes(0); }); it('will use WorkerRenderer after supported diesTable change', () => { @@ -160,13 +162,14 @@ describe('WaferMap', () => { value: Float64Array.from([]) }); processUpdates(); - expect(element.renderer instanceof WorkerRenderer).toBeTrue(); + expect(experimentalUpdateSpy).toHaveBeenCalledTimes(1); }); - it('will use RenderingModule after unsupported diesTable change', () => { + it('will use WorkerRenderer after unsupported diesTable change but it will fail', () => { element.diesTable = new Table(); processUpdates(); - expect(element.renderer instanceof RenderingModule).toBeTrue(); + expect(experimentalUpdateSpy).toHaveBeenCalledTimes(1); + expect(renderHoverSpy).toHaveBeenCalledTimes(0); }); it('will call renderHover after supported diesTable change', () => { diff --git a/packages/nimble-components/src/wafer-map/types.ts b/packages/nimble-components/src/wafer-map/types.ts index 7193593c4f..bd9a4ac975 100644 --- a/packages/nimble-components/src/wafer-map/types.ts +++ b/packages/nimble-components/src/wafer-map/types.ts @@ -100,3 +100,10 @@ export type WaferRequiredFields = { rowIndex: Int32, value: Float64 }; + +interface IColorScaleMarker { + color: string; + value: number; +} + +export type ColorScale = IColorScaleMarker[];