From f56b73b45161a55b967dc03bbdd094a6527c814c Mon Sep 17 00:00:00 2001 From: Natan Muntean <33986780+munteannatan@users.noreply.github.com> Date: Mon, 25 Mar 2024 18:45:21 +0200 Subject: [PATCH] Wafer Map Experimental Event Coordinator (#1916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Pull Request ## ๐Ÿคจ Rationale I tried to expand on the strategy switching mechanism from the renderer, and reused it in the coordinator This approach has the benefits of isolating the experimental code while in development, and an easy way to switch to the new version by changing just the imports and switching mechanism and removing the old versions after they become obsolete ## ๐Ÿ‘ฉโ€๐Ÿ’ป Implementation because arquero causes typescript errors I implemented an alternative search and posted a PR with fixes in arquero repo https://github.com/uwdata/arquero/pull/346 changed the zoom handler to catch events on the component, not just the canvas removed zooming border of around 100px when zooming in the wafer after PO decision, resulting in less complex code and touch controls enabling. ## ๐Ÿงช Testing existing tests are passing created new hover unit tests with wafer and data manager mocks ## โœ… Checklist - [x] I have updated the project documentation to reflect my changes or determined no changes are needed. --------- Co-authored-by: rajsite --- ...-163379f4-09c6-4606-95f4-05917dd50ee0.json | 7 + .../nimble-components/src/wafer-map/index.ts | 78 ++++++-- .../src/wafer-map/modules/data-manager.ts | 6 +- .../wafer-map/modules/event-coordinator.ts | 47 ----- .../modules/experimental/hover-handler.ts | 98 ++++++++++ .../{ => experimental}/worker-renderer.ts | 4 +- .../src/wafer-map/modules/hover-handler.ts | 50 +++-- .../src/wafer-map/modules/prerendering.ts | 14 +- .../wafer-map/modules/wafer-map-validator.ts | 52 ++--- .../src/wafer-map/modules/zoom-handler.ts | 83 +++----- .../src/wafer-map/tests/computations.spec.ts | 13 +- .../src/wafer-map/tests/data-generator.ts | 2 +- .../src/wafer-map/tests/hover-handler.spec.ts | 114 +++++++++++ .../tests/prerendering.coloring.spec.ts | 182 ++++++++---------- .../tests/prerendering.labeling.spec.ts | 162 +++++++--------- .../tests/prerendering.positioning.spec.ts | 62 +++--- .../src/wafer-map/tests/sets.ts | 2 +- .../src/wafer-map/tests/utilities.ts | 93 +++++++-- .../tests/wafer-map-validator.spec.ts | 27 ++- .../src/wafer-map/tests/wafer-map.spec.ts | 48 +++-- .../src/wafer-map/tests/wafer-map.stories.ts | 4 +- .../nimble-components/src/wafer-map/types.ts | 21 ++ 22 files changed, 698 insertions(+), 471 deletions(-) create mode 100644 change/@ni-nimble-components-163379f4-09c6-4606-95f4-05917dd50ee0.json delete mode 100644 packages/nimble-components/src/wafer-map/modules/event-coordinator.ts create mode 100644 packages/nimble-components/src/wafer-map/modules/experimental/hover-handler.ts rename packages/nimble-components/src/wafer-map/modules/{ => experimental}/worker-renderer.ts (95%) create mode 100644 packages/nimble-components/src/wafer-map/tests/hover-handler.spec.ts diff --git a/change/@ni-nimble-components-163379f4-09c6-4606-95f4-05917dd50ee0.json b/change/@ni-nimble-components-163379f4-09c6-4606-95f4-05917dd50ee0.json new file mode 100644 index 0000000000..7456d8a059 --- /dev/null +++ b/change/@ni-nimble-components-163379f4-09c6-4606-95f4-05917dd50ee0.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Created new hover event for the new diesTable api and changed the zoom event", + "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 aed309664d..70c09e9b8d 100644 --- a/packages/nimble-components/src/wafer-map/index.ts +++ b/packages/nimble-components/src/wafer-map/index.ts @@ -10,19 +10,23 @@ import { template } from './template'; import { styles } from './styles'; import { DataManager } from './modules/data-manager'; import { RenderingModule } from './modules/rendering'; -import { EventCoordinator } from './modules/event-coordinator'; import { + HoverDie, HoverDieOpacity, WaferMapColorScale, WaferMapColorScaleMode, WaferMapDie, WaferMapOrientation, WaferMapOriginLocation, - WaferMapValidity + WaferMapValidity, + type WaferRequiredFields } from './types'; import { WaferMapUpdateTracker } from './modules/wafer-map-update-tracker'; import { WaferMapValidator } from './modules/wafer-map-validator'; -import { WorkerRenderer } from './modules/worker-renderer'; +import { WorkerRenderer } from './modules/experimental/worker-renderer'; +import { HoverHandler } from './modules/hover-handler'; +import { HoverHandler as ExperimentalHoverHandler } from './modules/experimental/hover-handler'; +import { ZoomHandler } from './modules/zoom-handler'; declare global { interface HTMLElementTagNameMap { @@ -33,12 +37,14 @@ declare global { /** * A nimble-styled WaferMap */ -export class WaferMap extends FoundationElement { +export class WaferMap< + T extends WaferRequiredFields = WaferRequiredFields +> extends FoundationElement { /** * @internal * needs to be initialized before the properties trigger changes */ - public readonly waferMapUpdateTracker = new WaferMapUpdateTracker(this); + public readonly waferMapUpdateTracker: WaferMapUpdateTracker = new WaferMapUpdateTracker(this.asRequiredFieldsWaferMap); @attr({ attribute: 'origin-location' }) public originLocation: WaferMapOriginLocation = WaferMapOriginLocation.bottomLeft; @@ -88,15 +94,23 @@ export class WaferMap extends FoundationElement { /** * @internal */ - public readonly dataManager = new DataManager(this); + public readonly dataManager: DataManager = new DataManager( + this.asRequiredFieldsWaferMap + ); + /** * @internal */ - public readonly mainRenderer = new RenderingModule(this); + public readonly mainRenderer = new RenderingModule( + this.asRequiredFieldsWaferMap + ); + /** * @internal */ - public readonly workerRenderer = new WorkerRenderer(this); + public readonly workerRenderer = new WorkerRenderer( + this.asRequiredFieldsWaferMap + ); @observable public renderer: RenderingModule | WorkerRenderer = this.mainRenderer; @@ -144,20 +158,29 @@ export class WaferMap extends FoundationElement { /** * @internal */ - @observable public hoverDie: WaferMapDie | undefined; + @observable public hoverDie: WaferMapDie | HoverDie | undefined; @observable public highlightedTags: string[] = []; @observable public dies: WaferMapDie[] = []; - @observable public diesTable: Table | undefined; + @observable public diesTable: Table | undefined; @observable public colorScale: WaferMapColorScale = { colors: [], values: [] }; - private readonly eventCoordinator = new EventCoordinator(this); + private readonly hoverHandler: HoverHandler = new HoverHandler( + this.asRequiredFieldsWaferMap + ); + + private readonly experimentalHoverHandler: ExperimentalHoverHandler = new ExperimentalHoverHandler(this.asRequiredFieldsWaferMap); + + private readonly zoomHandler: ZoomHandler = new ZoomHandler( + this.asRequiredFieldsWaferMap + ); + private readonly resizeObserver = this.createResizeObserver(); - private readonly waferMapValidator = new WaferMapValidator(this); + private readonly waferMapValidator: WaferMapValidator = new WaferMapValidator(this.asRequiredFieldsWaferMap); public get validity(): WaferMapValidity { return this.waferMapValidator.getValidity(); @@ -168,12 +191,18 @@ export class WaferMap extends FoundationElement { this.canvasContext = this.canvas.getContext('2d', { willReadFrequently: true })!; + this.hoverHandler.connect(); + this.experimentalHoverHandler.connect(); + this.zoomHandler.connect(); this.resizeObserver.observe(this); this.waferMapUpdateTracker.trackAll(); } public override disconnectedCallback(): void { super.disconnectedCallback(); + this.hoverHandler.disconnect(); + this.experimentalHoverHandler.disconnect(); + this.zoomHandler.disconnect(); this.resizeObserver.unobserve(this); } @@ -190,8 +219,12 @@ export class WaferMap extends FoundationElement { if (this.validity.invalidDiesTableSchema) { return; } + this.renderer = this.isExperimentalRenderer() + ? this.workerRenderer + : this.mainRenderer; if (this.waferMapUpdateTracker.requiresEventsUpdate) { - this.eventCoordinator.detachEvents(); + // zoom translateExtent needs to be recalculated when canvas size changes + this.zoomHandler.disconnect(); if (this.waferMapUpdateTracker.requiresContainerDimensionsUpdate) { this.dataManager.updateContainerDimensions(); this.renderer.updateSortedDiesAndDrawWafer(); @@ -211,12 +244,19 @@ export class WaferMap extends FoundationElement { } else if (this.waferMapUpdateTracker.requiresDrawnWaferUpdate) { this.renderer.drawWafer(); } - this.eventCoordinator.attachEvents(); + this.zoomHandler.connect(); } else if (this.waferMapUpdateTracker.requiresRenderHoverUpdate) { this.renderer.renderHover(); } } + /** + * @internal + */ + public isExperimentalRenderer(): boolean { + return this.diesTable !== undefined; + } + private validate(): void { this.waferMapValidator.validateGridDimensions(); this.waferMapValidator.validateDiesTableSchema(); @@ -291,17 +331,11 @@ export class WaferMap extends FoundationElement { private diesChanged(): void { this.waferMapUpdateTracker.track('dies'); - this.renderer = this.diesTable === undefined - ? this.mainRenderer - : this.workerRenderer; this.waferMapUpdateTracker.queueUpdate(); } private diesTableChanged(): void { this.waferMapUpdateTracker.track('dies'); - this.renderer = this.diesTable === undefined - ? this.mainRenderer - : this.workerRenderer; this.waferMapUpdateTracker.queueUpdate(); } @@ -330,6 +364,10 @@ export class WaferMap extends FoundationElement { this.waferMapUpdateTracker.track('hoverDie'); this.waferMapUpdateTracker.queueUpdate(); } + + private get asRequiredFieldsWaferMap(): WaferMap { + return this as WaferMap; + } } const nimbleWaferMap = WaferMap.compose({ diff --git a/packages/nimble-components/src/wafer-map/modules/data-manager.ts b/packages/nimble-components/src/wafer-map/modules/data-manager.ts index fb9fdbc3e9..b098683bfd 100644 --- a/packages/nimble-components/src/wafer-map/modules/data-manager.ts +++ b/packages/nimble-components/src/wafer-map/modules/data-manager.ts @@ -58,13 +58,13 @@ export class DataManager { return this.dataMap; } - private readonly computations; - private readonly prerendering; + 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, this); + this.prerendering = new Prerendering(wafermap); } public updateContainerDimensions(): void { diff --git a/packages/nimble-components/src/wafer-map/modules/event-coordinator.ts b/packages/nimble-components/src/wafer-map/modules/event-coordinator.ts deleted file mode 100644 index 420d401be5..0000000000 --- a/packages/nimble-components/src/wafer-map/modules/event-coordinator.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { WaferMapDie } from '../types'; -import { ZoomHandler } from './zoom-handler'; -import type { WaferMap } from '..'; -import { HoverHandler } from './hover-handler'; - -export interface EventCoordinatorCallbacks { - dieSelected: (die: WaferMapDie) => void; -} - -/** - * EventCoordinator deals with user interactions and events - */ -export class EventCoordinator { - private readonly zoomHandler; - private readonly hoverHandler; - public constructor(private readonly wafermap: WaferMap) { - this.zoomHandler = new ZoomHandler(wafermap); - this.hoverHandler = new HoverHandler(wafermap); - } - - public attachEvents(): void { - this.zoomHandler.createZoomBehavior(); - this.wafermap.addEventListener('mousemove', this.onMouseMove); - this.wafermap.addEventListener('mouseout', this.onMouseOut); - this.wafermap.canvas.addEventListener('wheel', this.onWheelMove, { - passive: false - }); - } - - public detachEvents(): void { - this.wafermap.removeEventListener('mousemove', this.onMouseMove); - this.wafermap.removeEventListener('mouseout', this.onMouseOut); - this.wafermap.canvas.removeEventListener('wheel', this.onWheelMove); - } - - private readonly onWheelMove = (event: Event): void => { - event.preventDefault(); - }; - - private readonly onMouseMove = (event: MouseEvent): void => { - this.hoverHandler.mousemove(event); - }; - - private readonly onMouseOut = (): void => { - this.hoverHandler.mouseout(); - }; -} 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 new file mode 100644 index 0000000000..82339eebdc --- /dev/null +++ b/packages/nimble-components/src/wafer-map/modules/experimental/hover-handler.ts @@ -0,0 +1,98 @@ +import type { WaferMap } from '../..'; +import { PointCoordinates, WaferMapOriginLocation } from '../../types'; + +/** + * HoverHandler deals with user interactions and events like hovering + */ +export class HoverHandler { + public constructor(private readonly wafermap: WaferMap) {} + + /** + * @internal + */ + public connect(): void { + this.wafermap.addEventListener('mousemove', this.onMouseMove); + this.wafermap.addEventListener('mouseout', this.onMouseOut); + } + + /** + * @internal + */ + public disconnect(): void { + this.wafermap.removeEventListener('mousemove', this.onMouseMove); + 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()) { + return; + } + // get original mouse position in case we are in zoom. + const invertedPoint = this.wafermap.transform.invert([ + event.offsetX, + event.offsetY + ]); + + // does not work yet until data manager will parse diesTable + const dieCoordinates = this.calculateDieCoordinates({ + x: invertedPoint[0], + y: invertedPoint[1] + }); + const colIndex = this.wafermap + .diesTable!.getChild('colIndex')! + .toArray(); + const rowIndex = this.wafermap + .diesTable!.getChild('rowIndex')! + .toArray(); + + // will replace iterating with arquero filtering after fixing errors + for (let i = 0; i < colIndex.length; i++) { + if ( + colIndex[i] === dieCoordinates.x + && rowIndex[i] === dieCoordinates.y + ) { + this.wafermap.hoverDie = { + index: i, + x: dieCoordinates.x, + y: dieCoordinates.y + }; + return; + } + } + this.wafermap.hoverDie = undefined; + }; + + private readonly onMouseOut = (_event: MouseEvent): void => { + this.wafermap.hoverDie = undefined; + }; + + private calculateDieCoordinates( + mousePosition: PointCoordinates + ): PointCoordinates { + const originLocation = this.wafermap.originLocation; + const xRoundFunction = originLocation === WaferMapOriginLocation.bottomLeft + || originLocation === WaferMapOriginLocation.topLeft + ? Math.floor + : Math.ceil; + const yRoundFunction = originLocation === WaferMapOriginLocation.bottomLeft + || originLocation === WaferMapOriginLocation.bottomRight + ? Math.floor + : Math.ceil; + // go to x and y scale to get the x,y values of the die. + const x = xRoundFunction( + this.wafermap.dataManager.invertedHorizontalScale( + mousePosition.x - this.wafermap.dataManager.margin.left + ) + ); + const y = yRoundFunction( + this.wafermap.dataManager.invertedVerticalScale( + mousePosition.y - this.wafermap.dataManager.margin.top + ) + ); + return { x, y }; + } +} diff --git a/packages/nimble-components/src/wafer-map/modules/worker-renderer.ts b/packages/nimble-components/src/wafer-map/modules/experimental/worker-renderer.ts similarity index 95% rename from packages/nimble-components/src/wafer-map/modules/worker-renderer.ts rename to packages/nimble-components/src/wafer-map/modules/experimental/worker-renderer.ts index 24dbcc8a73..5556c7bfe2 100644 --- a/packages/nimble-components/src/wafer-map/modules/worker-renderer.ts +++ b/packages/nimble-components/src/wafer-map/modules/experimental/worker-renderer.ts @@ -1,5 +1,5 @@ -import type { WaferMap } from '..'; -import { HoverDieOpacity } from '../types'; +import type { WaferMap } from '../..'; +import { HoverDieOpacity } from '../../types'; /** * Responsible for drawing the dies inside the wafer map, adding dieText and scaling the canvas 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 cab13191d6..393d7ba015 100644 --- a/packages/nimble-components/src/wafer-map/modules/hover-handler.ts +++ b/packages/nimble-components/src/wafer-map/modules/hover-handler.ts @@ -6,13 +6,33 @@ import { PointCoordinates, WaferMapOriginLocation } from '../types'; */ export class HoverHandler { public constructor(private readonly wafermap: WaferMap) {} - public mousemove(event: MouseEvent): void { + + /** + * @internal + */ + public connect(): void { + this.wafermap.addEventListener('mousemove', this.onMouseMove); + this.wafermap.addEventListener('mouseout', this.onMouseOut); + } + + /** + * @internal + */ + public disconnect(): void { + this.wafermap.removeEventListener('mousemove', this.onMouseMove); + this.wafermap.removeEventListener('mouseout', this.onMouseOut); + } + + private readonly onMouseMove = (event: MouseEvent): void => { + if (this.wafermap.isExperimentalRenderer()) { + return; + } const mousePosition: PointCoordinates = { x: event.offsetX, y: event.offsetY }; - if (!this.hoversOverDie(this.wafermap, mousePosition)) { + if (!this.hoversOverDie(mousePosition)) { this.wafermap.hoverDie = undefined; return; } @@ -22,23 +42,22 @@ export class HoverHandler { mousePosition.y ]); - const dieCoordinates = this.calculateDieCoordinates(this.wafermap, { + const dieCoordinates = this.calculateDieCoordinates({ x: invertedPoint[0], y: invertedPoint[1] }); this.wafermap.hoverDie = this.wafermap.dataManager.getWaferMapDie(dieCoordinates); - } + }; - public mouseout(): void { + private readonly onMouseOut = (_event: MouseEvent): void => { this.wafermap.hoverDie = undefined; - } + }; private calculateDieCoordinates( - wafermap: WaferMap, mousePosition: PointCoordinates ): PointCoordinates { - const originLocation = wafermap.originLocation; + const originLocation = this.wafermap.originLocation; const xRoundFunction = originLocation === WaferMapOriginLocation.bottomLeft || originLocation === WaferMapOriginLocation.topLeft ? Math.floor @@ -49,23 +68,20 @@ export class HoverHandler { : Math.ceil; // go to x and y scale to get the x,y values of the die. const x = xRoundFunction( - wafermap.dataManager.invertedHorizontalScale( - mousePosition.x - wafermap.dataManager.margin.left + this.wafermap.dataManager.invertedHorizontalScale( + mousePosition.x - this.wafermap.dataManager.margin.left ) ); const y = yRoundFunction( - wafermap.dataManager.invertedVerticalScale( - mousePosition.y - wafermap.dataManager.margin.top + this.wafermap.dataManager.invertedVerticalScale( + mousePosition.y - this.wafermap.dataManager.margin.top ) ); return { x, y }; } - private hoversOverDie( - wafermap: WaferMap, - mousePosition: PointCoordinates - ): boolean { - const rgba = wafermap.canvasContext.getImageData( + private hoversOverDie(mousePosition: PointCoordinates): boolean { + const rgba = this.wafermap.canvasContext.getImageData( mousePosition.x, mousePosition.y, 1, diff --git a/packages/nimble-components/src/wafer-map/modules/prerendering.ts b/packages/nimble-components/src/wafer-map/modules/prerendering.ts index 9d4db977d0..85f464d548 100644 --- a/packages/nimble-components/src/wafer-map/modules/prerendering.ts +++ b/packages/nimble-components/src/wafer-map/modules/prerendering.ts @@ -8,7 +8,6 @@ import type { WaferMapDie } from '../types'; import type { WaferMap } from '..'; -import type { DataManager } from './data-manager'; /** * Prerendering prepares render-ready dies data to be used by the rendering module @@ -34,14 +33,11 @@ export class Prerendering { private readonly emptyDieColor = 'rgba(218,223,236,1)'; private readonly nanDieColor = 'rgba(122,122,122,1)'; - public constructor( - private readonly wafermap: WaferMap, - private readonly dataManager: Readonly - ) {} + public constructor(private readonly wafermap: WaferMap) {} public updateLabelsFontSize(): void { this._labelsFontSize = this.calculateLabelsFontSize( - this.dataManager.dieDimensions, + this.wafermap.dataManager.dieDimensions, this.wafermap.maxCharacters ); this.updateDiesRenderInfo(); @@ -61,10 +57,10 @@ export class Prerendering { } private computeDieRenderInfo(die: WaferMapDie): DieRenderInfo | null { - const margin = this.dataManager.margin; + const margin = this.wafermap.dataManager.margin; - const scaledX = this.dataManager.horizontalScale(die.x); - const scaledY = this.dataManager.verticalScale(die.y); + const scaledX = this.wafermap.dataManager.horizontalScale(die.x); + const scaledY = this.wafermap.dataManager.verticalScale(die.y); if (scaledX === undefined || scaledY === undefined) { return null; diff --git a/packages/nimble-components/src/wafer-map/modules/wafer-map-validator.ts b/packages/nimble-components/src/wafer-map/modules/wafer-map-validator.ts index 9ef38a00a0..6788f50149 100644 --- a/packages/nimble-components/src/wafer-map/modules/wafer-map-validator.ts +++ b/packages/nimble-components/src/wafer-map/modules/wafer-map-validator.ts @@ -25,10 +25,10 @@ export class WaferMapValidator { public validateGridDimensions(): boolean { this.invalidGridDimensions = false; if ( - typeof this.wafermap.gridMinX === 'undefined' - && typeof this.wafermap.gridMaxX === 'undefined' - && typeof this.wafermap.gridMinY === 'undefined' - && typeof this.wafermap.gridMaxY === 'undefined' + this.wafermap.gridMinX === undefined + && this.wafermap.gridMaxX === undefined + && this.wafermap.gridMinY === undefined + && this.wafermap.gridMaxY === undefined ) { this.invalidGridDimensions = false; } else if ( @@ -49,38 +49,20 @@ export class WaferMapValidator { if (this.wafermap.diesTable === undefined) { this.invalidDiesTableSchema = false; } else { - const colIndexField = this.wafermap.diesTable.schema.fields.findIndex( - f => f.name === 'colIndex' - ); - const rowIndexField = this.wafermap.diesTable.schema.fields.findIndex( - f => f.name === 'rowIndex' - ); - const valueField = this.wafermap.diesTable.schema.fields.findIndex( - f => f.name === 'value' - ); + const fields = this.wafermap.diesTable.schema.fields; + const colField = fields.find(field => field.name === 'colIndex'); + const rowField = fields.find(field => field.name === 'rowIndex'); + const valueField = fields.find(field => field.name === 'value'); if ( - this.wafermap.diesTable.numCols < 3 - || colIndexField === -1 - || rowIndexField === -1 - || valueField === -1 - || !DataType.isInt( - this.wafermap.diesTable.schema.fields[colIndexField]!.type - ) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - || this.wafermap.diesTable.schema.fields[colIndexField]!.type - .bitWidth !== 32 - || !DataType.isInt( - this.wafermap.diesTable.schema.fields[rowIndexField]!.type - ) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - || this.wafermap.diesTable.schema.fields[rowIndexField]!.type - .bitWidth !== 32 - || !DataType.isFloat( - this.wafermap.diesTable.schema.fields[valueField]!.type - ) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - || this.wafermap.diesTable.schema.fields[valueField]!.type - .precision !== Precision.DOUBLE + !colField + || !rowField + || !valueField + || !DataType.isInt(colField.type) + || colField.type.bitWidth !== 32 + || !DataType.isInt(rowField.type) + || rowField.type.bitWidth !== 32 + || !DataType.isFloat(valueField.type) + || valueField.type.precision !== Precision.DOUBLE ) { this.invalidDiesTableSchema = true; } diff --git a/packages/nimble-components/src/wafer-map/modules/zoom-handler.ts b/packages/nimble-components/src/wafer-map/modules/zoom-handler.ts index 38a82d89b1..79635b403c 100644 --- a/packages/nimble-components/src/wafer-map/modules/zoom-handler.ts +++ b/packages/nimble-components/src/wafer-map/modules/zoom-handler.ts @@ -1,11 +1,5 @@ import { select } from 'd3-selection'; -import { - zoom, - ZoomBehavior, - zoomIdentity, - ZoomTransform, - zoomTransform -} from 'd3-zoom'; +import { zoom, ZoomTransform } from 'd3-zoom'; import type { WaferMap } from '..'; interface ZoomEvent { @@ -16,61 +10,44 @@ interface ZoomEvent { * ZoomHandler deals with user interactions and events like zooming */ export class ZoomHandler { - private zoomTransform: ZoomTransform = zoomIdentity; - private readonly minScale = 1.1; - private readonly minExtentPoint: [number, number] = [-100, -100]; - private readonly extentPadding = 100; - private zoomBehavior!: ZoomBehavior; + private readonly scaleExtent: [number, number] = [1, 100]; + private readonly minExtentPoint: [number, number] = [0, 0]; public constructor(private readonly wafermap: WaferMap) {} - public createZoomBehavior(): void { - this.zoomBehavior = zoom() - .scaleExtent([ - 1.1, - this.getZoomMax( - this.wafermap.canvasWidth * this.wafermap.canvasHeight, - this.wafermap.dataManager.containerDimensions.width - * this.wafermap.dataManager.containerDimensions.height - ) - ]) + /** + * @internal + */ + public connect(): void { + this.createZoomBehavior(); + this.wafermap.addEventListener('wheel', this.onWheelMove, { + passive: false + }); + } + + /** + * @internal + */ + public disconnect(): void { + zoom().on('zoom', null)(select(this.wafermap as Element)); + this.wafermap.removeEventListener('wheel', this.onWheelMove); + } + + private createZoomBehavior(): void { + zoom() + .scaleExtent(this.scaleExtent) .translateExtent([ this.minExtentPoint, - [ - this.wafermap.canvasWidth + this.extentPadding, - this.wafermap.canvasHeight + this.extentPadding - ] + [this.wafermap.canvasWidth, this.wafermap.canvasHeight] ]) - .filter((event: Event) => { - const transform = zoomTransform(this.wafermap.canvas); - const filterEval = transform.k >= this.minScale || event.type === 'wheel'; - return filterEval; - }) .on('zoom', (event: ZoomEvent) => { // D3 will automatically remove existing handlers when adding new ones // See: https://github.com/d3/d3-zoom/blob/v3.0.0/README.md#zoom_on - this.rescale(event); - }); - - this.zoomBehavior(select(this.wafermap.canvas as Element)); + this.wafermap.transform = event.transform; + })(select(this.wafermap as Element)); } - private rescale(event: ZoomEvent): void { - const transform = event.transform; - if (transform.k === this.minScale) { - this.zoomTransform = zoomIdentity; - this.zoomBehavior.transform( - select(this.wafermap.canvas as Element), - zoomIdentity - ); - } else { - this.zoomTransform = transform; - } - - this.wafermap.transform = this.zoomTransform; - } - - private getZoomMax(canvasArea: number, dataArea: number): number { - return Math.ceil((dataArea / canvasArea) * 100); - } + private readonly onWheelMove = (event: Event): void => { + event.preventDefault(); + }; } diff --git a/packages/nimble-components/src/wafer-map/tests/computations.spec.ts b/packages/nimble-components/src/wafer-map/tests/computations.spec.ts index deb673b81c..0f0ead89ef 100644 --- a/packages/nimble-components/src/wafer-map/tests/computations.spec.ts +++ b/packages/nimble-components/src/wafer-map/tests/computations.spec.ts @@ -1,4 +1,3 @@ -import type { WaferMap } from '..'; import { Computations } from '../modules/computations'; import { Margin, WaferMapOriginLocation } from '../types'; import { getWaferMapMockComputations, getWaferMapDies } from './utilities'; @@ -20,7 +19,7 @@ describe('Wafermap Computations module', () => { 100, 100 ); - computationsModule = new Computations(waferMock as WaferMap); + computationsModule = new Computations(waferMock); computationsModule.updateContainerDimensions(); }); @@ -70,7 +69,7 @@ describe('Wafermap Computations module', () => { 200, 100 ); - computationsModule = new Computations(waferMock as WaferMap); + computationsModule = new Computations(waferMock); computationsModule.updateContainerDimensions(); }); @@ -114,7 +113,7 @@ describe('Wafermap Computations module', () => { 100, 100 ); - computationsModule = new Computations(waferMock as WaferMap); + computationsModule = new Computations(waferMock); computationsModule.updateContainerDimensions(); }); @@ -136,7 +135,7 @@ describe('Wafermap Computations module', () => { 100, 100 ); - computationsModule = new Computations(waferMock as WaferMap); + computationsModule = new Computations(waferMock); computationsModule.updateContainerDimensions(); }); @@ -158,7 +157,7 @@ describe('Wafermap Computations module', () => { 100, 100 ); - computationsModule = new Computations(waferMock as WaferMap); + computationsModule = new Computations(waferMock); computationsModule.updateContainerDimensions(); }); @@ -180,7 +179,7 @@ describe('Wafermap Computations module', () => { 100, 100 ); - computationsModule = new Computations(waferMock as WaferMap); + computationsModule = new Computations(waferMock); computationsModule.updateContainerDimensions(); }); diff --git a/packages/nimble-components/src/wafer-map/tests/data-generator.ts b/packages/nimble-components/src/wafer-map/tests/data-generator.ts index fff32e67e5..77cc8efa71 100644 --- a/packages/nimble-components/src/wafer-map/tests/data-generator.ts +++ b/packages/nimble-components/src/wafer-map/tests/data-generator.ts @@ -154,6 +154,6 @@ export const generateWaferTableData = ( return tableFromArrays({ colIndex: Int32Array.from(colIndex), rowIndex: Int32Array.from(rowIndex), - value: Float32Array.from(value) + value: Float64Array.from(value) }); }; diff --git a/packages/nimble-components/src/wafer-map/tests/hover-handler.spec.ts b/packages/nimble-components/src/wafer-map/tests/hover-handler.spec.ts new file mode 100644 index 0000000000..7bee0ae2aa --- /dev/null +++ b/packages/nimble-components/src/wafer-map/tests/hover-handler.spec.ts @@ -0,0 +1,114 @@ +import { zoomIdentity } from 'd3-zoom'; +import { tableFromArrays } from 'apache-arrow'; +import { html } from '@microsoft/fast-element'; +import { parameterizeSpec } from '@ni/jasmine-parameterized'; +import { HoverHandler } from '../modules/experimental/hover-handler'; +import { WaferMapOriginLocation } from '../types'; +import { + getDataManagerMockForHover, + getScaleQuantile, + getWaferMapMockHover +} from './utilities'; +import type { WaferMap } from '..'; +import { processUpdates } from '../../testing/async-helpers'; +import { Fixture, fixture } from '../../utilities/tests/fixture'; + +async function setup(): Promise> { + return fixture(html`
`); +} + +describe('HoverHandler', () => { + let element: HTMLDivElement; + let connect: () => Promise; + let disconnect: () => Promise; + let hoverHandler: HoverHandler; + let waferMock: WaferMap; + + beforeEach(async () => { + ({ element, connect, disconnect } = await setup()); + await connect(); + waferMock = getWaferMapMockHover( + tableFromArrays({ + colIndex: Int32Array.from([1, 2, 3]), + rowIndex: Int32Array.from([1, 2, 3]), + value: Float64Array.from([1, 2, 3]) + }), + zoomIdentity, + WaferMapOriginLocation.bottomLeft, + undefined, + getDataManagerMockForHover( + { left: 0, right: 0, top: 0, bottom: 0 }, + getScaleQuantile([1, 11], [1, 2, 3, 4]), + getScaleQuantile([1, 11], [1, 2, 3, 4]) + ), + true + ); + }); + + afterEach(async () => { + await disconnect(); + }); + + const testCases = [ + { + name: WaferMapOriginLocation.bottomLeft, + expectedDie: { index: 1, x: 2, y: 2 } + }, + { + name: WaferMapOriginLocation.topLeft, + expectedDie: { index: 1, x: 2, y: 2 } + }, + { + name: WaferMapOriginLocation.bottomRight, + expectedDie: { index: 1, x: 2, y: 2 } + }, + { + name: WaferMapOriginLocation.topRight, + expectedDie: { index: 1, x: 2, y: 2 } + } + ] as const; + + parameterizeSpec(testCases, (spec, name, value) => { + spec( + `will return the expected index when mouse moved in range from ${name}`, + () => { + waferMock.originLocation = value.name; + hoverHandler = new HoverHandler(waferMock); + element.addEventListener('mousemove', event => hoverHandler.onMouseMove(event)); + element.dispatchEvent( + new MouseEvent('mousemove', { + clientX: 4, + clientY: 4 + }) + ); + processUpdates(); + expect(waferMock.hoverDie).toEqual(value.expectedDie); + } + ); + }); + + const undefinedTestCases = [ + { name: WaferMapOriginLocation.bottomLeft, expectedDie: undefined }, + { name: WaferMapOriginLocation.topLeft, expectedDie: undefined }, + { name: WaferMapOriginLocation.bottomRight, expectedDie: undefined }, + { name: WaferMapOriginLocation.topRight, expectedDie: undefined } + ] as const; + parameterizeSpec(undefinedTestCases, (spec, name, value) => { + spec( + `will return undefined when mouse moved out of range from ${name}`, + () => { + waferMock.originLocation = value.name; + hoverHandler = new HoverHandler(waferMock); + element.addEventListener('mousemove', event => hoverHandler.onMouseMove(event)); + element.dispatchEvent( + new MouseEvent('mousemove', { + clientX: 15, + clientY: 15 + }) + ); + processUpdates(); + expect(waferMock.hoverDie).toEqual(value.expectedDie); + } + ); + }); +}); diff --git a/packages/nimble-components/src/wafer-map/tests/prerendering.coloring.spec.ts b/packages/nimble-components/src/wafer-map/tests/prerendering.coloring.spec.ts index 5cfb169a65..2b93f87e82 100644 --- a/packages/nimble-components/src/wafer-map/tests/prerendering.coloring.spec.ts +++ b/packages/nimble-components/src/wafer-map/tests/prerendering.coloring.spec.ts @@ -1,5 +1,3 @@ -import type { WaferMap } from '..'; -import type { DataManager } from '../modules/data-manager'; import { Prerendering } from '../modules/prerendering'; import { WaferMapColorScaleMode } from '../types'; import { @@ -28,6 +26,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: ['red'], values: ['1'] }, @@ -35,18 +39,10 @@ describe('Wafermap Prerendering module', () => { colorScaleMode, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -70,6 +66,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { @@ -80,18 +82,10 @@ describe('Wafermap Prerendering module', () => { colorScaleMode, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -115,6 +109,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { @@ -125,18 +125,10 @@ describe('Wafermap Prerendering module', () => { colorScaleMode, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -167,6 +159,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: ['red'], values: ['1'] }, @@ -174,18 +172,10 @@ describe('Wafermap Prerendering module', () => { colorScaleMode, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -209,6 +199,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { @@ -219,18 +215,10 @@ describe('Wafermap Prerendering module', () => { colorScaleMode, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -260,6 +248,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + getScaleBand([0, 1], [0, 100]), + getScaleBand([0, 1], [0, 100]) + ); const waferMock = getWaferMapMockPrerendering( [ { @@ -273,18 +267,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - getScaleBand([0, 1], [0, 100]), - getScaleBand([0, 1], [0, 100]) - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -308,6 +294,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + getScaleBand([0, 1], [0, 100]), + getScaleBand([0, 1], [0, 100]) + ); const waferMock = getWaferMapMockPrerendering( [ { @@ -321,18 +313,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - getScaleBand([0, 1], [0, 100]), - getScaleBand([0, 1], [0, 100]) - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -356,6 +340,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: ['red'], values: [] }, @@ -363,18 +353,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.ordinal, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -405,6 +387,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: ['red'], values: [] }, @@ -412,18 +400,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.ordinal, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); diff --git a/packages/nimble-components/src/wafer-map/tests/prerendering.labeling.spec.ts b/packages/nimble-components/src/wafer-map/tests/prerendering.labeling.spec.ts index 60c98db5b8..fb3d5ffdf7 100644 --- a/packages/nimble-components/src/wafer-map/tests/prerendering.labeling.spec.ts +++ b/packages/nimble-components/src/wafer-map/tests/prerendering.labeling.spec.ts @@ -1,5 +1,3 @@ -import type { WaferMap } from '..'; -import type { DataManager } from '../modules/data-manager'; import { Prerendering } from '../modules/prerendering'; import { WaferMapColorScaleMode } from '../types'; import { @@ -24,6 +22,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: [], values: [] }, @@ -31,18 +35,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -68,6 +64,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: [], values: [] }, @@ -75,18 +77,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -106,6 +100,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: [], values: [] }, @@ -113,18 +113,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -148,6 +140,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: [], values: [] }, @@ -155,18 +153,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -191,6 +181,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: [], values: [] }, @@ -198,18 +194,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -236,6 +224,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDiesAsNaN(), { colors: [], values: [] }, @@ -243,18 +237,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -281,6 +267,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDiesAsFloats(), { colors: [], values: [] }, @@ -288,18 +280,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -330,6 +314,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 0, right: 0, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: [], values: [] }, @@ -337,18 +327,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); diff --git a/packages/nimble-components/src/wafer-map/tests/prerendering.positioning.spec.ts b/packages/nimble-components/src/wafer-map/tests/prerendering.positioning.spec.ts index a54606c526..8b65558c50 100644 --- a/packages/nimble-components/src/wafer-map/tests/prerendering.positioning.spec.ts +++ b/packages/nimble-components/src/wafer-map/tests/prerendering.positioning.spec.ts @@ -1,5 +1,3 @@ -import type { WaferMap } from '..'; -import type { DataManager } from '../modules/data-manager'; import { Prerendering } from '../modules/prerendering'; import { WaferMapColorScaleMode } from '../types'; import { @@ -23,6 +21,12 @@ describe('Wafermap Prerendering module', () => { const margin = { top: 20, right: 10, bottom: 0, left: 0 }; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: [], values: [] }, @@ -30,18 +34,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -83,6 +79,12 @@ describe('Wafermap Prerendering module', () => { const highlightedTags: string[] = []; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + horizontalScale, + defaultVerticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: [], values: [] }, @@ -90,18 +92,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - horizontalScale, - defaultVerticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); @@ -132,6 +126,12 @@ describe('Wafermap Prerendering module', () => { const highlightedTags: string[] = []; beforeEach(() => { + const dataManagerMock = getDataManagerMock( + dieDimensions, + margin, + defaultHorizontalScale, + verticalScale + ); const waferMock = getWaferMapMockPrerendering( getWaferMapDies(), { colors: [], values: [] }, @@ -139,18 +139,10 @@ describe('Wafermap Prerendering module', () => { WaferMapColorScaleMode.linear, dieLabelsHidden, dieLabelsSuffix, - maxCharacters - ); - const dataManagerMock = getDataManagerMock( - dieDimensions, - margin, - defaultHorizontalScale, - verticalScale - ); - prerenderingModule = new Prerendering( - waferMock as WaferMap, - dataManagerMock as DataManager + maxCharacters, + dataManagerMock ); + prerenderingModule = new Prerendering(waferMock); prerenderingModule.updateLabelsFontSize(); }); diff --git a/packages/nimble-components/src/wafer-map/tests/sets.ts b/packages/nimble-components/src/wafer-map/tests/sets.ts index 3b26320e4c..80089b35ee 100644 --- a/packages/nimble-components/src/wafer-map/tests/sets.ts +++ b/packages/nimble-components/src/wafer-map/tests/sets.ts @@ -105,7 +105,7 @@ export const wafermapDiesTableSets: Table[] = [ tableFromArrays({ colIndex: Int32Array.from([0, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4]), rowIndex: Int32Array.from([2, 2, 1, 3, 2, 1, 0, 3, 4, 2, 1, 3, 2]), - value: Float32Array.from([ + value: Float64Array.from([ 14.24, 76.43, 44.63, 67.93, 72.71, 79.04, 26.49, 37.79, 59.82, 52.9, 98.5, 20.83, 62.8 ]), diff --git a/packages/nimble-components/src/wafer-map/tests/utilities.ts b/packages/nimble-components/src/wafer-map/tests/utilities.ts index f654e3b9bf..f30aa122dc 100644 --- a/packages/nimble-components/src/wafer-map/tests/utilities.ts +++ b/packages/nimble-components/src/wafer-map/tests/utilities.ts @@ -1,7 +1,9 @@ -import { ScaleBand, scaleBand } from 'd3-scale'; +import { ScaleBand, ScaleQuantile, scaleBand, scaleQuantile } from 'd3-scale'; import type { Table } from 'apache-arrow'; +import type { ZoomTransform } from 'd3-zoom'; import { Dimensions, + HoverDie, Margin, WaferMapColorScale, WaferMapColorScaleMode, @@ -64,6 +66,12 @@ export function getScaleBand( return scaleBand().domain(domain).range(range); } +export function getScaleQuantile( + domain: number[] = [], + range: number[] = [] +): ScaleQuantile { + return scaleQuantile().domain(domain).range(range); +} export const defaultHorizontalScale = scaleBand() .domain([2, 3, 4, 5, 6]) .range([2, 7]); @@ -77,16 +85,39 @@ export function getDataManagerMock( margin: Margin, horizontalScale: ScaleBand = getScaleBand([], []), verticalScale: ScaleBand = getScaleBand([], []) -): Pick< +): DataManager { + const dataManagerMock: Pick< DataManager, 'horizontalScale' | 'verticalScale' | 'dieDimensions' | 'margin' - > { - return { + > = { horizontalScale, verticalScale, dieDimensions, margin }; + return dataManagerMock as DataManager; +} + +export function getDataManagerMockForHover( + margin: Margin, + invertedHorizontalScale: ScaleQuantile = getScaleQuantile( + [], + [] + ), + invertedVerticalScale: ScaleQuantile = getScaleQuantile( + [], + [] + ) +): DataManager { + const dataManagerMock: Pick< + DataManager, + 'invertedHorizontalScale' | 'invertedVerticalScale' | 'margin' + > = { + invertedHorizontalScale, + invertedVerticalScale, + margin + }; + return dataManagerMock as DataManager; } export function getWaferMapMockPrerendering( @@ -96,8 +127,10 @@ export function getWaferMapMockPrerendering( colorScaleMode: WaferMapColorScaleMode = WaferMapColorScaleMode.linear, dieLabelsHidden = true, dieLabelsSuffix = '', - maxCharacters = 4 -): Pick< + maxCharacters = 4, + dataManager = {} as DataManager +): WaferMap { + const waferMapMock: Pick< WaferMap, | 'dies' | 'colorScale' @@ -106,18 +139,46 @@ export function getWaferMapMockPrerendering( | 'dieLabelsHidden' | 'dieLabelsSuffix' | 'maxCharacters' - > { - return { + | 'dataManager' + > = { dies, colorScale, highlightedTags, colorScaleMode, dieLabelsHidden, dieLabelsSuffix, - maxCharacters + maxCharacters, + dataManager }; + return waferMapMock as WaferMap; } +export function getWaferMapMockHover( + diesTable: Table, + transform: ZoomTransform, + originLocation: WaferMapOriginLocation, + hoverDie: HoverDie | undefined, + dataManager: DataManager, + isExperimentalRenderer: boolean +): WaferMap { + const waferMapMock: Pick< + WaferMap, + | 'diesTable' + | 'transform' + | 'originLocation' + | 'hoverDie' + | 'dataManager' + | 'isExperimentalRenderer' + > = { + diesTable, + transform, + originLocation, + hoverDie, + dataManager, + isExperimentalRenderer: () => isExperimentalRenderer + }; + return waferMapMock as WaferMap; +} export function getWaferMapMockComputations( dies: WaferMapDie[] = getWaferMapDies(), originLocation: WaferMapOriginLocation, @@ -127,17 +188,18 @@ export function getWaferMapMockComputations( invalidGridDimensions: false, invalidDiesTableSchema: false } -): Pick< +): WaferMap { + const waferMapMock: Pick< WaferMap, 'dies' | 'originLocation' | 'canvasWidth' | 'canvasHeight' | 'validity' - > { - return { + > = { dies, originLocation, canvasWidth, canvasHeight, validity }; + return waferMapMock as WaferMap; } export function getWaferMapMockValidator( @@ -146,15 +208,16 @@ export function getWaferMapMockValidator( gridMinY: number | undefined, gridMaxY: number | undefined, diesTable: Table | undefined = undefined -): Pick< +): WaferMap { + const waferMapMock: Pick< WaferMap, 'gridMinX' | 'gridMaxX' | 'gridMinY' | 'gridMaxY' | 'diesTable' - > { - return { + > = { gridMinX, gridMaxX, gridMinY, gridMaxY, diesTable }; + return waferMapMock as WaferMap; } diff --git a/packages/nimble-components/src/wafer-map/tests/wafer-map-validator.spec.ts b/packages/nimble-components/src/wafer-map/tests/wafer-map-validator.spec.ts index 732833fa9a..659fbee23c 100644 --- a/packages/nimble-components/src/wafer-map/tests/wafer-map-validator.spec.ts +++ b/packages/nimble-components/src/wafer-map/tests/wafer-map-validator.spec.ts @@ -1,5 +1,4 @@ import { Table, tableFromArrays } from 'apache-arrow'; -import type { WaferMap } from '..'; import { WaferMapValidator } from '../modules/wafer-map-validator'; import { getWaferMapMockValidator } from './utilities'; @@ -13,7 +12,7 @@ describe('Wafermap Validator module', () => { undefined, undefined ); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateGridDimensions(); expect(waferMapValidator.isValid()).toBeTrue(); @@ -21,7 +20,7 @@ describe('Wafermap Validator module', () => { it('with equal grid dimensions should be valid', () => { const waferMock = getWaferMapMockValidator(0, 0, 0, 0); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateGridDimensions(); expect(waferMapValidator.isValid()).toBeTrue(); @@ -29,7 +28,7 @@ describe('Wafermap Validator module', () => { it('with positive grid dimensions should be valid', () => { const waferMock = getWaferMapMockValidator(1, 2, 1, 2); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateGridDimensions(); expect(waferMapValidator.isValid()).toBeTrue(); @@ -37,7 +36,7 @@ describe('Wafermap Validator module', () => { it('with negative grid dimensions should be valid', () => { const waferMock = getWaferMapMockValidator(-2, -1, -2, -1); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateGridDimensions(); expect(waferMapValidator.isValid()).toBeTrue(); @@ -45,7 +44,7 @@ describe('Wafermap Validator module', () => { it('with one undefined grid dimension should not be valid', () => { const waferMock = getWaferMapMockValidator(0, 0, 0, undefined); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateGridDimensions(); expect(waferMapValidator.isValid()).toBeFalse(); @@ -53,7 +52,7 @@ describe('Wafermap Validator module', () => { it('with impossible grid dimension should not be valid', () => { const waferMock = getWaferMapMockValidator(1, -1, 1, -1); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateGridDimensions(); expect(waferMapValidator.getValidity()).toEqual({ @@ -71,7 +70,7 @@ describe('Wafermap Validator module', () => { undefined, undefined ); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateDiesTableSchema(); expect(waferMapValidator.isValid()).toBeTrue(); }); @@ -88,7 +87,7 @@ describe('Wafermap Validator module', () => { value: Float64Array.from([]) }) ); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateDiesTableSchema(); expect(waferMapValidator.isValid()).toBeTrue(); @@ -106,7 +105,7 @@ describe('Wafermap Validator module', () => { value: Float32Array.from([]) }) ); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateDiesTableSchema(); expect(waferMapValidator.isValid()).toBeFalse(); @@ -124,7 +123,7 @@ describe('Wafermap Validator module', () => { value: Float64Array.from([]) }) ); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateDiesTableSchema(); expect(waferMapValidator.isValid()).toBeFalse(); @@ -142,7 +141,7 @@ describe('Wafermap Validator module', () => { value: Int32Array.from([]) }) ); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateDiesTableSchema(); expect(waferMapValidator.isValid()).toBeFalse(); @@ -156,7 +155,7 @@ describe('Wafermap Validator module', () => { undefined, new Table() ); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateDiesTableSchema(); expect(waferMapValidator.isValid()).toBeFalse(); @@ -172,7 +171,7 @@ describe('Wafermap Validator module', () => { rowIndex: Int32Array.from([]) }) ); - waferMapValidator = new WaferMapValidator(waferMock as WaferMap); + waferMapValidator = new WaferMapValidator(waferMock); waferMapValidator.validateDiesTableSchema(); expect(waferMapValidator.isValid()).toBeFalse(); 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 f811df6fce..e443b632ee 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 @@ -9,7 +9,7 @@ import { WaferMapOriginLocation } from '../types'; import { RenderingModule } from '../modules/rendering'; -import { WorkerRenderer } from '../modules/worker-renderer'; +import { WorkerRenderer } from '../modules/experimental/worker-renderer'; async function setup(): Promise> { return fixture(html``); @@ -88,24 +88,12 @@ describe('WaferMap', () => { expect(spy).toHaveBeenCalledTimes(1); }); - it('will use RenderingModule after dies change', () => { - element.dies = [{ x: 1, y: 1, value: '1' }]; - processUpdates(); - expect(element.renderer instanceof RenderingModule).toBeTrue(); - }); - it('will update once after diesTable change', () => { element.diesTable = new Table(); processUpdates(); expect(spy).toHaveBeenCalledTimes(1); }); - it('will use WorkerRenderer after diesTable change', () => { - element.diesTable = new Table(); - processUpdates(); - expect(element.renderer instanceof WorkerRenderer).toBeTrue(); - }); - it('will update once after colorScale changes', () => { element.colorScale = { colors: ['red', 'red'], values: ['1', '1'] }; processUpdates(); @@ -158,6 +146,28 @@ describe('WaferMap', () => { renderHoverSpy = spyOn(element.workerRenderer, 'renderHover'); }); + it('will use RenderingModule after dies change', () => { + element.dies = [{ x: 1, y: 1, value: '1' }]; + processUpdates(); + expect(element.renderer instanceof RenderingModule).toBeTrue(); + }); + + it('will use WorkerRenderer after supported diesTable change', () => { + element.diesTable = tableFromArrays({ + colIndex: Int32Array.from([]), + rowIndex: Int32Array.from([]), + value: Float64Array.from([]) + }); + processUpdates(); + expect(element.renderer instanceof WorkerRenderer).toBeTrue(); + }); + + it('will use RenderingModule after unsupported diesTable change', () => { + element.diesTable = new Table(); + processUpdates(); + expect(element.renderer instanceof RenderingModule).toBeTrue(); + }); + it('will call renderHover after supported diesTable change', () => { element.diesTable = tableFromArrays({ colIndex: Int32Array.from([]), @@ -191,7 +201,7 @@ describe('WaferMap', () => { }); it('will zoom in the wafer-map', () => { - element.canvas.dispatchEvent( + element.dispatchEvent( new WheelEvent('wheel', { deltaY: -2, deltaMode: -1 }) ); processUpdates(); @@ -200,7 +210,7 @@ describe('WaferMap', () => { }); it('will zoom out to identity', () => { - element.canvas.dispatchEvent( + element.dispatchEvent( new WheelEvent('wheel', { deltaY: -2, deltaMode: -1 }) ); @@ -208,7 +218,7 @@ describe('WaferMap', () => { const zoomedValue = getTransform(); expect(zoomedValue).not.toEqual(initialValue); - element.canvas.dispatchEvent( + element.dispatchEvent( new WheelEvent('wheel', { deltaY: 2, deltaMode: -1 }) ); @@ -218,7 +228,7 @@ describe('WaferMap', () => { }); it('will not zoom out when at identity', () => { - element.canvas.dispatchEvent( + element.dispatchEvent( new WheelEvent('wheel', { deltaY: 2, deltaMode: -1 }) ); processUpdates(); @@ -258,7 +268,7 @@ describe('WaferMap', () => { expect(initialHeight).toBe(460); expect(initialWidth).toBe(460); - element.canvas.dispatchEvent( + element.dispatchEvent( new WheelEvent('wheel', { deltaY: -2, deltaMode: -1 }) ); processUpdates(); @@ -277,7 +287,7 @@ describe('WaferMap', () => { processUpdates(); const initialTransform = element.hoverTransform; expect(initialTransform).not.toEqual(''); - element.canvas.dispatchEvent( + element.dispatchEvent( new WheelEvent('wheel', { deltaY: -2, deltaMode: -1 }) ); processUpdates(); diff --git a/packages/nimble-components/src/wafer-map/tests/wafer-map.stories.ts b/packages/nimble-components/src/wafer-map/tests/wafer-map.stories.ts index 2cd7f02974..c0b82ce309 100644 --- a/packages/nimble-components/src/wafer-map/tests/wafer-map.stories.ts +++ b/packages/nimble-components/src/wafer-map/tests/wafer-map.stories.ts @@ -81,7 +81,7 @@ const getDiesSet = ( )!; break; default: - returnedValue = [] as WaferMapDie[]; + returnedValue = []; } return returnedValue; }; @@ -133,7 +133,7 @@ const getHighlightedTags = (setName: string, sets: string[][]): string[] => { returnedValue = sets[3]!; break; default: - returnedValue = [] as string[]; + returnedValue = []; break; } return returnedValue; diff --git a/packages/nimble-components/src/wafer-map/types.ts b/packages/nimble-components/src/wafer-map/types.ts index cac887b0a0..7193593c4f 100644 --- a/packages/nimble-components/src/wafer-map/types.ts +++ b/packages/nimble-components/src/wafer-map/types.ts @@ -1,3 +1,5 @@ +import type { Float64, Int32 } from 'apache-arrow'; + export const WaferMapOriginLocation = { bottomLeft: 'bottom-left', bottomRight: 'bottom-right', @@ -43,6 +45,12 @@ export interface WaferMapDie { tags?: string[]; } +export interface HoverDie { + index: number; + x: number; + y: number; +} + export interface WaferMapColorScale { colors: string[]; values: string[]; @@ -79,3 +87,16 @@ export interface WaferMapValidity extends ValidityObject { readonly invalidGridDimensions: boolean; readonly invalidDiesTableSchema: boolean; } + +// Apache arrow probably should not be using a Record and index types on TypeMap +// because in strict checking they end up required. +// See: https://github.com/apache/arrow/issues/12663#issuecomment-1088244575 +// We can work around that issue by using a type alias instead of an interface +// Where index signatures are looser. +// See: https://github.com/microsoft/TypeScript/issues/15300#issuecomment-1317901527 +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type WaferRequiredFields = { + colIndex: Int32, + rowIndex: Int32, + value: Float64 +};