From ef0590b4ee65bb526650f22ab0df32f829c85e13 Mon Sep 17 00:00:00 2001 From: Kevin De Baerdemaeker Date: Mon, 26 Jun 2023 18:40:02 +0200 Subject: [PATCH] chore: enable Typescript's strict mode (#648) * chore(typescript): enable strict mode * chore(typescript): ignore pixi plugin code * fix(utils): fix/escape typescript errors * chore(tests): add optional chaining to accessors * chore(components): fix/escape typescript errors Mostly ignoring undefined errors using `!`, to speed up transitioning to strict types, and fixing the actual types on ad-hoc basis. * chore(base-layers): fix typescript errors related to undefuned/nulls * fix(layers-displayobjects): fix/escape typescript undefined errors * chore(layers): fix/escape typescript errors * chore(InterSectionReferenceSystem): remove unused properties * chore(GeomodelLabelsLayer): fix typescript optional parameters * fix(eslint): run eslint --fix on the codebase * docs(eslint): add comment with regard to null-assertion warnings * fix: swap null check and isFinite check * Create thin-experts-confess.md * Update thin-experts-confess.md --- .changeset/thin-experts-confess.md | 5 + .eslintrc.json | 49 +++-- src/components/axis.ts | 53 +++-- src/control/ExtendedCurveInterpolator.ts | 14 +- src/control/IntersectionReferenceSystem.ts | 74 +++---- src/control/LayerManager.ts | 64 +++--- src/control/MainController.ts | 12 +- src/control/ZoomPanHandler.ts | 95 +++++---- src/control/interfaces.ts | 6 +- src/control/overlay.ts | 36 ++-- src/datautils/colortable.ts | 8 +- src/datautils/findsample.ts | 15 +- src/datautils/picks.ts | 26 +-- src/datautils/schematicShapeGenerator.ts | 64 ++++-- src/datautils/seismicimage.ts | 25 ++- src/datautils/surfacedata.ts | 53 ++--- src/datautils/trajectory.ts | 66 +++--- src/interfaces.ts | 2 +- src/layers/CalloutCanvasLayer.ts | 113 +++++----- .../ComplexRopeGeometry.ts | 43 ++-- .../FixedWidthSimpleRopeGeometry.ts | 19 +- .../UniformTextureStretchRopeGeometry.ts | 26 +-- src/layers/GeomodelCanvasLayer.ts | 79 +++---- src/layers/GeomodelLabelsLayer.ts | 195 +++++++++--------- src/layers/GeomodelLayerV2.ts | 22 +- src/layers/GridLayer.ts | 46 +++-- src/layers/ImageCanvasLayer.ts | 28 ++- src/layers/ReferenceLineLayer.ts | 98 +++++---- src/layers/SchematicLayer.ts | 153 +++++++------- src/layers/WellborePathLayer.ts | 94 ++++----- src/layers/base/CanvasLayer.ts | 36 ++-- src/layers/base/HTMLLayer.ts | 10 +- src/layers/base/Layer.ts | 36 ++-- src/layers/base/PixiLayer.ts | 44 ++-- src/layers/base/SVGLayer.ts | 10 +- src/utils/arc-length.ts | 13 +- src/utils/binary-search.ts | 4 +- src/utils/color.ts | 14 +- src/utils/root-finder.ts | 8 +- src/utils/text.ts | 10 +- src/utils/vectorUtils.ts | 19 +- src/vendor/pixi-dashed-line/index.ts | 5 +- test/callout-canvas-layer.test.ts | 11 +- test/callout.test.ts | 8 +- test/html-layer.test.ts | 12 +- test/layer.test.ts | 8 +- test/reference-system.test.ts | 28 +-- test/schematicShapeGenerator.test.ts | 1 - test/svg-layer.test.ts | 12 +- test/vectorUtils.test.ts | 12 +- tsconfig.base.json | 4 +- 51 files changed, 985 insertions(+), 903 deletions(-) create mode 100644 .changeset/thin-experts-confess.md diff --git a/.changeset/thin-experts-confess.md b/.changeset/thin-experts-confess.md new file mode 100644 index 00000000..6bb29fb4 --- /dev/null +++ b/.changeset/thin-experts-confess.md @@ -0,0 +1,5 @@ +--- +"@equinor/esv-intersection": patch +--- + +chore: enable Typescript's strict mode & fix 1500 errors diff --git a/.eslintrc.json b/.eslintrc.json index c42f6bcb..dc0574cd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,27 +13,36 @@ "SharedArrayBuffer": "readonly" }, "rules": { - "prettier/prettier": "error", - - "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/ban-ts-comment": "warn", + "@typescript-eslint/no-namespace": "warn", "@typescript-eslint/no-unused-vars": ["error", { "args": "all", "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }], - "@typescript-eslint/no-use-before-define": "off", - "curly": "error", - "no-continue": "off", - "no-plusplus": "off", - "no-param-reassign": "off", - "object-curly-newline": "off", - "no-underscore-dangle": "off", - "quotes": ["error", "single"], - "import/prefer-default-export": "off", - "max-len": ["error", { "code": 150 }], - "comma-dangle": ["error", "always-multiline"], - "eqeqeq": ["error", "always", { "null": "ignore" }], - "no-magic-numbers": ["error", { "ignore": [-1, 0, 0.5, 1, 2, 3, 4, 8, 16, 100, 255, 1000], "ignoreDefaultValues": true }], - "newline-per-chained-call": ["off", { "ignoreChainWithDepth": 1 }], - "no-warning-comments": [0, { "terms": ["todo", "fixme"], "location": "start" }] + /* NOTE(Kevin): Currently needed after enabling strict mode, as there were over 1500 Typescript errors. + * + * Fixing all these errors properly, would + * 1) result in a necessary api changes with regards to most of the public interfaces + * 2) require drastic changes in implementation details, with an even higher risk + * of introducing regressions. + * + * Therefore I opted to make minimal implementation detail changes in attempt to not + * break existing code, while still adhering to Typescript's strict mode. + + * But why enable strict mode? + * We've seen a couple bugs now that could have been prevented with strict mode. So + * everywhere in the code where there isn't heavily escaped code with `?` or `!` to + * force a `fake, correct type` the Typescript compiler will catch those mistakes. + * However, type mismatches might still happen in these cases where there's heavy use + * of `?` and `!` and those typing mistakes were already existing bugs, that were + * waiting to be fixed regardless. Once these bug surface in these areas, it's desired + * to remove the `!` and properly fix the types on a case by case basis. + + * These eslint below are metrics in form of warnings on how these are progressing. + * The initial warning amount is 297 warnings, slowly this number should be decreasing, + * as usage of `!` is not recommended and an escape hatch we needed in order to enable + * strict typing on all the other areas of the codebase. + * + */ + "@typescript-eslint/no-non-null-assertion": "warn", + "@typescript-eslint/no-non-null-asserted-optional-chain": "warn" } } diff --git a/src/components/axis.ts b/src/components/axis.ts index 2f07d9ea..1bf20cff 100644 --- a/src/components/axis.ts +++ b/src/components/axis.ts @@ -1,5 +1,5 @@ import { axisRight, axisBottom } from 'd3-axis'; -import { BaseType, Selection } from 'd3-selection'; +import { Selection } from 'd3-selection'; import { ScaleLinear, scaleLinear } from 'd3-scale'; import { OnResizeEvent, OnRescaleEvent } from '../interfaces'; @@ -10,27 +10,20 @@ export type Options = { }; export class Axis { - private mainGroup: Selection; + private mainGroup: Selection; private _scaleX: ScaleLinear; private _scaleY: ScaleLinear; private _showLabels = true; private _labelXDesc: string; private _labelYDesc: string; private _unitOfMeasure: string; - private _offsetX: number = 0; - private _offsetY: number = 0; + private _offsetX = 0; + private _offsetY = 0; private _flipX = false; private _flipY = false; - private visible: boolean = true; - - constructor( - mainGroup: Selection, - showLabels = true, - labelXDesc: string, - labelYDesc: string, - unitOfMeasure: string, - options?: Options, - ) { + private visible = true; + + constructor(mainGroup: Axis['mainGroup'], showLabels = true, labelXDesc: string, labelYDesc: string, unitOfMeasure: string, options?: Options) { this.mainGroup = mainGroup; this._showLabels = showLabels; this._labelXDesc = labelXDesc; @@ -51,12 +44,12 @@ export class Axis { this._scaleY = scaleLinear().domain([0, 1]).range([0, 1]); } - private renderLabelx(): Selection { + private renderLabelx(): Selection { const { _labelXDesc: labelXDesc, _unitOfMeasure: unitOfMeasure, _showLabels, _scaleX: scaleX } = this; const [, width] = scaleX.range(); const gx = this.renderGx(); - let labelx = gx.select('text.axis-labelx'); + let labelx: Selection = gx.select('text.axis-labelx'); if (_showLabels) { if (labelx.empty()) { labelx = gx @@ -71,16 +64,16 @@ export class Axis { } else { labelx.remove(); } - labelx.attr('transform', `translate(${width / 2},-4)`); + labelx.attr('transform', `translate(${width! / 2},-4)`); return labelx; } - private renderLabely(): Selection { + private renderLabely(): Selection { const { _labelYDesc: labelYDesc, _unitOfMeasure: unitOfMeasure, _showLabels, _scaleY } = this; const [, height] = _scaleY.range(); const gy = this.renderGy(); - let labely = gy.select('text.axis-labely'); + let labely: Selection = gy.select('text.axis-labely'); if (_showLabels) { if (labely.empty()) { labely = gy @@ -92,16 +85,16 @@ export class Axis { .style('font-size', '10px') .text(`${labelYDesc} (${unitOfMeasure})`); } - labely.attr('transform', `translate(-10,${height / 2})rotate(90)`); + labely.attr('transform', `translate(-10,${height! / 2})rotate(90)`); } else { labely.remove(); } return labely; } - private renderGy(): Selection { + private renderGy(): Selection { const { _scaleX, _scaleY } = this; - const yAxis = axisRight(_scaleY) as (selection: Selection) => void; + const yAxis = axisRight(_scaleY) as (selection: Selection, ...args: any[]) => void; const [, width] = _scaleX.range(); const gy = this.createOrGet('y-axis'); gy.call(yAxis); @@ -110,9 +103,9 @@ export class Axis { return gy; } - private renderGx(): Selection { + private renderGx(): Selection { const { _scaleX, _scaleY } = this; - const xAxis = axisBottom(_scaleX) as (selection: Selection) => void; + const xAxis = axisBottom(_scaleX) as (selection: Selection, ...args: any[]) => void; const [, height] = _scaleY.range(); const gx = this.createOrGet('x-axis'); @@ -121,9 +114,9 @@ export class Axis { return gx; } - private createOrGet = (name: string): Selection => { + private createOrGet = (name: string): Selection => { const { mainGroup } = this; - let res = mainGroup.select(`g.${name}`); + let res: Selection = mainGroup.select(`g.${name}`); if (res.empty()) { res = mainGroup.append('g').attr('class', name); } @@ -142,8 +135,8 @@ export class Axis { onRescale(event: OnRescaleEvent): void { const { _scaleX, _scaleY, offsetX, offsetY } = this; const { xScale, yScale } = event; - const xBounds = xScale.domain(); - const yBounds = yScale.domain(); + const xBounds = xScale.domain() as [number, number]; + const yBounds = yScale.domain() as [number, number]; const xRange = xScale.range(); const yRange = yScale.range(); @@ -173,7 +166,7 @@ export class Axis { flipX(flipX: boolean): Axis { this._flipX = flipX; - const domain = this._scaleX.domain(); + const domain = this._scaleX.domain() as [number, number]; const flip = flipX ? -1 : 1; this._scaleX.domain([flip * domain[0], flip * domain[1]]); return this; @@ -181,7 +174,7 @@ export class Axis { flipY(flipY: boolean): Axis { this._flipY = flipY; - const domain = this._scaleY.domain(); + const domain = this._scaleY.domain() as [number, number]; const flip = flipY ? -1 : 1; this._scaleY.domain([flip * domain[0], flip * domain[1]]); return this; diff --git a/src/control/ExtendedCurveInterpolator.ts b/src/control/ExtendedCurveInterpolator.ts index ece8e1fe..bc7e39bf 100644 --- a/src/control/ExtendedCurveInterpolator.ts +++ b/src/control/ExtendedCurveInterpolator.ts @@ -78,13 +78,13 @@ export class ExtendedCurveInterpolator extends CurveInterpolator { this.generateArcLengthLookup(); } const index = BinarySearch.search(this.arcLengthLookup, arcLength); - const v1 = this.arcLengthLookup[index]; - const v2 = this.arcLengthLookup[index + 1]; + const v1 = this.arcLengthLookup[index]!; + const v2 = this.arcLengthLookup[index + 1]!; const t = (index + (arcLength - v1) / (v2 - v1)) / this.arcLengthLookup.length; return t; } - generateArcLengthLookup(segments: number = 1000): void { + generateArcLengthLookup(segments = 1000): void { let lastPos = this.getPointAt(0); let length = 0; for (let i = 0; i < segments; i++) { @@ -123,15 +123,15 @@ export class ExtendedCurveInterpolator extends CurveInterpolator { if (from !== 0) { const fromIndex = Math.floor(from * this.arcLengthLookup.length); - const fromLl = this.arcLengthLookup[fromIndex]; - const fromLh = this.arcLengthLookup[fromIndex + 1]; + const fromLl = this.arcLengthLookup[fromIndex]!; + const fromLh = this.arcLengthLookup[fromIndex + 1]!; fromLength = fromLl + ((from * this.arcLengthLookup.length) % this.arcLengthLookup.length) * (fromLh - fromLl); } if (to !== 1) { const toIndex = Math.floor(to * this.arcLengthLookup.length); - const toLl = this.arcLengthLookup[toIndex]; - const toLh = this.arcLengthLookup[toIndex + 1]; + const toLl = this.arcLengthLookup[toIndex]!; + const toLh = this.arcLengthLookup[toIndex + 1]!; toLength = toLl + ((from * this.arcLengthLookup.length) % this.arcLengthLookup.length) * (toLh - toLl); } diff --git a/src/control/IntersectionReferenceSystem.ts b/src/control/IntersectionReferenceSystem.ts index 5cfef60d..75684314 100644 --- a/src/control/IntersectionReferenceSystem.ts +++ b/src/control/IntersectionReferenceSystem.ts @@ -36,31 +36,23 @@ export interface ReferenceSystemOptions { } export class IntersectionReferenceSystem { - options: ReferenceSystemOptions; + options!: ReferenceSystemOptions; path: number[][] = []; projectedPath: number[][] = []; - projectedTrajectory: number[][]; + private _offset = 0; - private _offset: number = 0; + displacement!: number; - displacement: number; + interpolators!: Interpolators; - depthReference: number; + startVector!: number[]; - wellboreId: number; + endVector!: number[]; - trajectoryOffset: number; - - interpolators: Interpolators; - - startVector: number[]; - - endVector: number[]; - - _curtainPathCache: MDPoint[]; + _curtainPathCache: MDPoint[] | undefined; /** * Creates a common reference system that layers and other components can use @@ -93,15 +85,15 @@ export class IntersectionReferenceSystem { this.projectedPath = IntersectionReferenceSystem.toDisplacement(path); - const [displacement] = this.projectedPath[this.projectedPath.length - 1]; - this.displacement = displacement; + const [displacement] = this.projectedPath[this.projectedPath.length - 1]!; + this.displacement = displacement!; this.interpolators = { curve: options.curveInterpolator || new ExtendedCurveInterpolator(path), trajectory: options.trajectoryInterpolator || new ExtendedCurveInterpolator( - path.map((d: number[]) => [d[0], d[1]]), + path.map((d: number[]) => [d[0]!, d[1]!]), { tension: tension || TENSION, arcDivisions: arcDivisions || ARC_DIVISIONS }, ), curtain: @@ -160,7 +152,7 @@ export class IntersectionReferenceSystem { let prevAngle = Math.PI * 2; // Always add first point for (let i = this._offset; i <= this.length + this._offset; i += CURTAIN_SAMPLING_INTERVAL) { const point = this.project(i); - const angle = Math.atan2(point[1], point[0]); + const angle = Math.atan2(point[1]!, point[0]!); // Reduce number of points on a straight line by angle since last point if (Math.abs(angle - prevAngle) > CURTAIN_SAMPLING_ANGLE_THRESHOLD) { @@ -183,7 +175,7 @@ export class IntersectionReferenceSystem { /** * Map a displacement back to length along the curve */ - unproject(displacement: number): number { + unproject(displacement: number): number | undefined { const { normalizedLength, calculateDisplacementFromBottom } = this.options; const displacementFromStart = calculateDisplacementFromBottom ? this.displacement - displacement : displacement; const length = normalizedLength || this.length; @@ -197,9 +189,9 @@ export class IntersectionReferenceSystem { const ls = this.interpolators.curtain.getIntersectsAsPositions(displacementFromStart, 0, 1); if (ls && ls.length) { - return ls[0] * length + this._offset; + return ls[0]! * length + this._offset; } - return null; + return undefined; } /** @@ -208,7 +200,7 @@ export class IntersectionReferenceSystem { getProjectedLength(length: number): number { const { curtain } = this.interpolators; const pl = this.project(length); - const l = pl[0] / curtain.maxX; + const l = pl[0]! / curtain.maxX; return Number.isFinite(l) ? clamp(l, 0, 1) : 0; } @@ -229,21 +221,21 @@ export class IntersectionReferenceSystem { const extensionStart = from < 0 ? -from : 0; const extensionEnd = to > 1 ? to - 1 : 0; - const refStart = this.interpolators.trajectory.getPointAt(0) as number[]; - const refEnd = this.interpolators.trajectory.getPointAt(1) as number[]; + const refStart = this.interpolators.trajectory.getPointAt(0) as [number, number]; + const refEnd = this.interpolators.trajectory.getPointAt(1) as [number, number]; - let p0; - let p3; + let p0: [number, number]; + let p3: [number, number]; let offset = 0; const t0 = Math.max(0, from); const t1 = Math.min(1, to); - const p1 = this.interpolators.trajectory.getPointAt(t0) as number[]; - const p2 = this.interpolators.trajectory.getPointAt(t1) as number[]; + const p1 = this.interpolators.trajectory.getPointAt(t0) as [number, number]; + const p2 = this.interpolators.trajectory.getPointAt(t1) as [number, number]; if (extensionStart) { p0 = [ - refStart[0] + this.startVector[0] * extensionStart * this.displacement, - refStart[1] + this.startVector[1] * extensionStart * this.displacement, + refStart[0] + this.startVector[0]! * extensionStart * this.displacement, + refStart[1] + this.startVector[1]! * extensionStart * this.displacement, ]; offset = -Vector2.distance(p0, refStart); } else if (from > 0) { @@ -251,7 +243,7 @@ export class IntersectionReferenceSystem { } if (extensionEnd) { - p3 = [refEnd[0] + this.endVector[0] * extensionEnd * this.displacement, refEnd[1] + this.endVector[1] * extensionEnd * this.displacement]; + p3 = [refEnd[0] + this.endVector[0]! * extensionEnd * this.displacement, refEnd[1] + this.endVector[1]! * extensionEnd * this.displacement]; } const points = []; const tl = to - from; @@ -259,19 +251,19 @@ export class IntersectionReferenceSystem { const curveSteps = Math.ceil(((t1 - t0) / tl) * steps); const postSteps = steps - curveSteps - preSteps; - if (p0) { + if (p0!) { points.push(p0); for (let i = 1; i < preSteps; i++) { const f = (i / preSteps) * extensionStart * this.displacement; - points.push([p0[0] - this.startVector[0] * f, p0[1] - this.startVector[1] * f]); + points.push([p0[0] - this.startVector[0]! * f, p0[1] - this.startVector[1]! * f]); } } const curvePoints = this.interpolators.trajectory.getPoints(curveSteps - 1, null, t0, t1) as number[][]; // returns steps + 1 points points.push(...curvePoints); - if (p3) { + if (p3!) { for (let i = 1; i < postSteps - 1; i++) { const f = (i / postSteps) * extensionEnd * this.displacement; - points.push([p2[0] + this.endVector[0] * f, p2[1] + this.endVector[1] * f]); + points.push([p2[0] + this.endVector[0]! * f, p2[1] + this.endVector[1]! * f]); } points.push(p3); } @@ -330,7 +322,7 @@ export class IntersectionReferenceSystem { getTrajectoryVector(): number[] { const { trajectoryAngle, calculateDisplacementFromBottom } = this.options; - if (isFinite(trajectoryAngle)) { + if (trajectoryAngle != null && isFinite(trajectoryAngle)) { const angleInRad = radians(trajectoryAngle); return new Vector2(Math.cos(angleInRad), Math.sin(angleInRad)).toArray(); } @@ -351,11 +343,11 @@ export class IntersectionReferenceSystem { * @returns {array} */ static toDisplacement(points: number[][], offset = 0): number[][] { - let p0: number[] = points[0]; + let p0: number[] = points[0]!; let l = 0; const projected = points.map((p1: number[]) => { - const dx = p1[0] - p0[0]; - const dy = p1[1] - p0[1]; + const dx = p1[0]! - p0[0]!; + const dy = p1[1]! - p0[1]!; l += Math.sqrt(dx ** 2 + dy ** 2); p0 = p1; return [offset > 0 ? offset - l : l, p1[2] || 0]; @@ -377,7 +369,7 @@ export class IntersectionReferenceSystem { } get length(): number { - return this.interpolators.curve.length; + return this.interpolators.curve?.length ?? 0; } get offset(): number { diff --git a/src/control/LayerManager.ts b/src/control/LayerManager.ts index f727a86d..fa59bd83 100644 --- a/src/control/LayerManager.ts +++ b/src/control/LayerManager.ts @@ -16,8 +16,8 @@ export class LayerManager { private layers: Layer[] = []; - private _axis: Axis; - private _svgContainer: Selection; + private _axis: Axis | undefined; + private _svgContainer: Selection | undefined; /** * Handles layers and axis also holds a zoom and pan handler object @@ -30,7 +30,7 @@ export class LayerManager { this.layerContainer = document.createElement('div'); this.layerContainer.className = 'layer-container'; this.container.appendChild(this.layerContainer); - this.adjustToSize(+this.container.getAttribute('width'), +this.container.getAttribute('height')); + this.adjustToSize(+(this.container.getAttribute('width') ?? 0), +(this.container.getAttribute('height') ?? 0)); this._zoomPanHandler = new ZoomPanHandler(container, (event) => this.rescale(event)); if (scaleOptions) { const { xMin, xMax, yMin, yMax, xBounds, yBounds } = scaleOptions; @@ -71,7 +71,7 @@ export class LayerManager { * Clears data from all mounted layers * @param includeReferenceSystem - (optional) if true also removes reference system, default is true */ - clearAllData(includeReferenceSystem: boolean = true): LayerManager { + clearAllData(includeReferenceSystem = true): LayerManager { this.layers.forEach((l) => l.clearData(includeReferenceSystem)); return this; } @@ -113,7 +113,7 @@ export class LayerManager { return this; } - getLayer(layerId: string): Layer { + getLayer(layerId: string): Layer | undefined { return this.layers.find((l) => l.id === layerId || l.getInternalLayerIds().includes(layerId)); } @@ -181,51 +181,57 @@ export class LayerManager { } showAxis(): LayerManager { - this._axis.show(); + this._axis?.show(); return this; } hideAxis(): LayerManager { - this._axis.hide(); + this._axis?.hide(); return this; } showAxisLabels(): LayerManager { - this._axis.showLabels(); + this._axis?.showLabels(); return this; } hideAxisLabels(): LayerManager { - this._axis.hideLabels(); + this._axis?.hideLabels(); return this; } setAxisOffset(x: number, y: number): LayerManager { - this._axis.offsetX = x; - this._axis.offsetY = y; - const gridLayers = this.layers.filter((l: Layer) => l instanceof GridLayer); - gridLayers.forEach((l: GridLayer) => { - l.offsetX = x; - l.offsetY = y; - }); + if (this._axis) { + this._axis.offsetX = x; + this._axis.offsetY = y; + const gridLayers = this.layers.filter((l: Layer): l is GridLayer => l instanceof GridLayer); + gridLayers.forEach((l: GridLayer) => { + l.offsetX = x; + l.offsetY = y; + }); + } return this; } setXAxisOffset(x: number): LayerManager { - this._axis.offsetX = x; - const gridLayers = this.layers.filter((l: Layer) => l instanceof GridLayer); - gridLayers.forEach((l: GridLayer) => { - l.offsetX = x; - }); + if (this._axis) { + this._axis.offsetX = x; + const gridLayers = this.layers.filter((l: Layer): l is GridLayer => l instanceof GridLayer); + gridLayers.forEach((l: GridLayer) => { + l.offsetX = x; + }); + } return this; } setYAxisOffset(y: number): LayerManager { - this._axis.offsetY = y; - const gridLayers = this.layers.filter((l: Layer) => l instanceof GridLayer); - gridLayers.forEach((l: GridLayer) => { - l.offsetY = y; - }); + if (this._axis) { + this._axis.offsetY = y; + const gridLayers = this.layers.filter((l: Layer): l is GridLayer => l instanceof GridLayer); + gridLayers.forEach((l: GridLayer) => { + l.offsetY = y; + }); + } return this; } @@ -247,10 +253,6 @@ export class LayerManager { destroy(): LayerManager { this.removeAllLayers(); this.layerContainer.remove(); - this.layerContainer = undefined; - this.container = undefined; - this.layers = undefined; - this._zoomPanHandler = undefined; this._axis = undefined; this._svgContainer = undefined; @@ -261,7 +263,7 @@ export class LayerManager { return this._zoomPanHandler; } - get axis(): Axis { + get axis(): Axis | undefined { return this._axis; } diff --git a/src/control/MainController.ts b/src/control/MainController.ts index b3729393..5e70557f 100644 --- a/src/control/MainController.ts +++ b/src/control/MainController.ts @@ -12,7 +12,7 @@ import { HORIZONTAL_AXIS_MARGIN, VERTICAL_AXIS_MARGIN } from '../constants'; * API for controlling data and layers */ export class Controller { - private _referenceSystem: IntersectionReferenceSystem; + private _referenceSystem: IntersectionReferenceSystem | undefined; private layerManager: LayerManager; private _overlay: Overlay; @@ -69,7 +69,7 @@ export class Controller { * Clears data from all mounted layers * @param includeReferenceSystem - (optional) if true also removes reference system, default is true */ - clearAllData(includeReferenceSystem: boolean = true): Controller { + clearAllData(includeReferenceSystem = true): Controller { this.layerManager.clearAllData(includeReferenceSystem); return this; } @@ -106,7 +106,7 @@ export class Controller { * Find first layer with given id, returns undefined if none are found * @param layerId string id */ - getLayer(layerId: string): Layer { + getLayer(layerId: string): Layer | undefined { return this.layerManager.getLayer(layerId); } @@ -259,8 +259,6 @@ export class Controller { this.layerManager.destroy(); this._overlay.destroy(); this._referenceSystem = undefined; - this.layerManager = undefined; - this._overlay = undefined; return this; } @@ -278,7 +276,7 @@ export class Controller { return this._overlay; } - get referenceSystem(): IntersectionReferenceSystem { + get referenceSystem(): IntersectionReferenceSystem | undefined { return this._referenceSystem; } @@ -286,7 +284,7 @@ export class Controller { return this.layerManager.zoomPanHandler; } - get axis(): Axis { + get axis(): Axis | undefined { return this.layerManager.axis; } diff --git a/src/control/ZoomPanHandler.ts b/src/control/ZoomPanHandler.ts index 0de9f082..cf2e6e00 100644 --- a/src/control/ZoomPanHandler.ts +++ b/src/control/ZoomPanHandler.ts @@ -12,20 +12,19 @@ export type RescaleFunction = (event: OnRescaleEvent) => void; * Handle zoom and pan for intersection layers */ export class ZoomPanHandler { - zoom: ZoomBehavior = null; - elm: HTMLElement = null; - container: Selection = null; - onRescale: RescaleFunction = null; - options: ZoomAndPanOptions = null; + zoom!: ZoomBehavior; + container: Selection; + onRescale: RescaleFunction; + options: ZoomAndPanOptions; xBounds: [number, number] = [0, 1]; yBounds: [number, number] = [0, 1]; translateBoundsX: [number, number] = [0, 1]; translateBoundsY: [number, number] = [0, 1]; - scaleX: ScaleLinear = null; - scaleY: ScaleLinear = null; - _zFactor: number = 1; - _enableTranslateExtent: boolean; - currentTransform: ZoomTransform; + scaleX: ScaleLinear; + scaleY: ScaleLinear; + _zFactor = 1; + _enableTranslateExtent = false; + currentTransform: ZoomTransform | undefined; /** * Constructor @@ -66,7 +65,7 @@ export class ZoomPanHandler { * @returns width */ get width(): number { - return this.scaleX.range()[1]; + return this.scaleX.range()[1] ?? 0; } /** @@ -74,7 +73,7 @@ export class ZoomPanHandler { * @returns height */ get height(): number { - return this.scaleY.range()[1]; + return this.scaleY.range()[1] ?? 0; } /** @@ -111,8 +110,8 @@ export class ZoomPanHandler { * @returns ratio */ get xRatio(): number { - const domain: number[] = this.scaleX.domain(); - const ratio: number = Math.abs(this.width / (domain[1] - domain[0])); + const domain = this.scaleX.domain() as [number, number]; + const ratio = Math.abs(this.width / (domain[1] - domain[0])); return ratio; } @@ -121,8 +120,8 @@ export class ZoomPanHandler { * @returns ratio */ get yRatio(): number { - const domain: number[] = this.scaleY.domain(); - const ratio: number = Math.abs(this.height / (domain[1] - domain[0])); + const domain = this.scaleY.domain() as [number, number]; + const ratio = Math.abs(this.height / (domain[1] - domain[0])); return ratio; } @@ -183,10 +182,10 @@ export class ZoomPanHandler { updateTranslateExtent(): void { const { width, xSpan, zFactor, enableTranslateExtent, translateBoundsX, translateBoundsY } = this; - let x1: number = -Infinity; - let y1: number = -Infinity; - let x2: number = +Infinity; - let y2: number = +Infinity; + let x1 = -Infinity; + let y1 = -Infinity; + let x2 = +Infinity; + let y2 = +Infinity; if (enableTranslateExtent) { const ppu: number = width / xSpan; @@ -237,8 +236,7 @@ export class ZoomPanHandler { * Initialized handler */ init(): void { - this.zoom = zoom().scaleExtent([this.options.minZoomLevel, this.options.maxZoomLevel]).on('zoom', this.onZoom); - + this.zoom = zoom().scaleExtent([this.options.minZoomLevel, this.options.maxZoomLevel]).on('zoom', this.onZoom); this.container.call(this.zoom); } @@ -292,19 +290,20 @@ export class ZoomPanHandler { setViewport(cx?: number, cy?: number, displ?: number, duration?: number): void { const { zoom, container, calculateTransform, scaleX, scaleY, isXInverted } = this; - if (isNaN(cx) || isNaN(displ)) { - const xd: number[] = scaleX.domain(); + if (cx == null || displ == null || isNaN(cx) || isNaN(displ)) { + const xd = scaleX.domain() as [number, number]; const dspan: number = xd[1] - xd[0]; - if (isNaN(cx)) { + + if (cx == null || isNaN(cx)) { cx = xd[0] + dspan / 2 || 0; } - if (isNaN(displ)) { + if (displ == null || isNaN(displ)) { displ = Math.abs(dspan) || 1; } } - if (isNaN(cy)) { - const yd: number[] = scaleY.domain(); + if (cy == null || isNaN(cy)) { + const yd = scaleY.domain() as [number, number]; cy = yd[0] + (yd[1] - yd[0]) / 2 || 0; } @@ -315,7 +314,7 @@ export class ZoomPanHandler { const t: ZoomTransform = calculateTransform(dx0, dx1, cy); - if (Number.isFinite(duration) && duration > 0) { + if (duration != null && Number.isFinite(duration) && duration > 0) { zoom.transform(container.transition().duration(duration), t); } else { zoom.transform(container, t); @@ -346,19 +345,25 @@ export class ZoomPanHandler { * Adjust zoom due to changes in size of target * @param force - force update even if size did not change */ - adjustToSize(width?: number | boolean, height?: number, force: boolean = false): void { + adjustToSize(): void; + adjustToSize(autoAdjust: boolean): void; + adjustToSize(width: number, height: number, force: boolean): void; + adjustToSize(widthOrAutoAdjust?: unknown, height?: number, force = false): void { const { width: oldWidth, height: oldHeight, scaleX, scaleY, recalculateZoomTransform } = this; let w = 0; let h = 0; - if (typeof width === 'undefined' || typeof width === 'boolean') { - const { width: containerWidth, height: containerHeight } = this.container.node().getBoundingClientRect(); - w = containerWidth; - h = containerHeight; - } else { - w = width; + if (typeof widthOrAutoAdjust === 'number' && typeof height === 'number') { h = height; + w = widthOrAutoAdjust; + } else { + const containerEl = this.container.node(); + if (containerEl) { + const { width: containerWidth, height: containerHeight } = containerEl.getBoundingClientRect(); + w = containerWidth; + h = containerHeight; + } } const newWidth: number = Math.max(1, w); @@ -386,15 +391,15 @@ export class ZoomPanHandler { calculateTransform(dx0: number, dx1: number, dy: number): ZoomTransform { const { scaleX, xSpan, xBounds, yBounds, zFactor, viewportRatio: ratio, isXInverted, isYInverted } = this; - const [rx1, rx2] = scaleX.range(); - const displ: number = Math.abs(dx1 - dx0); - const k: number = xSpan / displ; - const unitsPerPixels: number = displ / (rx2 - rx1); + const [rx1, rx2] = scaleX.range() as [number, number]; + const displ = Math.abs(dx1 - dx0); + const k = xSpan / displ; + const unitsPerPixels = displ / (rx2 - rx1); - const dy0: number = dy - (isYInverted ? -displ : displ) / zFactor / ratio / 2; + const dy0 = dy - (isYInverted ? -displ : displ) / zFactor / ratio / 2; - const tx: number = (xBounds[0] - dx0) / (isXInverted ? -unitsPerPixels : unitsPerPixels); - const ty: number = (yBounds[0] - dy0) / ((isYInverted ? -unitsPerPixels : unitsPerPixels) / zFactor); + const tx = (xBounds[0] - dx0) / (isXInverted ? -unitsPerPixels : unitsPerPixels); + const ty = (yBounds[0] - dy0) / ((isYInverted ? -unitsPerPixels : unitsPerPixels) / zFactor); return zoomIdentity.translate(tx, ty).scale(k); } @@ -405,8 +410,8 @@ export class ZoomPanHandler { recalculateZoomTransform(): void { const { scaleX, scaleY, container, calculateTransform, updateTranslateExtent } = this; - const [dx0, dx1] = scaleX.domain(); - const [dy0, dy1] = scaleY.domain(); + const [dx0, dx1] = scaleX.domain() as [number, number]; + const [dy0, dy1] = scaleY.domain() as [number, number]; const dy: number = dy0 + (dy1 - dy0) / 2; diff --git a/src/control/interfaces.ts b/src/control/interfaces.ts index cb0cf778..9ce50eeb 100644 --- a/src/control/interfaces.ts +++ b/src/control/interfaces.ts @@ -18,8 +18,8 @@ export interface ControllerOptions { } interface OverlayEvent { - target?: Element; - source: Element; + target: Element | undefined; + source: Element | undefined; caller: T; } @@ -33,7 +33,7 @@ export interface OverlayMouseMoveEvent extends OverlayEvent { y: number; } -export interface OverlayMouseExitEvent extends OverlayEvent {} +export type OverlayMouseExitEvent = OverlayEvent; export interface OverlayCallbacks { onMouseMove?(event: OverlayMouseMoveEvent): void; diff --git a/src/control/overlay.ts b/src/control/overlay.ts index 4b9d88f3..23ed4474 100644 --- a/src/control/overlay.ts +++ b/src/control/overlay.ts @@ -2,8 +2,8 @@ import { select, Selection, pointer, ContainerElement } from 'd3-selection'; import { OverlayCallbacks } from './interfaces'; export class Overlay { - elm: Selection; - source: Element; + elm: Selection; + source: HTMLDivElement | undefined; elements: { [propName: string]: Element } = {}; listeners: { [propName: string]: OverlayCallbacks } = {}; enabled = true; @@ -11,8 +11,7 @@ export class Overlay { constructor(caller: T, container: HTMLElement) { const con = select(container); this.elm = con.append('div').attr('id', 'overlay').style('z-index', '11').style('position', 'absolute'); - - this.source = this.elm.node(); + this.source = this.elm.node() ?? undefined; const { elm } = this; elm.on('resize', (event) => { @@ -24,11 +23,11 @@ export class Overlay { } Object.keys(this.listeners).forEach((key: string) => { - const target = this.elements[key] || null; + const target = this.elements[key] ?? undefined; const ops = this.listeners[key]; if (ops && ops.onResize) { requestAnimationFrame(() => - ops.onResize({ + ops.onResize?.({ target, source: this.source, caller, @@ -47,12 +46,12 @@ export class Overlay { const [mx, my] = pointer(event, this.elm.node() as ContainerElement); Object.keys(this.listeners).forEach((key: string) => { - const target = this.elements[key] || null; + const target = this.elements[key] ?? undefined; const ops = this.listeners[key]; if (ops && ops.onMouseMove) { requestAnimationFrame(() => - ops.onMouseMove({ + ops.onMouseMove?.({ x: mx, y: my, target, @@ -69,11 +68,11 @@ export class Overlay { return; } Object.keys(this.listeners).forEach((key: string) => { - const target = this.elements[key] || null; + const target = this.elements[key] || undefined; const ops = this.listeners[key]; if (ops && ops.onMouseExit) { requestAnimationFrame(() => - ops.onMouseExit({ + ops.onMouseExit?.({ target, source: this.source, caller, @@ -84,13 +83,18 @@ export class Overlay { }); } - create(key: string, callbacks?: OverlayCallbacks): HTMLElement { + create(key: string, callbacks?: OverlayCallbacks): HTMLElement | undefined { const newElm = this.elm.append('div').style('position', 'relative').style('pointer-events', 'none').node(); - this.elements[key] = newElm; - if (callbacks) { - this.listeners[key] = callbacks; + + if (newElm != null) { + this.elements[key] = newElm; + if (callbacks) { + this.listeners[key] = callbacks; + } + return newElm; + } else { + return undefined; } - return newElm; } register(key: string, callbacks: OverlayCallbacks): void { @@ -111,7 +115,7 @@ export class Overlay { } destroy(): void { - this.source.remove(); + this.source?.remove(); } } diff --git a/src/datautils/colortable.ts b/src/datautils/colortable.ts index acb44975..f6b629a1 100644 --- a/src/datautils/colortable.ts +++ b/src/datautils/colortable.ts @@ -1,13 +1,13 @@ import { scaleLinear } from 'd3-scale'; import { color } from 'd3-color'; -export function createColorTable(colorMap: string[], size: number): number[][] { +export function createColorTable(colorMap: string[], size: number): [number, number, number][] { const colorDomain = colorMap.map((_v, i) => (i * size) / colorMap.length); const colorScale = scaleLinear().domain(colorDomain).range(colorMap); - const table = Array.from(new Array(size).keys()).map((i) => { - const rgb = color(colorScale(i)).rgb(); - return [rgb.r, rgb.g, rgb.b]; + const table = Array.from(new Array(size).keys()).map<[number, number, number]>((i) => { + const rgb = color(colorScale(i))?.rgb(); + return rgb != null ? [rgb.r, rgb.g, rgb.b] : [0, 0, 0]; }); return table; diff --git a/src/datautils/findsample.ts b/src/datautils/findsample.ts index d93dc5cc..08edb83c 100644 --- a/src/datautils/findsample.ts +++ b/src/datautils/findsample.ts @@ -47,15 +47,16 @@ export function findIndexOfSample(data: number[][], pos: number): number { return index; } -export function findSampleAtPos(data: number[][], pos: number, topLimit: number = null, bottomLimit: number = null): number { - let y: number = null; +export function findSampleAtPos(data: number[][], pos: number, topLimit = 0, bottomLimit = 0): number { + let y = 0; const index = findIndexOfSample(data, pos); if (index !== -1) { - const v1 = data[index][1]; - const v2 = data[index + 1][1]; - if (v2 && v2) { - const x1 = data[index][0]; - const x2 = data[index + 1][0]; + const v1 = data[index]?.[1]; + const v2 = data[index + 1]?.[1]; + + if (v1 && v2) { + const x1 = data[index]?.[0] ?? 0; + const x2 = data[index + 1]?.[0] ?? 0; const span = x2 - x1; const d = pos - x1; const f = d / span; diff --git a/src/datautils/picks.ts b/src/datautils/picks.ts index 1b4602c8..5bb29209 100644 --- a/src/datautils/picks.ts +++ b/src/datautils/picks.ts @@ -103,7 +103,7 @@ function getFilteredExitPicks(formationPicks: PairedPickAndUnit[]): Annotation[] export const getPicksData = (picksData: { unitPicks: PairedPickAndUnit[]; nonUnitPicks: PickWithId[] }): Annotation[] => [...getReferencePicks(picksData.nonUnitPicks), ...getEntryPicks(picksData.unitPicks), ...getFilteredExitPicks(picksData.unitPicks)].sort( - (a, b) => a.md - b.md, + (a, b) => a.md! - b.md!, ); /** @@ -142,7 +142,7 @@ function findGaps(from: number, to: number, arr: { from: number; to: number; itm let d = from; let i = 0; while (d < to && i < arr.length) { - const itm = arr[i]; + const itm = arr[i]!; if (itm.from > d) { gaps.push([d, Math.min(itm.from, to)]); } @@ -171,13 +171,13 @@ function joinPicksAndStratColumn(picks: Pick[], stratColumn: Unit[]): { joined: const nonUnitPicks: PickWithId[] = []; const joined: PickAndUnit[] = []; picks.forEach((p: Pick) => { - const matches = transformed.filter((u: UnitDto) => p.pickIdentifier.search(new RegExp(`(${u.topSurface}|${u.baseSurface})`, 'i')) !== -1); + const matches = transformed.filter((u: UnitDto) => p.pickIdentifier?.search(new RegExp(`(${u.topSurface}|${u.baseSurface})`, 'i')) !== -1); if (matches.length > 0) { matches.forEach((u: UnitDto) => joined.push({ md: p.md, tvd: p.tvd, - identifier: p.pickIdentifier, + identifier: p.pickIdentifier!, confidence: p.confidence, mdUnit: p.mdUnit, depthReferencePoint: p.depthReferencePoint, @@ -185,7 +185,7 @@ function joinPicksAndStratColumn(picks: Pick[], stratColumn: Unit[]): { joined: }), ); } else { - nonUnitPicks.push({ identifier: p.pickIdentifier, ...p }); + nonUnitPicks.push({ identifier: p.pickIdentifier!, ...p }); } }); @@ -206,7 +206,7 @@ function pairJoinedPicks(joined: PickAndUnit[]): PairedPickAndUnit[] { .sort((a: PickAndUnit, b: PickAndUnit) => a.unitName.localeCompare(b.unitName) || a.md - b.md || a.ageTop - b.ageTop); while (sorted.length > 0) { - current = sorted.shift(); + current = sorted.shift()!; const name = current.identifier; let pairWithName: string; @@ -222,8 +222,8 @@ function pairJoinedPicks(joined: PickAndUnit[]): PairedPickAndUnit[] { continue; } - let top: PickAndUnit; - let base: PickAndUnit; + let top: PickAndUnit | undefined; + let base: PickAndUnit | undefined; const pairWith = sorted.find((p: PickAndUnit) => p.identifier === pairWithName); if (!pairWith) { @@ -233,7 +233,7 @@ function pairJoinedPicks(joined: PickAndUnit[]): PairedPickAndUnit[] { base = joined .filter((d: PickAndUnit) => d.level) .sort((a: PickAndUnit, b: PickAndUnit) => a.md - b.md) - .find((p: PickAndUnit) => p.md > top.md); + .find((p: PickAndUnit) => p.md > top!.md); if (base) { console.warn(`Using ${base.identifier} as base for ${name}`); } else { @@ -245,7 +245,7 @@ function pairJoinedPicks(joined: PickAndUnit[]): PairedPickAndUnit[] { top = joined .filter((d: PickAndUnit) => d.level) .sort((a: PickAndUnit, b: PickAndUnit) => b.md - a.md) - .find((p: PickAndUnit) => p.md < base.md); + .find((p: PickAndUnit) => p.md < base!.md); if (top) { console.warn(`Using ${top.identifier} as top for ${name}`); } else { @@ -303,10 +303,10 @@ export function transformFormationData(picks: Pick[], stratColumn: Unit[]): { un // given presedence over lower levels for overlapping picks. const unitPicks = []; while (itemstack.length > 0) { - const first = itemstack.pop(); + const first = itemstack.pop()!; const group: PairedPickAndUnit[] = []; - while (itemstack.length > 0 && itemstack[itemstack.length - 1].level > first.level) { - group.push(itemstack.pop()); + while (itemstack.length > 0 && itemstack[itemstack.length - 1]?.level! > first.level) { + group.push(itemstack.pop()!); } group.reverse(); group.push(first); diff --git a/src/datautils/schematicShapeGenerator.ts b/src/datautils/schematicShapeGenerator.ts index 542480c1..0487a509 100644 --- a/src/datautils/schematicShapeGenerator.ts +++ b/src/datautils/schematicShapeGenerator.ts @@ -51,15 +51,15 @@ export interface CasingRenderObject { } export const getEndLines = ( - rightPath: IPoint[], - leftPath: IPoint[], + rightPath: [IPoint, IPoint, ...IPoint[]], + leftPath: [IPoint, IPoint, ...IPoint[]], ): { - top: IPoint[]; - bottom: IPoint[]; + top: [IPoint, IPoint]; + bottom: [IPoint, IPoint]; } => { return { top: [rightPath[0], leftPath[0]], - bottom: [rightPath[rightPath.length - 1], leftPath[leftPath.length - 1]], + bottom: [rightPath[rightPath.length - 1] as IPoint, leftPath[leftPath.length - 1] as IPoint], }; }; @@ -215,8 +215,8 @@ export const createComplexRopeSegmentsForCement = ( throw new Error(`Invalid cement data, can't find referenced casing/completion string for cement with id '${cement.id}'`); } - attachedStrings.sort((a: Casing, b: Casing) => a.end - b.end); // ascending - const bottomOfCement = attachedStrings[attachedStrings.length - 1].end; + attachedStrings.sort((a, b) => a.end - b.end); // ascending + const bottomOfCement = attachedStrings[attachedStrings.length - 1]!.end; const { overlappingOuterStrings, overlappingHoles } = findIntersectingItems(cement.toc, bottomOfCement, nonAttachedStrings, holes); @@ -232,7 +232,7 @@ export const createComplexRopeSegmentsForCement = ( return []; } - const nextDepth = list[index + 1]; + const nextDepth = list[index + 1]!; const diameterAtChangeDepth = findCementOuterDiameterAtDepth(attachedStrings, overlappingOuterStrings, overlappingHoles, depth); return [{ top: depth, bottom: nextDepth, diameter: diameterAtChangeDepth * exaggerationFactor }]; @@ -258,7 +258,7 @@ const splitByReferencedStrings = ( } return { ...acc, nonAttachedStrings: [...acc.nonAttachedStrings, current] }; }, - { attachedStrings: [], nonAttachedStrings: [] }, + { attachedStrings: [] as (Casing | Completion)[], nonAttachedStrings: [] as (Casing | Completion)[] }, ); export const createComplexRopeSegmentsForCementSqueeze = ( @@ -289,7 +289,7 @@ export const createComplexRopeSegmentsForCementSqueeze = ( return []; } - const nextDepth = list[index + 1]; + const nextDepth = list[index + 1]!; const diameterAtDepth = findCementOuterDiameterAtDepth(attachedStrings, overlappingOuterStrings, overlappingHoles, depth); @@ -327,7 +327,7 @@ export const createComplexRopeSegmentsForCementPlug = ( return []; } - const nextDepth = list[index + 1]; + const nextDepth = list[index + 1]!; const diameterAtDepth = findCementPlugInnerDiameterAtDepth(attachedStrings, overlappingOuterStrings, overlappingHoles, depth); return [{ top: depth, bottom: nextDepth, diameter: diameterAtDepth * exaggerationFactor }]; @@ -364,6 +364,10 @@ export const createHoleBaseTexture = ({ firstColor, secondColor }: HoleOptions, canvas.height = height; const canvasCtx = canvas.getContext('2d'); + if (canvasCtx == null) { + throw Error('Could not get canvas context!'); + } + canvasCtx.fillStyle = createGradientFill(canvas, canvasCtx, firstColor, secondColor, 0); canvasCtx.fillRect(0, 0, canvas.width, canvas.height); @@ -376,6 +380,9 @@ export const createScreenTexture = ({ scalingFactor }: ScreenOptions): Texture = canvas.width = size; canvas.height = size; const canvasCtx = canvas.getContext('2d'); + if (canvasCtx == null) { + throw Error('Could not get canvas context!'); + } canvasCtx.fillStyle = 'white'; canvasCtx.fillRect(0, 0, canvas.width, canvas.height); @@ -401,6 +408,10 @@ export const createTubingTexture = ({ innerColor, outerColor, scalingFactor }: T canvas.width = size; canvas.height = size; const canvasCtx = canvas.getContext('2d'); + + if (canvasCtx == null) { + throw Error('Could not get canvas context!'); + } const gradient = canvasCtx.createLinearGradient(0, 0, 0, size); const innerColorStart = 0.3; @@ -425,6 +436,10 @@ export const createCementTexture = ({ firstColor, secondColor, scalingFactor }: canvas.height = size; const canvasCtx = canvas.getContext('2d'); + if (canvasCtx == null) { + throw Error('Could not get canvas context!'); + } + canvasCtx.fillStyle = firstColor; canvasCtx.fillRect(0, 0, canvas.width, canvas.height); canvasCtx.lineWidth = lineWidth; @@ -449,6 +464,10 @@ export const createCementPlugTexture = ({ firstColor, secondColor, scalingFactor canvas.height = size; const canvasCtx = canvas.getContext('2d'); + if (canvasCtx == null) { + throw Error('Could not get canvas context!'); + } + canvasCtx.fillStyle = firstColor; canvasCtx.fillRect(0, 0, canvas.width, canvas.height); canvasCtx.lineWidth = scalingFactor; @@ -475,6 +494,10 @@ export const createCementSqueezeTexture = ({ firstColor, secondColor, scalingFac canvas.height = size; const canvasCtx = canvas.getContext('2d'); + if (canvasCtx == null) { + throw Error('Could not get canvas context!'); + } + canvasCtx.lineWidth = lineWidth; canvasCtx.fillStyle = firstColor; canvasCtx.strokeStyle = secondColor; @@ -535,7 +558,7 @@ export const getCasingIntervalsWithWindows = (casing: Casing): CasingInterval[] ? createCasingInterval(nextLastBottom, casing.end) : null; - const newIntervals: CasingInterval[] = [startCasingInterval, windowInterval, endCasingInterval].filter((i) => i); + const newIntervals: CasingInterval[] = [startCasingInterval, windowInterval, endCasingInterval].filter((i): i is CasingInterval => i != null); return { intervals: [...intervals, ...newIntervals], lastBottom: nextLastBottom }; }, @@ -599,7 +622,7 @@ export const createComplexRopeSegmentsForPerforation = ( return []; } - const nextDepth = list[index + 1]; + const nextDepth = list[index + 1]!; const diameterAtDepth = findPerforationOuterDiameterAtDepth(overlappingOuterStrings, overlappingHoles, depth, perforation.subKind); @@ -769,14 +792,17 @@ const errorTexture = (errorMessage = 'Error!', existingContext?: { canvas: HTMLC const xy: [number, number] = [0, 0]; const wh: [number, number] = [canvas.width, canvas.height]; + if (canvasCtx == null) { + throw Error('Could not get canvas context!'); + } canvasCtx.fillStyle = '#ff00ff'; canvasCtx.fillRect(...xy, ...wh); const texture = new Texture( Texture.from(canvas, { wrapMode: WRAP_MODES.CLAMP }).baseTexture, - null, + undefined, new Rectangle(0, 0, canvas.width, canvas.height), - null, + undefined, groupD8.MIRROR_HORIZONTAL, ); return texture; @@ -793,15 +819,19 @@ const createPerforationCanvas = ( canvas.height = size; const ctx = canvas.getContext('2d'); + if (ctx == null) { + throw Error('Could not get canvas context!'); + } + return { canvas, ctx }; }; const createPerforationTexture = (canvas: HTMLCanvasElement) => { const texture = new Texture( Texture.from(canvas, { wrapMode: WRAP_MODES.CLAMP }).baseTexture, - null, + undefined, new Rectangle(0, 0, canvas.width, canvas.height), - null, + undefined, groupD8.MIRROR_HORIZONTAL, ); return texture; diff --git a/src/datautils/seismicimage.ts b/src/datautils/seismicimage.ts index 69ce1d01..dca88905 100644 --- a/src/datautils/seismicimage.ts +++ b/src/datautils/seismicimage.ts @@ -44,11 +44,11 @@ export function getSeismicInfo(data: { datapoints: number[][]; yAxisValues: numb if (!(data && data.datapoints)) { return null; } - const minX = trajectory.reduce((acc: number, val: number[]) => Math.min(acc, val[0]), 0); - const maxX = trajectory.reduce((acc: number, val: number[]) => Math.max(acc, val[0]), 0); + const minX = trajectory.reduce((acc: number, val: number[]) => Math.min(acc, val[0]!), 0); + const maxX = trajectory.reduce((acc: number, val: number[]) => Math.max(acc, val[0]!), 0); - const minTvdMsl = data.yAxisValues && data.yAxisValues[0]; - const maxTvdMsl = data.yAxisValues && data.yAxisValues[data.yAxisValues.length - 1]; + const minTvdMsl = data.yAxisValues && data.yAxisValues[0]!; + const maxTvdMsl = data.yAxisValues && data.yAxisValues[data.yAxisValues.length - 1]!; // Find value domain const dp = data.datapoints || []; @@ -117,8 +117,7 @@ export async function generateSeismicSliceImage( difference: dmax - dmin, }; - const length = trajectory[0][0] - trajectory[trajectory.length - 1][0]; - // eslint-disable-next-line no-magic-numbers + const length = trajectory[0]?.[0]! - trajectory[trajectory.length - 1]?.[0]!; const width = Math.abs(Math.floor(length / 5)); const height = data.yAxisValues.length; @@ -132,7 +131,7 @@ export async function generateSeismicSliceImage( let offset = 0; const colorFactor = (colorTableSize - 1) / domain.difference; - let pos = options?.isLeftToRight ? trajectory[0][0] : trajectory[trajectory.length - 1][0]; + let pos = options?.isLeftToRight ? trajectory[0]?.[0]! : trajectory[trajectory.length - 1]?.[0]!; const step = (length / width) * (options?.isLeftToRight ? -1 : 1); @@ -147,15 +146,15 @@ export async function generateSeismicSliceImage( for (let x = 0; x < width; x++) { offset = x * 4; const index = findIndexOfSample(trajectory, pos); - const x1 = trajectory[index][0]; - const x2 = trajectory[index + 1][0]; + const x1 = trajectory[index]?.[0]!; + const x2 = trajectory[index + 1]?.[0]!; const span = x2 - x1; const dx = pos - x1; const ratio = dx / span; for (let y = 0; y < height; y++) { - val1 = dp[y][index]; - val2 = dp[y][index + 1]; + val1 = dp[y]?.[index]!; + val2 = dp[y]?.[index + 1]!; if (val1 == null || val2 == null) { col = black; opacity = 0; @@ -163,11 +162,11 @@ export async function generateSeismicSliceImage( val = val1 * (1 - ratio) + val2 * ratio; i = (val - domain.min) * colorFactor; i = clamp(~~i, 0, colorTableSize - 1); - col = colorTable[i]; + col = colorTable[i]!; opacity = 255; } - d.set([col[0], col[1], col[2], opacity], offset); + d.set([col[0]!, col[1]!, col[2]!, opacity], offset); offset += width * 4; } diff --git a/src/datautils/surfacedata.ts b/src/datautils/surfacedata.ts index f1baa3e2..d84b4703 100644 --- a/src/datautils/surfacedata.ts +++ b/src/datautils/surfacedata.ts @@ -91,7 +91,7 @@ function getSurfaceLines(mappedSurfaces: MappedSurfaces[], trajectory: number[][ label: l.name, width: 2, color: convertColor(l.color || 'black'), - data: trajectory.map((p, j) => [p[0], l.values[j]]), + data: trajectory.map((p, j) => [p[0]!, l.values[j]!]), })); return lines; @@ -99,13 +99,14 @@ function getSurfaceLines(mappedSurfaces: MappedSurfaces[], trajectory: number[][ function generateGroupAreas(groups: MappedGroup[], trajectory: number[][]): SurfaceArea[] { const groupAreas = groups.map((g: MappedGroup, i: number) => { - const next: MappedGroup | null = i + 1 < groups.length ? groups[i + 1] : null; + const next: MappedGroup | null = i + 1 < groups.length ? groups[i + 1]! : null; return { id: g.id, color: convertColor(g.color), - data: trajectory.map((p: number[], j: number) => [p[0], g.top[j], next ? next.top[j] : null]), + data: trajectory.map((p: number[], j: number) => [p[0]!, g.top[j]!, ...(next ? [next.top[j]!] : [])]), }; }); + return groupAreas; } @@ -113,7 +114,7 @@ function mapGroups(stratGroups: Map, surfaceAreas: SurfaceAr const groups = Array.from(stratGroups.values()) .sort((a: StratGroup, b: StratGroup) => a.age - b.age) .filter((g: StratGroup) => { - const surfaces: SurfaceArea[] = surfaceAreas[g.name]; + const surfaces = surfaceAreas[g.name]; const isValid = surfaces && surfaces.length > 0; if (!isValid) { console.warn(`Intersection surface group '${g.name}' has no valid entries and will be discarded.`); @@ -121,13 +122,13 @@ function mapGroups(stratGroups: Map, surfaceAreas: SurfaceAr return isValid; }) .map((g: StratGroup, i: number) => { - const surface: SurfaceArea[] = surfaceAreas[g.name]; - const top = surface[0]; + const surface = surfaceAreas[g.name]!; + const top = surface[0]!; return { id: g.name, label: g.name, color: unassignedColorScale(i), - top: top.data.map((d: number[]) => d[1]), + top: top.data.map((d: number[]) => d[1]!), }; }); return groups; @@ -144,11 +145,11 @@ function combineSurfacesAndStratColumn( .filter((d: MappedSurfaces) => d.visualization === 'interval' || d.visualization === 'none') .map((s: MappedSurfaces) => { const path: StratUnit[] = []; - const stratUnit: StratUnit = findStratcolumnUnit(stratColumn, s.name, path); + const stratUnit = findStratcolumnUnit(stratColumn, s.name, path); if (!stratUnit) { console.warn(`No match for ${s.name} in strat column`); } - const group: StratUnit = path[0] || stratUnit; + const group = path[0]! || stratUnit; const groupName: string = (group && group.identifier) || defaultGroupName; if (group && !stratGroups.has(groupName)) { stratGroups.set(groupName, { @@ -158,7 +159,7 @@ function combineSurfacesAndStratColumn( } return { ...s, - unit: stratUnit, + unit: stratUnit!, group: groupName, }; }); @@ -201,13 +202,13 @@ function sortStratigraphies(stratigrafies: Stratigraphy[]): void { * @param {[]} path */ function findStratcolumnUnit(units: StratUnit[], unitname: string, path: StratUnit[] = []): StratUnit | null { - const unit: StratUnit = units.find((u: StratUnit) => u.identifier.toLowerCase() === unitname.toLowerCase()); + const unit = units.find((u: StratUnit) => u.identifier.toLowerCase() === unitname.toLowerCase()); if (unit) { // Build path - let temp: StratUnit = unit; + let temp: StratUnit | undefined = unit; do { path.unshift(temp); - temp = units.find((u: StratUnit) => u.identifier === temp.stratUnitParent); + temp = units.find((u: StratUnit) => u.identifier === temp!.stratUnitParent); } while (temp); return unit; @@ -247,11 +248,11 @@ const unassignedColorScale = scaleOrdinal() /** * Find the best matching base index based on name or by values */ -function findBestMatchingBaseIndex(top: Stratigraphy, index: number, surfaces: Stratigraphy[], stratColumn: StratUnit[]): number { +function findBestMatchingBaseIndex(top: Stratigraphy, index: number, surfaces: Stratigraphy[], stratColumn: StratUnit[]): number | undefined { const nextIndex: number = index + 1; if (!surfaces || nextIndex >= surfaces.length) { - return null; + return undefined; } // If there is a matching base by name, use that. More robust, does not rely on sorting @@ -262,14 +263,14 @@ function findBestMatchingBaseIndex(top: Stratigraphy, index: number, surfaces: S for (let i = nextIndex; i < surfaces.length; i++) { const candidate = surfaces[i]; - if (!candidate.isBase) { + if (!candidate?.isBase) { return i; } if (isAnchestor(top, candidate, stratColumn)) { return i; } } - return null; + return undefined; } function isAnchestor(descendant: Stratigraphy, candidate: Stratigraphy, stratColumn: StratUnit[]): boolean { @@ -284,15 +285,15 @@ function generateSurfaceAreas(projection: number[][], surfaces: Stratigraphy[], if (!acc[surface.group]) { acc[surface.group] = []; } - const baseIndex: number = findBestMatchingBaseIndex(surface, i, surfaces, stratColumn); - acc[surface.group].push({ + const baseIndex = findBestMatchingBaseIndex(surface, i, surfaces, stratColumn); + acc[surface.group]?.push({ id: surface.name, label: surface.name, color: (surface.unit && getColorFromUnit(surface.unit)) || WHITE, exclude: surface.visualization === 'none' || !surface.unit, data: projection.map((p, j) => { - const baseValue: number = surface.values[j] !== null ? getBaseValue(baseIndex, surfaces, j) : null; - return [p[0], surface.values[j], baseValue]; + const baseValue = surface.values[j] != null ? getBaseValue(baseIndex, surfaces, j) : undefined; + return [p[0]!, surface.values[j]!, baseValue!]; }), }); } @@ -303,15 +304,15 @@ function generateSurfaceAreas(projection: number[][], surfaces: Stratigraphy[], // get the value from the surface with the supplied index, // iterate to next surface if value is null -function getBaseValue(index: number, surfaces: Stratigraphy[], datapoint: number): number { +function getBaseValue(index: number | undefined, surfaces: Stratigraphy[], datapoint: number): number | undefined { if (!surfaces || !index || index >= surfaces.length) { - return null; + return undefined; } for (let i: number = index; i < surfaces.length; i++) { - if (surfaces[i].values[datapoint] !== null) { - return surfaces[i].values[datapoint]; + if (surfaces[i]?.values[datapoint] != null) { + return surfaces[i]?.values[datapoint]; } } - return null; + return undefined; } diff --git a/src/datautils/trajectory.ts b/src/datautils/trajectory.ts index 1a3b88fa..0d2fcfac 100644 --- a/src/datautils/trajectory.ts +++ b/src/datautils/trajectory.ts @@ -15,19 +15,21 @@ const pathSteps = 10; * Code originally developed for REP * @param {[]} poslog Position log from SMDA */ -export function generateProjectedWellborePath(poslog: SurveySample[]): number[][] { +export function generateProjectedWellborePath(poslog: SurveySample[]): [number, number][] { if (!poslog || poslog.length === 0) { return []; } - const points: number[][] = poslog ? poslog.map((p: SurveySample) => [p.easting, p.northing, p.tvd, p.md]) : []; + const points: [number, number, number, number][] = poslog ? poslog.map((p: SurveySample) => [p.easting, p.northing, p.tvd, p.md]) : []; - const projection: number[][] = simplify(projectCurtain(points)); - const offset: number = projection[projection.length - 1][0]; + const projection = simplify(projectCurtain(points)); + const offset = projection[projection.length - 1]?.[0]; - projection.forEach((p, i) => { - projection[i][0] = offset - p[0]; - }); + if (offset != null) { + projection.forEach((p, i) => { + projection[i]![0] = offset - p[0]; + }); + } return projection; } @@ -43,25 +45,25 @@ export function generateProjectedTrajectory(poslog: SurveySample[], defaultInter return []; } - const points: number[][] = poslog ? poslog.map((p) => [p.easting, p.northing, p.tvd, p.md]) : []; + const points: [number, number, number, number][] = poslog ? poslog.map((p) => [p.easting, p.northing, p.tvd, p.md]) : []; const interpolator: CurveInterpolator = new CurveInterpolator(points, { tension: 0.75, arcDivisions: 5000 }); const displacement: number = interpolator.length; const nPoints: number = Math.round(displacement * pathSteps); - let path: number[][] = null; + let path: [number, number][]; if (nPoints > 0) { const maxOffset = 0.0005; const maxDistance = 10; path = simplify(interpolator.getPoints(nPoints), maxOffset, maxDistance); } else { - path = [[points[0][0], points[0][1]]]; + path = [[points[0]![0], points[0]![1]]]; } - const first: number[] = path[0]; - const last: number[] = path[path.length - 1]; + const first = path[0]!; + const last = path[path.length - 1]!; const relativeDist: number = Vector2.distance(first, last); - let v: Vector2 = null; + let v: Vector2; if (relativeDist < thresholdRelativeDist) { const oneEighty = 180; @@ -72,9 +74,9 @@ export function generateProjectedTrajectory(poslog: SurveySample[], defaultInter } const extensionLengthStart: number = Math.max(0, extensionLength - displacement); const offset: number = extensionLengthStart + displacement; - const trajectory: number[][] = []; + const trajectory: [number, number][] = []; - let firstPoints: number[][] = []; + let firstPoints: [number, number][] = []; // Reference to initial vector const initial: number[] = v.toArray(); @@ -93,7 +95,7 @@ export function generateProjectedTrajectory(poslog: SurveySample[], defaultInter } trajectory.push(...path); - const endPoints: number[][] = seqI(Math.ceil(extensionLength * stepSize)) + const endPoints = seqI(Math.ceil(extensionLength * stepSize)) .map((t) => v .set(initial) @@ -105,7 +107,7 @@ export function generateProjectedTrajectory(poslog: SurveySample[], defaultInter trajectory.push(...endPoints); - const projectedTrajectory: number[][] = projectCurtain(trajectory, null, offset); + const projectedTrajectory: number[][] = projectCurtain(trajectory, undefined, offset); return projectedTrajectory; } @@ -124,7 +126,7 @@ function getDirectionVector(path: number[][], threshold: number): Vector2 { for (let i = 0; i < path.length - 1; i++) { const index = path.length - 1 - i; - temp.set(path[index]).sub(path[index - 1]); + temp.set(path[index]!).sub(path[index - 1]!); res.add(temp); len = res.magnitude; @@ -152,25 +154,25 @@ function getDirectionVector(path: number[][], threshold: number): Vector2 { * * @return {Number[]} Simplified array */ -function simplify(inputArr: number[][], maxOffset = 0.001, maxDistance = 10): number[][] { +function simplify(inputArr: [number, number][], maxOffset = 0.001, maxDistance = 10): [number, number][] { if (inputArr.length <= 4) { return inputArr; } - const [o0, o1] = inputArr[0]; - const arr = inputArr.map((d) => [d[0] - o0, d[1] - o1]); - let [a0, a1] = arr[0]; - const sim: number[][] = [inputArr[0]]; + const [o0, o1] = inputArr[0]!; + const arr = inputArr.map<[number, number]>((d) => [d[0]! - o0, d[1]! - o1]); + let [a0, a1] = arr[0]!; + const sim = [inputArr[0]!]; for (let i = 1; i + 1 < arr.length; i++) { - const [t0, t1] = arr[i]; - const [b0, b1] = arr[i + 1]; + const [t0, t1] = arr[i] ?? []; + const [b0, b1] = arr[i + 1] ?? []; // If t->b vector is NOT [0, 0] - if (b0 - t0 !== 0 || b1 - t1 !== 0) { + if (t0 != null && t1 != null && b0 != null && b1 != null && (b0 - t0 !== 0 || b1 - t1 !== 0)) { // Proximity check const proximity: number = Math.abs(a0 * b1 - a1 * b0 + b0 * t1 - b1 * t0 + a1 * t0 - a0 * t1) / Math.sqrt((b0 - a0) ** 2 + (b1 - a1) ** 2); - const dir: number[] = [a0 - t0, a1 - t1]; + const dir: [number, number] = [a0 - t0, a1 - t1]; const len: number = Math.sqrt(dir[0] ** 2 + dir[1] ** 2); if (proximity > maxOffset || len >= maxDistance) { @@ -179,7 +181,7 @@ function simplify(inputArr: number[][], maxOffset = 0.001, maxDistance = 10): nu } } } - const last: number[] = arr[arr.length - 1]; + const last = arr[arr.length - 1]!; sim.push([last[0] + o0, last[1] + o1]); return sim; @@ -192,15 +194,15 @@ function simplify(inputArr: number[][], maxOffset = 0.001, maxDistance = 10): nu * @param offset * @returns {array} */ -function projectCurtain(points: number[][], origin: number[] = null, offset = 0): number[][] { - let p0: number[] = origin || points[0]; +function projectCurtain(points: [number, number, ...number[]][], origin?: [number, number, number, number], offset = 0): [number, number][] { + let p0 = origin || points[0]!; let l = 0; - const projected = points.map((p1: number[]) => { + const projected = points.map<[number, number]>((p1) => { const dx = p1[0] - p0[0]; const dy = p1[1] - p0[1]; l += Math.sqrt(dx ** 2 + dy ** 2); p0 = p1; - return [offset > 0 ? offset - l : l, p1[2] || 0]; + return [offset > 0 ? offset - l : l, p1[2] ?? 0]; }); return projected; } diff --git a/src/interfaces.ts b/src/interfaces.ts index b3bd75d4..c2c52228 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -15,7 +15,7 @@ export interface OnMountEvent extends LayerEvent { height?: number; } -export interface OnUnmountEvent extends LayerEvent {} +export type OnUnmountEvent = LayerEvent; export interface OnResizeEvent extends LayerEvent { width: number; diff --git a/src/layers/CalloutCanvasLayer.ts b/src/layers/CalloutCanvasLayer.ts index c21b9416..f3b03723 100644 --- a/src/layers/CalloutCanvasLayer.ts +++ b/src/layers/CalloutCanvasLayer.ts @@ -47,10 +47,10 @@ export interface CalloutOptions extends LayerOptions } export class CalloutCanvasLayer extends CanvasLayer { - rescaleEvent: OnRescaleEvent; - xRatio: number; - callouts: Callout[]; - groupFilter: string[] = null; + rescaleEvent: OnRescaleEvent | undefined; + xRatio: number | undefined; + callouts: Callout[] = []; + groupFilter: string[] = []; minFontSize: number; maxFontSize: number; fontSizeFactor: number; @@ -60,25 +60,23 @@ export class CalloutCanvasLayer extends CanvasLayer { constructor(id?: string, options?: CalloutOptions) { super(id, options); - this.minFontSize = options.minFontSize || DEFAULT_MIN_FONT_SIZE; - this.maxFontSize = options.maxFontSize || DEFAULT_MAX_FONT_SIZE; - this.fontSizeFactor = options.fontSizeFactor || DEFAULT_FONT_SIZE_FACTOR; - this.offsetMin = options.offsetMin || DEFAULT_OFFSET_MIN; - this.offsetMax = options.offsetMax || DEFAULT_OFFSET_MAX; - this.offsetFactor = options.offsetFactor || DEFAULT_OFFSET_FACTOR; + this.minFontSize = options?.minFontSize || DEFAULT_MIN_FONT_SIZE; + this.maxFontSize = options?.maxFontSize || DEFAULT_MAX_FONT_SIZE; + this.fontSizeFactor = options?.fontSizeFactor || DEFAULT_FONT_SIZE_FACTOR; + this.offsetMin = options?.offsetMin || DEFAULT_OFFSET_MIN; + this.offsetMax = options?.offsetMax || DEFAULT_OFFSET_MAX; + this.offsetFactor = options?.offsetFactor || DEFAULT_OFFSET_FACTOR; } setGroupFilter(filter: string[]): void { this.groupFilter = filter; - this.callouts = undefined; + this.callouts = []; this.render(); } override onUpdate(event: OnUpdateEvent): void { super.onUpdate(event); - - this.callouts = undefined; - + this.callouts = []; this.render(); } @@ -102,14 +100,14 @@ export class CalloutCanvasLayer extends CanvasLayer { const fontSize = calcSize(this.fontSizeFactor, this.minFontSize, this.maxFontSize, xScale); - if (!isPanning || !this.callouts) { + if (!isPanning || this.callouts.length <= 0) { const { data, ctx, groupFilter } = this; const { calculateDisplacementFromBottom } = this.referenceSystem.options; const isLeftToRight = calculateDisplacementFromBottom ? xBounds[0] < xBounds[1] : xBounds[0] > xBounds[1]; const scale = 0; - ctx.font = `bold ${fontSize}px arial`; - const filtered = data.filter((d: Annotation) => !groupFilter || groupFilter.includes(d.group)); + ctx != null && (ctx.font = `bold ${fontSize}px arial`); + const filtered = data.filter((d: Annotation) => groupFilter.length <= 0 || groupFilter.includes(d.group)); const offset = calcSize(this.offsetFactor, this.offsetMin, this.offsetMax, xScale); this.callouts = this.positionCallouts(filtered, isLeftToRight, xScale, yScale, scale, fontSize, offset); } @@ -138,27 +136,24 @@ export class CalloutCanvasLayer extends CanvasLayer { this.renderText(label, x, y, fontSize, color); }; - private renderText( - title: string, - x: number, - y: number, - fontSize: number, - color: string, - font: string = 'arial', - fontStyle: string = 'normal', - ): void { + private renderText(title: string, x: number, y: number, fontSize: number, color: string, font = 'arial', fontStyle = 'normal'): void { const { ctx } = this; - ctx.font = `${fontStyle} ${fontSize}px ${font}`; - ctx.fillStyle = color; - ctx.fillText(title, x, y); + if (ctx != null) { + ctx.font = `${fontStyle} ${fontSize}px ${font}`; + ctx.fillStyle = color; + ctx.fillText(title, x, y); + } } - private renderPoint(x: number, y: number, radius: number = 3): void { + private renderPoint(x: number, y: number, radius = 3): void { const { ctx } = this; - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.arc(x, y, radius, 0, Math.PI * 2); - ctx.fill(); + + if (ctx != null) { + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.fill(); + } } private renderCallout(title: string, label: string, boundingBox: BoundingBox, color: string, location: string): void { @@ -172,25 +167,27 @@ export class CalloutCanvasLayer extends CanvasLayer { this.renderLine(x, y, width, dotX, dotY, color, placeLeft); } - private renderLine = (x: number, y: number, width: number, dotX: number, dotY: number, color: string, placeLeft: boolean = true): void => { + private renderLine = (x: number, y: number, width: number, dotX: number, dotY: number, color: string, placeLeft = true): void => { const { ctx } = this; const textX = placeLeft ? x : x + width; const inverseTextX = placeLeft ? x + width : x; const textY = y + 2; - ctx.strokeStyle = color; - ctx.lineWidth = 1; + if (ctx != null) { + ctx.strokeStyle = color; + ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(dotX, dotY); - ctx.lineTo(textX, textY); - ctx.lineTo(inverseTextX, textY); + ctx.beginPath(); + ctx.moveTo(dotX, dotY); + ctx.lineTo(textX, textY); + ctx.lineTo(inverseTextX, textY); - ctx.stroke(); + ctx.stroke(); + } }; private getPosition(boundingBox: BoundingBox, location: string): Point { - const { x, y, offsetX, offsetY, width } = boundingBox; + const { x, y, offsetX = 0, offsetY = 0, width } = boundingBox; switch (location) { case Location.topleft: return { @@ -228,7 +225,7 @@ export class CalloutCanvasLayer extends CanvasLayer { yScale: ScaleLinear, _scale: number, fontSize: number, - offset: number = 20, + offset = 20, ): Callout[] { if (annotations.length === 0) { return []; @@ -236,12 +233,12 @@ export class CalloutCanvasLayer extends CanvasLayer { const alignment = isLeftToRight ? Location.topleft : Location.topright; const nodes = annotations.map((a) => { - const pos = a.pos ? a.pos : this.referenceSystem.project(a.md); + const pos = a.pos ? a.pos : this.referenceSystem?.project(a.md!)!; return { title: a.title, label: a.label, color: a.color, - pos: { x: pos[0], y: pos[1] }, + pos: { x: pos?.[0]!, y: pos?.[1]! }, group: a.group, alignment, boundingBox: this.getAnnotationBoundingBox(a.title, a.label, pos, xScale, yScale, fontSize), @@ -250,7 +247,7 @@ export class CalloutCanvasLayer extends CanvasLayer { }; }); - const top = [nodes[nodes.length - 1]]; + const top = [nodes[nodes.length - 1]!]; const bottom: Callout[] = []; // Initial best effort @@ -274,11 +271,11 @@ export class CalloutCanvasLayer extends CanvasLayer { height: number, ): { x: number; y: number; width: number; height: number } { const { ctx } = this; - const ax1 = xScale(pos[0]); - const ay1 = yScale(pos[1]); + const ax1 = xScale(pos[0]!); + const ay1 = yScale(pos[1]!); - const labelWidth = ctx.measureText(label).width; - const titleWidth = ctx.measureText(title).width; + const labelWidth = ctx?.measureText(label).width ?? 0; + const titleWidth = ctx?.measureText(title).width ?? 0; const width = Math.max(labelWidth, titleWidth); const bbox = { @@ -292,15 +289,15 @@ export class CalloutCanvasLayer extends CanvasLayer { chooseTopOrBottomPosition(nodes: Callout[], bottom: Callout[], top: Callout[]): void { for (let i = nodes.length - 2; i >= 0; --i) { - const node = nodes[i]; - const prevNode = top[0]; + const node = nodes[i]!; + const prevNode = top[0]!; const overlap = isOverlapping(node.boundingBox, prevNode.boundingBox); if (overlap) { node.alignment = node.alignment === Location.topleft ? Location.bottomright : Location.bottomleft; bottom.push(node); if (i > 0) { - top.unshift(nodes[--i]); + top.unshift(nodes[--i]!); } } else { top.unshift(node); @@ -310,9 +307,9 @@ export class CalloutCanvasLayer extends CanvasLayer { adjustTopPositions(top: Callout[]): void { for (let i = top.length - 2; i >= 0; --i) { - const currentNode = top[i]; + const currentNode = top[i]!; for (let j = top.length - 1; j > i; --j) { - const prevNode = top[j]; + const prevNode = top[j]!; const overlap = getOverlapOffset(currentNode.boundingBox, prevNode.boundingBox); if (overlap) { currentNode.dy += overlap.dy; @@ -324,9 +321,9 @@ export class CalloutCanvasLayer extends CanvasLayer { adjustBottomPositions(bottom: Callout[]): void { for (let i = bottom.length - 2; i >= 0; --i) { - const currentNode = bottom[i]; + const currentNode = bottom[i]!; for (let j = bottom.length - 1; j > i; --j) { - const prevNode = bottom[j]; + const prevNode = bottom[j]!; const overlap = getOverlapOffset(prevNode.boundingBox, currentNode.boundingBox); if (overlap) { currentNode.dy += overlap.dy; diff --git a/src/layers/CustomDisplayObjects/ComplexRopeGeometry.ts b/src/layers/CustomDisplayObjects/ComplexRopeGeometry.ts index ee417479..6a5b3472 100644 --- a/src/layers/CustomDisplayObjects/ComplexRopeGeometry.ts +++ b/src/layers/CustomDisplayObjects/ComplexRopeGeometry.ts @@ -28,7 +28,7 @@ export class ComplexRopeGeometry extends MeshGeometry { * @readonly */ get width(): number { - return max(this.segments, (segment) => segment.diameter); + return max(this.segments, (segment) => segment.diameter)!; } /** Refreshes Rope indices and uvs */ @@ -66,7 +66,7 @@ export class ComplexRopeGeometry extends MeshGeometry { uvs[3] = 1; const segmentCount = segments.length; - const maxDiameter = max(segments, (segment) => segment.diameter); + const maxDiameter = max(segments, (segment) => segment.diameter)!; let amount = 0; let uvIndex = 0; @@ -74,21 +74,21 @@ export class ComplexRopeGeometry extends MeshGeometry { let indexCount = 0; for (let i = 0; i < segmentCount; i++) { - let prev = segments[i].points[0]; + let prev = segments[i]?.points[0]!; const textureWidth = maxDiameter; - const radius = segments[i].diameter / maxDiameter / 2; + const radius = segments[i]?.diameter! / maxDiameter / 2; - const total = segments[i].points.length; // - 1; + const total = segments[i]?.points.length!; // - 1; for (let j = 0; j < total; j++) { // time to do some smart drawing! // calculate pixel distance from previous point - const dx = prev.x - segments[i].points[j].x; - const dy = prev.y - segments[i].points[j].y; + const dx = prev.x - segments[i]?.points[j]?.x!; + const dy = prev.y - segments[i]?.points[j]?.y!; const distance = Math.sqrt(dx * dx + dy * dy); - prev = segments[i].points[j]; + prev = segments[i]?.points[j]!; amount += distance / textureWidth; uvs[uvIndex] = amount; @@ -131,20 +131,20 @@ export class ComplexRopeGeometry extends MeshGeometry { const segmentCount = segments.length; let lastIndex = 0; for (let i = 0; i < segmentCount; i++) { - let lastPoint = segments[i].points[0]; + let lastPoint = segments[i]?.points[0]!; let nextPoint; let perpX = 0; let perpY = 0; - const vertices = this.buffers[0].data; - const total = segments[i].points.length; + const vertices = this.buffers[0]?.data; + const total = segments[i]?.points.length!; let index = 0; for (let j = 0; j < total; j++) { - const point = segments[i].points[j]; + const point = segments[i]?.points[j]!; index = lastIndex + j * 4; - if (j < segments[i].points.length - 1) { - nextPoint = segments[i].points[j + 1]; + if (j < segments[i]?.points.length! - 1) { + nextPoint = segments[i]?.points[j + 1]!; } else { nextPoint = point; } @@ -153,7 +153,7 @@ export class ComplexRopeGeometry extends MeshGeometry { perpX = nextPoint.y - lastPoint.y; const perpLength = Math.sqrt(perpX * perpX + perpY * perpY); - const num = segments[i].diameter / 2; + const num = segments[i]?.diameter! / 2; perpX /= perpLength; perpY /= perpLength; @@ -161,16 +161,19 @@ export class ComplexRopeGeometry extends MeshGeometry { perpX *= num; perpY *= num; - vertices[index] = point.x + perpX; - vertices[index + 1] = point.y + perpY; - vertices[index + 2] = point.x - perpX; - vertices[index + 3] = point.y - perpY; + if (vertices != null) { + vertices[index] = point.x + perpX; + vertices[index + 1] = point.y + perpY; + vertices[index + 2] = point.x - perpX; + vertices[index + 3] = point.y - perpY; + } + lastPoint = point; } lastIndex = index + 4; } - this.buffers[0].update(); + this.buffers[0]?.update(); } public update(): void { diff --git a/src/layers/CustomDisplayObjects/FixedWidthSimpleRopeGeometry.ts b/src/layers/CustomDisplayObjects/FixedWidthSimpleRopeGeometry.ts index 10f2494e..61f7e744 100644 --- a/src/layers/CustomDisplayObjects/FixedWidthSimpleRopeGeometry.ts +++ b/src/layers/CustomDisplayObjects/FixedWidthSimpleRopeGeometry.ts @@ -8,7 +8,6 @@ export class FixedWidthSimpleRopeGeometry extends MeshGeometry { * @param {PIXI.Point[]} [points] - An array of PIXI.Point objects to construct this rope. */ constructor(points: IPoint[], width = 200) { - // eslint-disable-next-line no-magic-numbers super(new Float32Array(points.length * 4), new Float32Array(points.length * 4), new Uint16Array((points.length - 1) * 6)); /** * An array of points that determine the rope @@ -66,17 +65,17 @@ export class FixedWidthSimpleRopeGeometry extends MeshGeometry { uvs[2] = 0; uvs[3] = 1; let amount = 0; - let prev = points[0]; + let prev = points[0]!; const total = points.length; // - 1; for (let i = 0; i < total; i++) { // time to do some smart drawing! const index = i * 4; // calculate pixel distance from previous point - const dx = prev.x - points[i].x; - const dy = prev.y - points[i].y; + const dx = prev.x - points[i]?.x!; + const dy = prev.y - points[i]?.y!; const distance = Math.sqrt(dx * dx + dy * dy); - prev = points[i]; + prev = points[i]!; amount += distance / this._width; uvs[index] = amount; @@ -107,17 +106,17 @@ export class FixedWidthSimpleRopeGeometry extends MeshGeometry { if (points.length < 1) { return; } - let lastPoint = points[0]; + let lastPoint = points[0]!; let nextPoint; let perpX = 0; let perpY = 0; - const vertices = this.buffers[0].data; + const vertices = this.buffers[0]?.data!; const total = points.length; for (let i = 0; i < total; i++) { - const point = points[i]; + const point = points[i]!; const index = i * 4; if (i < points.length - 1) { - nextPoint = points[i + 1]; + nextPoint = points[i + 1]!; } else { nextPoint = point; } @@ -140,7 +139,7 @@ export class FixedWidthSimpleRopeGeometry extends MeshGeometry { vertices[index + 3] = point.y - perpY; lastPoint = point; } - this.buffers[0].update(); + this.buffers[0]?.update(); } public update(): void { diff --git a/src/layers/CustomDisplayObjects/UniformTextureStretchRopeGeometry.ts b/src/layers/CustomDisplayObjects/UniformTextureStretchRopeGeometry.ts index 0ae44a4a..80e6039b 100644 --- a/src/layers/CustomDisplayObjects/UniformTextureStretchRopeGeometry.ts +++ b/src/layers/CustomDisplayObjects/UniformTextureStretchRopeGeometry.ts @@ -55,14 +55,14 @@ export class UniformTextureStretchRopeGeometry extends MeshGeometry { const total = points.length; // - 1; let totalLength = 0; - let prevPoint = points[0]; + let prevPoint = points[0]!; for (let i = 0; i < total; i++) { - const dx = prevPoint.x - points[i].x; - const dy = prevPoint.y - points[i].y; + const dx = prevPoint.x - points[i]?.x!; + const dy = prevPoint.y - points[i]?.y!; const distance = Math.sqrt(dx * dx + dy * dy); - prevPoint = points[i]; + prevPoint = points[i]!; totalLength += distance; } @@ -75,18 +75,18 @@ export class UniformTextureStretchRopeGeometry extends MeshGeometry { uvs[3] = 1; let amount = 0; - let prev = points[0]; + let prev = points[0]!; for (let i = 0; i < total; i++) { // time to do some smart drawing! const index = i * 4; // calculate pixel distance from previous point - const dx = prev.x - points[i].x; - const dy = prev.y - points[i].y; + const dx = prev.x - points[i]?.x!; + const dy = prev.y - points[i]?.y!; const distance = Math.sqrt(dx * dx + dy * dy); - prev = points[i]; + prev = points[i]!; // strech texture on distance/length instead of point/points.length to get a more correct strech amount += distance / totalLength; @@ -127,20 +127,20 @@ export class UniformTextureStretchRopeGeometry extends MeshGeometry { return; } - let lastPoint = points[0]; + let lastPoint = points[0]!; let nextPoint; let perpX = 0; let perpY = 0; - const vertices = this.buffers[0].data; + const vertices = this.buffers[0]?.data!; const total = points.length; for (let i = 0; i < total; i++) { - const point = points[i]; + const point = points[i]!; const index = i * 4; if (i < points.length - 1) { - nextPoint = points[i + 1]; + nextPoint = points[i + 1]!; } else { nextPoint = point; } @@ -165,7 +165,7 @@ export class UniformTextureStretchRopeGeometry extends MeshGeometry { lastPoint = point; } - this.buffers[0].update(); + this.buffers[0]?.update(); } public update(): void { diff --git a/src/layers/GeomodelCanvasLayer.ts b/src/layers/GeomodelCanvasLayer.ts index f64db568..ba050315 100644 --- a/src/layers/GeomodelCanvasLayer.ts +++ b/src/layers/GeomodelCanvasLayer.ts @@ -12,7 +12,7 @@ type SurfacePaths = { }; export class GeomodelCanvasLayer extends CanvasLayer { - rescaleEvent: OnRescaleEvent; + rescaleEvent: OnRescaleEvent | undefined; surfaceAreasPaths: SurfacePaths[] = []; @@ -69,65 +69,72 @@ export class GeomodelCanvasLayer extends CanvasLayer { } generateSurfaceAreasPaths(): void { - this.surfaceAreasPaths = this.data.areas.reduce((acc: SurfacePaths[], s: SurfaceArea) => { - const polygons = this.createPolygons(s.data); - const mapped: SurfacePaths[] = polygons.map((polygon: number[]) => ({ - color: this.colorToCSSColor(s.color), - path: this.generatePolygonPath(polygon), - })); - acc.push(...mapped); - return acc; - }, []); + this.surfaceAreasPaths = + this.data?.areas.reduce((acc: SurfacePaths[], s: SurfaceArea) => { + const polygons = this.createPolygons(s.data); + const mapped: SurfacePaths[] = polygons.map((polygon: number[]) => ({ + color: this.colorToCSSColor(s.color), + path: this.generatePolygonPath(polygon), + })); + acc.push(...mapped); + return acc; + }, []) ?? []; } generateSurfaceLinesPaths(): void { - this.surfaceLinesPaths = this.data.lines.reduce((acc: SurfacePaths[], l: SurfaceLine) => { - const lines = this.generateLinePaths(l); - const mapped: SurfacePaths[] = lines.map((path: Path2D) => ({ color: this.colorToCSSColor(l.color), path })); - acc.push(...mapped); - return acc; - }, []); + this.surfaceLinesPaths = + this.data?.lines.reduce((acc: SurfacePaths[], l: SurfaceLine) => { + const lines = this.generateLinePaths(l); + const mapped: SurfacePaths[] = lines.map((path: Path2D) => ({ color: this.colorToCSSColor(l.color), path })); + acc.push(...mapped); + return acc; + }, []) ?? []; } drawPolygonPath = (color: string, path: Path2D): void => { const { ctx } = this; - ctx.fillStyle = color; - ctx.fill(path); + if (ctx != null) { + ctx.fillStyle = color; + ctx.fill(path); + } }; drawLinePath = (color: string, path: Path2D): void => { const { ctx } = this; - ctx.strokeStyle = color; - ctx.stroke(path); + + if (ctx != null) { + ctx.strokeStyle = color; + ctx.stroke(path); + } }; createPolygons = (data: number[][]): number[][] => { const polygons: number[][] = []; - let polygon: number[] = null; + let polygon: number[] = []; // Start generating polygons for (let i = 0; i < data.length; i++) { // Generate top of polygon as long as we have valid values - const topIsValid = !!data[i][1]; + const topIsValid = !!data[i]?.[1]; if (topIsValid) { if (polygon === null) { polygon = []; } - polygon.push(data[i][0], data[i][1]); + polygon.push(data[i]?.[0]!, data[i]?.[1]!); } const endIsReached = i === data.length - 1; if (!topIsValid || endIsReached) { - if (polygon) { + if (polygon.length > 0) { // Generate bottom of polygon for (let j: number = !topIsValid ? i - 1 : i; j >= 0; j--) { - if (!data[j][1]) { + if (!data[j]?.[1]) { break; } - polygon.push(data[j][0], data[j][2] || this.maxDepth); + polygon.push(data[j]?.[0]!, data[j]?.[2] || this.maxDepth); } polygons.push(polygon); - polygon = null; + polygon = []; } } } @@ -138,9 +145,9 @@ export class GeomodelCanvasLayer extends CanvasLayer { generatePolygonPath = (polygon: number[]): Path2D => { const path = new Path2D(); - path.moveTo(polygon[0], polygon[1]); + path.moveTo(polygon[0]!, polygon[1]!); for (let i = 2; i < polygon.length; i += 2) { - path.lineTo(polygon[i], polygon[i + 1]); + path.lineTo(polygon[i]!, polygon[i + 1]!); } path.closePath(); @@ -152,22 +159,22 @@ export class GeomodelCanvasLayer extends CanvasLayer { const { data: d } = s; let penDown = false; - let path = null; + let path: Path2D | undefined; for (let i = 0; i < d.length; i++) { - if (d[i][1]) { - if (penDown) { - path.lineTo(d[i][0], d[i][1]); + if (d[i]?.[1]) { + if (penDown && path) { + path.lineTo(d[i]?.[0]!, d[i]?.[1]!); } else { path = new Path2D(); - path.moveTo(d[i][0], d[i][1]); + path.moveTo(d[i]?.[0]!, d[i]?.[1]!); penDown = true; } - } else if (penDown) { + } else if (penDown && path) { paths.push(path); penDown = false; } } - if (penDown) { + if (penDown && path) { paths.push(path); } diff --git a/src/layers/GeomodelLabelsLayer.ts b/src/layers/GeomodelLabelsLayer.ts index d765ce2c..51bed7e8 100644 --- a/src/layers/GeomodelLabelsLayer.ts +++ b/src/layers/GeomodelLabelsLayer.ts @@ -33,11 +33,11 @@ export class GeomodelLabelsLayer extends CanvasLayer { defaultTextColor: string = DEFAULT_TEXT_COLOR; defaultFont: string = DEFAULT_FONT; - rescaleEvent: OnRescaleEvent; - isLabelsOnLeftSide: boolean = true; + rescaleEvent: OnRescaleEvent | undefined; + isLabelsOnLeftSide = true; maxFontSizeInWorldCoordinates: number = MAX_FONT_SIZE_IN_WORLD_COORDINATES; - isXFlipped: boolean = false; - areasWithAvgTopDepth: SurfaceAreaWithAvgTopDepth[] = null; + isXFlipped = false; + areasWithAvgTopDepth: SurfaceAreaWithAvgTopDepth[] = []; constructor(id?: string, options?: GeomodelLayerLabelsOptions) { super(id, options); @@ -54,11 +54,11 @@ export class GeomodelLabelsLayer extends CanvasLayer { override setData(data: T): void { super.setData(data); - this.areasWithAvgTopDepth = null; + this.areasWithAvgTopDepth = []; } generateSurfacesWithAvgDepth(): void { - const { areas } = this.data; + const areas = this.data?.areas ?? []; this.areasWithAvgTopDepth = areas.reduce((acc: SurfaceAreaWithAvgTopDepth[], area: SurfaceArea) => { // Filter surfaces without label if (!area.label) { @@ -118,7 +118,7 @@ export class GeomodelLabelsLayer extends CanvasLayer { return; } - if (!this.areasWithAvgTopDepth) { + if (this.areasWithAvgTopDepth.length <= 0) { this.generateSurfacesWithAvgDepth(); } @@ -147,13 +147,15 @@ export class GeomodelLabelsLayer extends CanvasLayer { } drawLineLabels(): void { - this.data.lines.filter((surfaceLine: SurfaceLine) => surfaceLine.label).forEach((surfaceLine: SurfaceLine) => this.drawLineLabel(surfaceLine)); + this.data?.lines.filter((surfaceLine: SurfaceLine) => surfaceLine.label).forEach((surfaceLine: SurfaceLine) => this.drawLineLabel(surfaceLine)); } drawAreaLabel = (surfaceArea: SurfaceArea, nextSurfaceArea: SurfaceArea | null, surfaces: SurfaceArea[], i: number): void => { const { data } = surfaceArea; const { ctx, maxFontSizeInWorldCoordinates, isXFlipped } = this; - const { xScale, yScale, xRatio, yRatio, zFactor } = this.rescaleEvent; + const { xScale, yScale, xRatio, yRatio, zFactor } = this.rescaleEvent!; + if (ctx == null) return; + let isLabelsOnLeftSide = this.checkDrawLabelsOnLeftSide(); const margins = (this.options.margins || this.defaultMargins) * (isXFlipped ? -1 : 1); const marginsInWorldCoords = margins / xRatio; @@ -168,14 +170,14 @@ export class GeomodelLabelsLayer extends CanvasLayer { } } - const leftEdge = xScale.invert(xScale.range()[0]) + marginsInWorldCoords; - const rightEdge = xScale.invert(xScale.range()[1]) - marginsInWorldCoords; - const [surfaceAreaLeftEdge, surfaceAreaRightEdge] = this.getSurfacesAreaEdges(); + const leftEdge = xScale.invert(xScale.range()[0]!) + marginsInWorldCoords; + const rightEdge = xScale.invert(xScale.range()[1]!) - marginsInWorldCoords; + const [surfaceAreaLeftEdge, surfaceAreaRightEdge] = this.getSurfacesAreaEdges() as [number, number]; // Get label metrics ctx.save(); ctx.font = `${fontSizeInWorldCoords * yRatio}px ${this.options.font || this.defaultFont}`; - let labelMetrics = ctx.measureText(surfaceArea.label); + let labelMetrics = ctx.measureText(surfaceArea.label ?? ''); let labelLengthInWorldCoords = labelMetrics.width / xRatio; // Check if label will fit horizontally @@ -200,8 +202,8 @@ export class GeomodelLabelsLayer extends CanvasLayer { startPos = isXFlipped ? Math.max(surfaceAreaRightEdge, rightEdge) : Math.min(surfaceAreaRightEdge, rightEdge); } - const topEdge = yScale.invert(yScale.range()[0]); - const bottomEdge = yScale.invert(yScale.range()[1]); + const topEdge = yScale.invert(yScale.range()[0]!); + const bottomEdge = yScale.invert(yScale.range()[1]!); // Calculate where to sample points const dirSteps = 5; @@ -211,14 +213,14 @@ export class GeomodelLabelsLayer extends CanvasLayer { const dirStep = (labelLengthInWorldCoords / dirSteps) * (isLabelsOnLeftSide ? 1 : -1) * (isXFlipped ? -1 : 1); // Sample points from top and calculate position - const topData = data.map((d) => [d[0], d[1]]); + const topData = data.map((d) => [d[0]!, d[1]!]); const topPos = this.calcPos(topData, startPos, posSteps, posStep, topEdge, bottomEdge); if (!topPos) { return; } // Sample points from bottom and calculate position - const bottomData = data.map((d) => [d[0], d[2]]); + const bottomData = data.map((d) => [d[0]!, d[2]!]); let bottomPos = this.calcPos( bottomData, startPos, @@ -226,7 +228,7 @@ export class GeomodelLabelsLayer extends CanvasLayer { posStep, topEdge, bottomEdge, - nextSurfaceArea ? nextSurfaceArea.data.map((d) => [d[0], d[1]]) : null, + nextSurfaceArea?.data.map((d) => [d[0]!, d[1]!]) ?? [], surfaces, i, ); @@ -244,7 +246,7 @@ export class GeomodelLabelsLayer extends CanvasLayer { // Use reduced fontsize fontSizeInWorldCoords = thickness; ctx.font = `${fontSizeInWorldCoords * yRatio}px ${this.options.font || this.defaultFont}`; - labelMetrics = ctx.measureText(surfaceArea.label); + labelMetrics = ctx.measureText(surfaceArea.label ?? ''); labelLengthInWorldCoords = labelMetrics.width / xRatio; } // Sample points from top and bottom and calculate direction vector @@ -261,7 +263,7 @@ export class GeomodelLabelsLayer extends CanvasLayer { 0, Math.PI / 4, 4, - nextSurfaceArea ? nextSurfaceArea.data.map((d) => [d[0], d[1]]) : null, + nextSurfaceArea?.data.map((d) => [d[0]!, d[1]!]) ?? [], surfaces, i, ); @@ -271,20 +273,24 @@ export class GeomodelLabelsLayer extends CanvasLayer { const textX = startPos; const textY = (topPos.y + bottomPos.y) / 2; const textAngle = isXFlipped ? -scaledAngle : scaledAngle; - ctx.textAlign = isLabelsOnLeftSide ? 'left' : 'right'; - ctx.translate(xScale(textX), yScale(textY)); - ctx.rotate(textAngle); - ctx.fillStyle = this.options.textColor || this.defaultTextColor; - ctx.font = `${fontSizeInWorldCoords * yRatio}px ${this.options.font || this.defaultFont}`; - ctx.textBaseline = 'middle'; - ctx.fillText(surfaceArea.label, 0, 0); - ctx.restore(); + if (ctx) { + ctx.textAlign = isLabelsOnLeftSide ? 'left' : 'right'; + ctx.translate(xScale(textX), yScale(textY)); + ctx.rotate(textAngle); + ctx.fillStyle = this.options.textColor || this.defaultTextColor; + ctx.font = `${fontSizeInWorldCoords * yRatio}px ${this.options.font || this.defaultFont}`; + ctx.textBaseline = 'middle'; + ctx.fillText(surfaceArea.label ?? '', 0, 0); + + ctx.restore(); + } }; drawLineLabel = (s: SurfaceLine): void => { const { ctx, isXFlipped } = this; - const { xScale, yScale, xRatio, yRatio, zFactor } = this.rescaleEvent; + const { xScale, yScale, xRatio, yRatio, zFactor } = this.rescaleEvent!; + if (ctx == null) return; const isLabelsOnLeftSide = this.checkDrawLabelsOnLeftSide(); const marginsInWorldCoords = this.getMarginsInWorldCoordinates(); const maxFontSize = this.options.maxFontSize || this.defaultMaxFontSize; @@ -296,9 +302,9 @@ export class GeomodelLabelsLayer extends CanvasLayer { const labelMetrics = ctx.measureText(s.label); const labelLengthInWorldCoords = labelMetrics.width / xRatio; - const leftEdge = xScale.invert(xScale.range()[0]) + marginsInWorldCoords; - const rightEdge = xScale.invert(xScale.range()[1]) - marginsInWorldCoords; - const [surfaceAreaLeftEdge, surfaceAreaRightEdge] = this.getSurfacesAreaEdges(); + const leftEdge = xScale.invert(xScale.range()[0]!) + marginsInWorldCoords; + const rightEdge = xScale.invert(xScale.range()[1]!) - marginsInWorldCoords; + const [surfaceAreaLeftEdge, surfaceAreaRightEdge] = this.getSurfacesAreaEdges() as [number, number]; // Find edge where to draw let startPos: number; @@ -326,14 +332,16 @@ export class GeomodelLabelsLayer extends CanvasLayer { const textDir = Vector2.angleRight(dir) - (isLabelsOnLeftSide ? Math.PI : 0); // Draw label - ctx.textAlign = isLabelsOnLeftSide ? 'right' : 'left'; - ctx.translate(xScale(textX), yScale(textY)); - ctx.rotate(textDir); - ctx.fillStyle = this.colorToCSSColor(s.color); - ctx.textBaseline = 'middle'; - ctx.fillText(s.label, 0, 0); - - ctx.restore(); + if (ctx) { + ctx.textAlign = isLabelsOnLeftSide ? 'right' : 'left'; + ctx.translate(xScale(textX), yScale(textY)); + ctx.rotate(textDir); + ctx.fillStyle = this.colorToCSSColor(s.color); + ctx.textBaseline = 'middle'; + ctx.fillText(s.label, 0, 0); + + ctx.restore(); + } }; colorToCSSColor(color: number | string): string { @@ -342,7 +350,6 @@ export class GeomodelLabelsLayer extends CanvasLayer { } let hexString = color.toString(16); - // eslint-disable-next-line no-magic-numbers hexString = '000000'.substr(0, 6 - hexString.length) + hexString; return `#${hexString}`; } @@ -352,12 +359,12 @@ export class GeomodelLabelsLayer extends CanvasLayer { offset: number, count: number, step: number, - topLimit: number = null, - bottomLimit: number = null, - alternativeSurfaceData: number[][] = null, + topLimit?: number, + bottomLimit?: number, + alternativeSurfaceData?: number[][], surfaces: SurfaceArea[] | null = null, - currentSurfaceIndex: number = null, - ): Vector2 { + currentSurfaceIndex?: number, + ): Vector2 | null { const pos = Vector2.zero.mutable; let samples = 0; for (let i = 0; i < count; i++) { @@ -381,12 +388,12 @@ export class GeomodelLabelsLayer extends CanvasLayer { getAlternativeYValueIfAvailable( x: number, - topLimit: number, - bottomLimit: number, - alternativeSurfaceData: number[][], - surfaces: SurfaceArea[] | null, - currentSurfaceIndex: number, - ): number { + topLimit?: number, + bottomLimit?: number, + alternativeSurfaceData?: number[][], + surfaces: SurfaceArea[] | null = null, + currentSurfaceIndex?: number, + ): number | null { if (!alternativeSurfaceData) { return null; } @@ -397,12 +404,7 @@ export class GeomodelLabelsLayer extends CanvasLayer { let si = currentSurfaceIndex + 1; while (altY == null && si < surfaces.length) { const altSurface = surfaces[si++]; - altY = findSampleAtPos( - altSurface.data.map((d: number[]) => [d[0], d[1]]), - x, - topLimit, - bottomLimit, - ); + altY = findSampleAtPos(altSurface?.data.map((d: number[]) => [d[0]!, d[1]!]) ?? [], x, topLimit, bottomLimit); } } return altY; @@ -415,8 +417,8 @@ export class GeomodelLabelsLayer extends CanvasLayer { step: number, zFactor: number, initalVector: Vector2 = Vector2.left, - topLimit: number = null, - bottomLimit: number = null, + topLimit?: number, + bottomLimit?: number, ): Vector2 { const dir = initalVector.mutable; @@ -447,14 +449,14 @@ export class GeomodelLabelsLayer extends CanvasLayer { count: number, step: number, initalVector: Vector2 = Vector2.left, - topLimit: number = null, - bottomLimit: number = null, - minReductionAngle: number = 0, + topLimit: number, + bottomLimit: number, + minReductionAngle = 0, maxReductionAngle: number = Math.PI / 4, - angleReductionExponent: number = 4, - alternativeSurfaceBottomData: number[][] = null, + angleReductionExponent = 4, + alternativeSurfaceBottomData: number[][], surfaces: SurfaceArea[] | null = null, - currentSurfaceIndex: number = null, + currentSurfaceIndex: number, ): number { const angles: number[] = []; const tmpVec = Vector2.zero.mutable; @@ -483,7 +485,7 @@ export class GeomodelLabelsLayer extends CanvasLayer { } else { if (topY !== null) { tmpVec.set(x, (topY + usedBottomY) / 2); - tmpVec.sub(vecAtEnd); + tmpVec.sub(vecAtEnd!); angles.push(Vector2.angleRight(tmpVec)); } else { @@ -492,7 +494,7 @@ export class GeomodelLabelsLayer extends CanvasLayer { } } - const refAngle = angles[0]; + const refAngle = angles[0]!; const offsetAngles = angles.map((d: number) => d - refAngle); let factors = 0; const offsetSum = offsetAngles.reduce((acc: number, v: number) => { @@ -506,50 +508,51 @@ export class GeomodelLabelsLayer extends CanvasLayer { } updateXFlipped(): void { - const { xBounds } = this.rescaleEvent; + const { xBounds } = this.rescaleEvent!; this.isXFlipped = xBounds[0] > xBounds[1]; } getMarginsInWorldCoordinates(): number { - const { xRatio } = this.rescaleEvent; + const { xRatio } = this.rescaleEvent!; const margins = (this.options.margins || this.defaultMargins) * (this.isXFlipped ? -1 : 1); const marginsInWorldCoords = margins / xRatio; return marginsInWorldCoords; } getSurfacesAreaEdges(): number[] { - const endPoints = this.data.areas.reduce((acc, area) => { - const { data } = area; - const firstValidPoint = data.find((d: number[]) => d[1] != null); - if (firstValidPoint) { - acc.push(firstValidPoint[0]); - } - // TODO: Use findLast() when TypeScript stops complaining about it - for (let i = data.length - 1; i >= 0; i--) { - if (data[i][1] != null) { - acc.push(data[i][0]); - break; + const endPoints = + this.data?.areas.reduce((acc, area) => { + const { data } = area; + const firstValidPoint = data.find((d: number[]) => d[1] != null); + if (firstValidPoint) { + acc.push(firstValidPoint[0]!); + } + // TODO: Use findLast() when TypeScript stops complaining about it + for (let i = data.length - 1; i >= 0; i--) { + if (data[i]?.[1] != null) { + acc.push(data[i]?.[0]!); + break; + } } - } - return acc; - }, []); + return acc; + }, [] as number[]) ?? []; endPoints.push( - ...this.data.lines.reduce((acc, line) => { + ...(this.data?.lines.reduce((acc, line) => { const { data } = line; const firstValidPoint = data.find((d: number[]) => d[1] != null); if (firstValidPoint) { - acc.push(firstValidPoint[0]); + acc.push(firstValidPoint[0]!); } // TODO: Use findLast() when TypeScript stops complaining about it for (let i = data.length - 1; i >= 0; i--) { - if (data[i][1] != null) { - acc.push(data[i][0]); + if (data[i]?.[1] != null) { + acc.push(data[i]?.[0]!); break; } } return acc; - }, []), + }, [] as number[]) ?? []), ); const minX = Math.min(...endPoints); @@ -567,11 +570,11 @@ export class GeomodelLabelsLayer extends CanvasLayer { return true; } - const { xScale, yScale, xRatio } = this.rescaleEvent; + const { xScale, yScale, xRatio } = this.rescaleEvent!; const t = 200; // TODO: Use actual size of largest label or average size of all - const [dx1, dx2] = xScale.domain(); - const [dy1, dy2] = yScale.domain(); + const [dx1, dx2] = xScale.domain() as [number, number]; + const [dy1, dy2] = yScale.domain() as [number, number]; let top = referenceSystem.interpolators.curtain.getIntersects(dy1, 1, 0) as number[][]; if (top.length === 0) { @@ -582,8 +585,8 @@ export class GeomodelLabelsLayer extends CanvasLayer { bottom = [referenceSystem.interpolators.curtain.getPointAt(1.0) as number[]]; } - const maxX = Math.max(top[0][0], bottom[0][0]); - const minX = Math.min(top[0][0], bottom[0][0]); + const maxX = Math.max(top[0]?.[0]!, bottom[0]?.[0]!); + const minX = Math.min(top[0]?.[0]!, bottom[0]?.[0]!); const wbBBox = { left: isXFlipped ? maxX : minX, @@ -594,7 +597,7 @@ export class GeomodelLabelsLayer extends CanvasLayer { const screenLeftEdge = dx1 + margin; const screenRightEdge = dx2 - margin; - const [surfaceAreaLeftEdge, surfaceAreaRightEdge] = this.getSurfacesAreaEdges(); + const [surfaceAreaLeftEdge, surfaceAreaRightEdge] = this.getSurfacesAreaEdges() as [number, number]; const leftLimit = isXFlipped ? Math.min(screenLeftEdge, surfaceAreaLeftEdge) : Math.max(screenLeftEdge, surfaceAreaLeftEdge); const rightLimit = isXFlipped ? Math.max(screenRightEdge, surfaceAreaRightEdge) : Math.min(screenRightEdge, surfaceAreaRightEdge); @@ -608,7 +611,7 @@ export class GeomodelLabelsLayer extends CanvasLayer { spaceOnLeftSide > spaceOnRightSide || spaceOnLeftSideInScreenCoordinates > t || (spaceOnLeftSideInScreenCoordinates < t && spaceOnRightSideInScreenCoordinates < t && isXFlipped) || - bottom[0][1] < dy1; + bottom[0]?.[1]! < dy1; return isLabelsOnLeftSide; } diff --git a/src/layers/GeomodelLayerV2.ts b/src/layers/GeomodelLayerV2.ts index 4f89989c..9ef5f62f 100644 --- a/src/layers/GeomodelLayerV2.ts +++ b/src/layers/GeomodelLayerV2.ts @@ -7,7 +7,7 @@ import { SURFACE_LINE_WIDTH } from '../constants'; const DEFAULT_Y_BOTTOM = 10000; export class GeomodelLayerV2 extends PixiLayer { - private isPreRendered: boolean = false; + private isPreRendered = false; override onRescale(event: OnRescaleEvent): void { super.onRescale(event); @@ -30,31 +30,31 @@ export class GeomodelLayerV2 extends PixiLayer { } preRender(): void { - const { data }: { data: SurfaceData } = this; + const { data } = this; if (!data) { return; } - data.areas.forEach((a: SurfaceArea) => this.generateAreaPolygon(a)); - data.lines.forEach((l: SurfaceLine) => this.generateSurfaceLine(l)); + data.areas.forEach((a) => this.generateAreaPolygon(a)); + data.lines.forEach((l) => this.generateSurfaceLine(l)); this.isPreRendered = true; } createPolygons = (data: number[][]): number[][] => { const polygons: number[][] = []; - let polygon: number[] = null; + let polygon: number[] | undefined; // Start generating polygons for (let i = 0; i < data.length; i++) { // Generate top of polygon as long as we have valid values - const topIsValid = !!data[i][1]; + const topIsValid = !!data[i]?.[1]; if (topIsValid) { - if (polygon === null) { + if (polygon == null) { polygon = []; } - polygon.push(data[i][0], data[i][1]); + polygon.push(data[i]?.[0]!, data[i]?.[1]!); } const endIsReached = i === data.length - 1; @@ -62,13 +62,13 @@ export class GeomodelLayerV2 extends PixiLayer { if (polygon) { // Generate bottom of polygon for (let j: number = !topIsValid ? i - 1 : i; j >= 0; j--) { - if (!data[j][1]) { + if (!data[j]?.[1]) { break; } - polygon.push(data[j][0], data[j][2] || DEFAULT_Y_BOTTOM); + polygon.push(data[j]?.[0]!, data[j]?.[2] || DEFAULT_Y_BOTTOM); } polygons.push(polygon); - polygon = null; + polygon = undefined; } } } diff --git a/src/layers/GridLayer.ts b/src/layers/GridLayer.ts index d0052bb8..ebc31b70 100644 --- a/src/layers/GridLayer.ts +++ b/src/layers/GridLayer.ts @@ -4,10 +4,10 @@ import { ScaleLinear } from 'd3-scale'; import { LayerOptions } from './base/Layer'; // constants -const MINORCOLOR: string = 'lightgray'; -const MAJORCOLOR: string = 'gray'; -const MINORWIDTH: number = 0.25; -const MAJORWIDTH: number = 0.75; +const MINORCOLOR = 'lightgray'; +const MAJORCOLOR = 'gray'; +const MINORWIDTH = 0.25; +const MAJORWIDTH = 0.75; const defaultOptions = { minorColor: MINORCOLOR, @@ -29,8 +29,8 @@ export interface OnGridLayerUpdateEvent extends OnUpdateEvent { } export class GridLayer extends CanvasLayer { - private _offsetX: number = 0; - private _offsetY: number = 0; + private _offsetX = 0; + private _offsetY = 0; constructor(id?: string, options?: GridLayerOptions) { super(id, options); @@ -65,11 +65,11 @@ export class GridLayer extends CanvasLayer { return; } - const xScale = event.xScale.copy(); - const yScale = event.yScale.copy(); + const xScale = event.xScale!.copy(); + const yScale = event.yScale!.copy(); - const xDomain = xScale.domain(); - const yDomain = yScale.domain(); + const xDomain = xScale.domain() as [number, number]; + const yDomain = yScale.domain() as [number, number]; const offsetX = this.offsetX; const offsetY = this.offsetY; @@ -77,8 +77,8 @@ export class GridLayer extends CanvasLayer { xScale.domain([xDomain[0] - offsetX, xDomain[1] - offsetX]); yScale.domain([yDomain[0] - offsetY, yDomain[1] - offsetY]); - const [rx1, rx2] = xScale.range(); - const [ry1, ry2] = yScale.range(); + const [rx1, rx2] = xScale.range() as [number, number]; + const [ry1, ry2] = yScale.range() as [number, number]; ctx.lineWidth = minorWidth || MINORWIDTH; ctx.strokeStyle = minorColor || MINORCOLOR; @@ -101,27 +101,31 @@ export class GridLayer extends CanvasLayer { private renderTicksX(xscale: ScaleLinear, xticks: number[], ry1: number, ry2: number): void { xticks.forEach((tx: number) => { const x = xscale(tx); - this.ctx.beginPath(); - this.ctx.moveTo(x, ry1); - this.ctx.lineTo(x, ry2); - this.ctx.stroke(); + if (this.ctx != null) { + this.ctx.beginPath(); + this.ctx.moveTo(x, ry1); + this.ctx.lineTo(x, ry2); + this.ctx.stroke(); + } }); } private renderTicksY(yscale: ScaleLinear, yticks: number[], rx1: number, rx2: number): void { yticks.forEach((ty: number) => { const y = yscale(ty); - this.ctx.beginPath(); - this.ctx.moveTo(rx1, y); - this.ctx.lineTo(rx2, y); - this.ctx.stroke(); + if (this.ctx != null) { + this.ctx.beginPath(); + this.ctx.moveTo(rx1, y); + this.ctx.lineTo(rx2, y); + this.ctx.stroke(); + } }); } private mapMinorTicks(ticks: number[]): number[] { let xminticks: number[] = []; if (ticks.length >= 2) { - xminticks = ticks.map((v: number) => v + (ticks[1] - ticks[0]) / 2); + xminticks = ticks.map((v: number) => v + (ticks[1]! - ticks[0]!) / 2); xminticks.pop(); } return xminticks; diff --git a/src/layers/ImageCanvasLayer.ts b/src/layers/ImageCanvasLayer.ts index 931f08e3..ad23ba8d 100644 --- a/src/layers/ImageCanvasLayer.ts +++ b/src/layers/ImageCanvasLayer.ts @@ -15,7 +15,7 @@ export interface OnImageLayerUpdateEvent extends OnUpdateEvent { export type OnImageLayerRescaleEvent = OnImageLayerUpdateEvent & OnRescaleEvent; export class ImageLayer extends CanvasLayer { - img: HTMLImageElement; + img: HTMLImageElement | undefined; override onMount(event: OnMountEvent): void { super.onMount(event); @@ -26,7 +26,9 @@ export class ImageLayer extends CanvasLayer { override onUpdate(event: OnImageLayerUpdateEvent): void { super.onUpdate(event); - this.img.src = event.url; + if (this.img != null) { + this.img.src = event.url; + } this.render(event); } @@ -37,19 +39,23 @@ export class ImageLayer extends CanvasLayer { } render(event: OnImageLayerUpdateEvent): void { - const width = parseInt(this.elm.getAttribute('width'), 10); - const height = parseInt(this.elm.getAttribute('height'), 10); + const width = parseInt(this.elm?.getAttribute('width') ?? '0', 10); + const height = parseInt(this.elm?.getAttribute('height') ?? '0', 10); const { xScale, yScale, xRatio, yRatio, x, y } = event; const calcWidth = width * (xRatio || 1); const calcHeight = height * (yRatio || 1); this.clearCanvas(); - if (this.isLoading) { - this.img.onload = (): void => { - this.isLoading = false; - this.ctx.drawImage(this.img, xScale(x || 0), yScale(y || 0), calcWidth, calcHeight); - }; - } else { - this.ctx.drawImage(this.img, xScale(x || 0), yScale(y || 0), calcWidth, calcHeight); + + if (this.img != null) { + if (this.isLoading) { + this.img.onload = (): void => { + this.isLoading = false; + // An extra undefined check should happen here as the execution doesn't happen synchronously + this.img != null && this.ctx?.drawImage(this.img, xScale(x || 0), yScale(y || 0), calcWidth, calcHeight); + }; + } else { + this.ctx?.drawImage(this.img, xScale(x || 0), yScale(y || 0), calcWidth, calcHeight); + } } } } diff --git a/src/layers/ReferenceLineLayer.ts b/src/layers/ReferenceLineLayer.ts index a2781dce..ae968ef9 100644 --- a/src/layers/ReferenceLineLayer.ts +++ b/src/layers/ReferenceLineLayer.ts @@ -16,7 +16,7 @@ export type ReferenceLine = { fontSize?: string; }; -export interface ReferenceLineLayerOptions extends LayerOptions {} +export type ReferenceLineLayerOptions = LayerOptions; const foldReferenceLine = ( options: { @@ -66,36 +66,41 @@ export class ReferenceLineLayer extends CanvasLayer { private drawDashed(dashed: ReferenceLine) { const { ctx } = this; const { canvas } = this; - const y = this.yScale(dashed.depth); - ctx.save(); - ctx.strokeStyle = dashed.color; - this.setCtxLineStyle(ctx, dashed); - this.setCtxLineWidth(ctx, dashed); - ctx.beginPath(); - ctx.moveTo(0, y); - ctx.lineTo(canvas.width, y); - ctx.stroke(); - ctx.restore(); - if (dashed.text) { - this.drawText(ctx, dashed, ctx.canvas.width, y); + + if (ctx != null && canvas != null) { + const y = this.yScale?.(dashed.depth)!; + ctx.save(); + ctx.strokeStyle = dashed.color; + this.setCtxLineStyle(ctx, dashed); + this.setCtxLineWidth(ctx, dashed); + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(canvas.width, y); + ctx.stroke(); + ctx.restore(); + if (dashed.text) { + this.drawText(ctx, dashed, ctx.canvas.width, y); + } } } private drawSolid(solid: ReferenceLine) { const { ctx } = this; const { canvas } = this; - const y = this.yScale(solid.depth); - ctx.save(); - ctx.strokeStyle = solid.color; - this.setCtxLineStyle(ctx, solid); - this.setCtxLineWidth(ctx, solid); - ctx.beginPath(); - ctx.moveTo(0, y); - ctx.lineTo(canvas.width, y); - ctx.stroke(); - ctx.restore(); - if (solid.text) { - this.drawText(ctx, solid, ctx.canvas.width, y); + const y = this.yScale!(solid.depth); + if (ctx != null && canvas != null) { + ctx.save(); + ctx.strokeStyle = solid.color; + this.setCtxLineStyle(ctx, solid); + this.setCtxLineWidth(ctx, solid); + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(canvas.width, y); + ctx.stroke(); + ctx.restore(); + if (solid.text) { + this.drawText(ctx, solid, ctx.canvas.width, y); + } } } @@ -103,25 +108,27 @@ export class ReferenceLineLayer extends CanvasLayer { const factor = 4; const min = 2.5; const max = 500; - const { ctx } = this; - const { canvas } = this; - const waveHeight = calcSize(factor, min, max, this.yScale); - const wavePeriod = waveHeight * 2; - const y = this.yScale(wavy.depth) - waveHeight; - const steps = Math.ceil(canvas.width / wavePeriod) + 1; - const xOffset = this.xScale(0) % wavePeriod; - ctx.save(); - ctx.strokeStyle = wavy.color; - this.setCtxLineStyle(ctx, wavy); - this.setCtxLineWidth(ctx, wavy); - for (let i = -1; i < steps; i++) { - ctx.beginPath(); - ctx.arc(i * wavePeriod + xOffset + waveHeight, y, waveHeight, 0, Math.PI); - ctx.stroke(); - } - ctx.restore(); - if (wavy.text) { - this.drawText(ctx, wavy, ctx.canvas.width, y); + const { ctx, canvas } = this; + + if (this.xScale != null && this.yScale != null && canvas != null && ctx != null) { + const waveHeight = calcSize(factor, min, max, this.yScale); + const wavePeriod = waveHeight * 2; + const y = this.yScale(wavy.depth) - waveHeight; + const steps = Math.ceil(canvas.width / wavePeriod) + 1; + const xOffset = this.xScale(0) % wavePeriod; + ctx.save(); + ctx.strokeStyle = wavy.color; + this.setCtxLineStyle(ctx, wavy); + this.setCtxLineWidth(ctx, wavy); + for (let i = -1; i < steps; i++) { + ctx.beginPath(); + ctx.arc(i * wavePeriod + xOffset + waveHeight, y, waveHeight, 0, Math.PI); + ctx.stroke(); + } + ctx.restore(); + if (wavy.text) { + this.drawText(ctx, wavy, ctx.canvas.width, y); + } } } @@ -129,12 +136,13 @@ export class ReferenceLineLayer extends CanvasLayer { const textColor = refLine.textColor || '#000'; const fontSize = refLine.fontSize || '10px sans-serif'; const textOffsetX = 10; + ctx.save(); ctx.strokeStyle = textColor; ctx.font = fontSize; ctx.textAlign = 'end'; ctx.textBaseline = 'bottom'; - ctx.fillText(refLine.text, x - textOffsetX, y); + ctx.fillText(refLine.text ?? '', x - textOffsetX, y); ctx.restore(); } diff --git a/src/layers/SchematicLayer.ts b/src/layers/SchematicLayer.ts index ea991fcb..62b0e48e 100644 --- a/src/layers/SchematicLayer.ts +++ b/src/layers/SchematicLayer.ts @@ -153,13 +153,13 @@ export class SchematicLayer extends PixiLayer { perforationLayerId: true, }; - private cementTextureCache: Texture; - private cementSqueezeTextureCache: Texture; - private cementPlugTextureCache: Texture; - private holeTextureCache: Texture; - private screenTextureCache: Texture; - private tubingTextureCache: Texture; - private textureSymbolCacheArray: { [key: string]: Texture }; + private cementTextureCache: Texture | null = null; + private cementSqueezeTextureCache: Texture | null = null; + private cementPlugTextureCache: Texture | null = null; + private holeTextureCache: Texture | null = null; + private screenTextureCache: Texture | null = null; + private tubingTextureCache: Texture | null = null; + private textureSymbolCacheArray: { [key: string]: Texture } | null = null; protected scalingFactors: ScalingFactors = { height: 600, @@ -178,14 +178,12 @@ export class SchematicLayer extends PixiLayer { public override onUnmount(event?: OnUnmountEvent): void { super.onUnmount(event); - this.scalingFactors = null; this.cementTextureCache = null; this.cementSqueezeTextureCache = null; this.holeTextureCache = null; this.screenTextureCache = null; this.tubingTextureCache = null; this.textureSymbolCacheArray = null; - this.internalLayerVisibility = null; } public override onUpdate(event: OnUpdateEvent): void { @@ -220,8 +218,10 @@ export class SchematicLayer extends PixiLayer { } const { internalLayerOptions } = this.options as SchematicLayerOptions; + const entries = internalLayerOptions ? Object.entries(internalLayerOptions) : []; + const entryFound = entries.find(([_key, id]: [string, string]) => id === layerId); + const keyFound = entryFound?.[0]; - const [keyFound] = Object.entries(internalLayerOptions).find(([_key, id]: [string, string]) => id === layerId); if (keyFound) { this.internalLayerVisibility[keyFound as keyof InternalLayerVisibility] = isVisible; this.clearLayer(); @@ -232,7 +232,7 @@ export class SchematicLayer extends PixiLayer { public override getInternalLayerIds(): string[] { const { internalLayerOptions } = this.options as SchematicLayerOptions; - return Object.values(internalLayerOptions); + return internalLayerOptions ? Object.values(internalLayerOptions) : []; } /** @@ -240,18 +240,18 @@ export class SchematicLayer extends PixiLayer { * TODO consider to move this into ZoomPanHandler */ protected yRatio(): number { - const domain = this.scalingFactors.yScale.domain(); + const domain = this.scalingFactors.yScale.domain() as [number, number]; const ySpan = domain[1] - domain[0]; const baseYSpan = ySpan * this.scalingFactors.zFactor; - const baseDomain = [domain[0], domain[0] + baseYSpan]; + const baseDomain: [number, number] = [domain[0], domain[0] + baseYSpan]; return Math.abs(this.scalingFactors.height / (baseDomain[1] - baseDomain[0])); } protected getZFactorScaledPathForPoints = (start: number, end: number): Point[] => { const y = (y: number): number => y * this.scalingFactors.zFactor; - const path = this.referenceSystem.getCurtainPath(start, end, true); - return path.map((p) => new Point(p.point[0], y(p.point[1]))); + const path = this.referenceSystem?.getCurtainPath(start, end, true) ?? []; + return path.map((p) => new Point(p.point[0], y(p.point[1]!))); }; protected drawBigPolygon = (coords: IPoint[], color = 0x000000) => { @@ -265,13 +265,11 @@ export class SchematicLayer extends PixiLayer { protected drawRope(path: Point[], texture: Texture, tint?: number): void { if (path.length === 0) { - return null; + return undefined; } const rope: SimpleRope = new SimpleRope(texture, path, 1); - rope.tint = tint || rope.tint; - this.addChild(rope); } @@ -294,8 +292,8 @@ export class SchematicLayer extends PixiLayer { ): void { const leftPathReverse = leftPath.map((d) => d.clone()).reverse(); - const startPointRight = rightPath[0]; - const startPointLeft = leftPathReverse[0]; + const startPointRight = rightPath[0]!; + const startPointLeft = leftPathReverse[0]!; const line = new Graphics(); line.lineStyle(lineWidth, lineColor, undefined, lineAlignment); @@ -333,7 +331,7 @@ export class SchematicLayer extends PixiLayer { const graphics = new Graphics(); graphics.lineStyle(lineWidth, convertColor(lineColor), undefined, solidAlignment); - const startPointLinePath = linePath[0]; + const startPointLinePath = linePath[0]!; graphics.moveTo(startPointLinePath.x, startPointLinePath.y); linePath.forEach((p: Point) => graphics.lineTo(p.x, p.y)); @@ -344,7 +342,7 @@ export class SchematicLayer extends PixiLayer { alignment: dashedAlignment, }); - const startPointDashedPath = dashedPath[0]; + const startPointDashedPath = dashedPath[0]!; dashedLine.moveTo(startPointDashedPath.x, startPointDashedPath.y); dashedPath.forEach((currentPoint: Point) => { dashedLine.lineTo(currentPoint.x, currentPoint.y); @@ -360,13 +358,14 @@ export class SchematicLayer extends PixiLayer { return; } - const { exaggerationFactor } = this.options as SchematicLayerOptions; + const { exaggerationFactor = 1 } = this.options as SchematicLayerOptions; const { holeSizes, casings, cements, completion, symbols, pAndA, perforations } = this.data; this.updateSymbolCache(symbols); holeSizes.sort((a: HoleSize, b: HoleSize) => b.diameter - a.diameter); - const maxHoleDiameter = holeSizes.length > 0 ? max(holeSizes, (d) => d.diameter) * exaggerationFactor : EXAGGERATED_DIAMETER * exaggerationFactor; + const maxHoleDiameter = + holeSizes.length > 0 ? (max(holeSizes, (d) => d.diameter) ?? 0) * exaggerationFactor : EXAGGERATED_DIAMETER * exaggerationFactor; if (this.internalLayerVisibility.holeLayerId) { holeSizes.forEach((hole: HoleSize) => this.drawHoleSize(maxHoleDiameter, hole)); } @@ -407,12 +406,14 @@ export class SchematicLayer extends PixiLayer { }, (cementRO: CementRenderObject) => { if (this.internalLayerVisibility.cementLayerId) { - this.drawComplexRope(cementRO.segments, this.getCementTexture()); + const texture = this.getCementTexture(); + texture && this.drawComplexRope(cementRO.segments, texture); } }, (cementSqueezesRO: CementSqueezeRenderObject) => { if (this.internalLayerVisibility.pAndALayerId) { - this.drawComplexRope(cementSqueezesRO.segments, this.getCementSqueezeTexture()); + const texture = this.getCementSqueezeTexture(); + texture && this.drawComplexRope(cementSqueezesRO.segments, texture); } }, ), @@ -440,15 +441,15 @@ export class SchematicLayer extends PixiLayer { if (!dict[ps.diameter]) { dict[ps.diameter] = []; } - dict[ps.diameter] = [...dict[ps.diameter], ps]; + dict[ps.diameter] = [...(dict[ps.diameter] ?? []), ps]; return dict; }, {}, ); Object.values(perfShapesByDiameter).forEach((perfShapesWithSameDiameter) => { - const texture = createPerforationPackingTexture(perforation, perfShapesWithSameDiameter[0], perforationOptions); + const texture = createPerforationPackingTexture(perforation, perfShapesWithSameDiameter[0]!, perforationOptions!); const rope = this.drawComplexRope(perfShapesWithSameDiameter, texture); - this.perforationRopeAndTextureReferences.push({ rope, texture }); + rope && this.perforationRopeAndTextureReferences.push({ rope, texture }); }); }); @@ -460,16 +461,16 @@ export class SchematicLayer extends PixiLayer { if (!dict[ps.diameter]) { dict[ps.diameter] = []; } - dict[ps.diameter] = [...dict[ps.diameter], ps]; + dict[ps.diameter] = [...(dict[ps.diameter] ?? []), ps]; return dict; }, {}, ); Object.values(perfShapesByDiameter).forEach((perfShapesWithSameDiameter) => { perfShapesWithSameDiameter.forEach((perfShape) => { - const texture = createPerforationSpikeTexture(perforation, perforations, perfShape, perforationOptions); + const texture = createPerforationSpikeTexture(perforation, perforations, perfShape, perforationOptions!); const rope = this.drawComplexRope([perfShape], texture); - this.perforationRopeAndTextureReferences.push({ rope, texture }); + rope && this.perforationRopeAndTextureReferences.push({ rope, texture }); }); }); }); @@ -482,16 +483,16 @@ export class SchematicLayer extends PixiLayer { if (!dict[ps.diameter]) { dict[ps.diameter] = []; } - dict[ps.diameter] = [...dict[ps.diameter], ps]; + dict[ps.diameter] = [...(dict[ps.diameter] ?? []), ps]; return dict; }, {}, ); Object.values(perfShapesByDiameter).forEach((perfShapesWithSameDiameter) => { perfShapesWithSameDiameter.forEach((perfShape) => { - const texture = createPerforationFracLineTexture(perforation, perfShape, perforationOptions); + const texture = createPerforationFracLineTexture(perforation, perfShape, perforationOptions!); const rope = this.drawComplexRope([perfShape], texture); - this.perforationRopeAndTextureReferences.push({ rope, texture }); + rope && this.perforationRopeAndTextureReferences.push({ rope, texture }); }); }); }); @@ -533,14 +534,14 @@ export class SchematicLayer extends PixiLayer { const existingKeys = Object.keys(this.textureSymbolCacheArray); Object.entries(symbols).forEach(([key, symbol]: [string, string]) => { - if (!existingKeys.includes(key)) { + if (!existingKeys.includes(key) && this.textureSymbolCacheArray) { this.textureSymbolCacheArray[key] = Texture.from(symbol); } }); } private drawCementPlug(cementPlug: CementPlug, casings: Casing[], completion: Completion[], holes: HoleSize[]) { - const { exaggerationFactor, cementPlugOptions } = this.options as SchematicLayerOptions; + const { exaggerationFactor = 1, cementPlugOptions } = this.options as SchematicLayerOptions; const cementPlugSegments = createComplexRopeSegmentsForCementPlug( cementPlug, @@ -550,7 +551,7 @@ export class SchematicLayer extends PixiLayer { exaggerationFactor, this.getZFactorScaledPathForPoints, ); - this.drawComplexRope(cementPlugSegments, this.getCementPlugTexture(cementPlugOptions)); + cementPlugOptions && this.drawComplexRope(cementPlugSegments, this.getCementPlugTexture(cementPlugOptions)); const { rightPath, leftPath } = cementPlugSegments.reduce<{ rightPath: Point[]; leftPath: Point[] }>( (acc, current) => { @@ -568,7 +569,7 @@ export class SchematicLayer extends PixiLayer { } private createCasingRenderObject(casing: Casing): CasingRenderObject { - const { exaggerationFactor } = this.options as SchematicLayerOptions; + const { exaggerationFactor = 1 } = this.options as SchematicLayerOptions; return prepareCasingRenderObject(exaggerationFactor, casing, this.getZFactorScaledPathForPoints); } @@ -580,7 +581,7 @@ export class SchematicLayer extends PixiLayer { } private prepareSymbolRenderObject = (component: CompletionSymbol | PAndASymbol): SymbolRenderObject => { - const { exaggerationFactor } = this.options as SchematicLayerOptions; + const { exaggerationFactor = 1 } = this.options as SchematicLayerOptions; const exaggeratedDiameter = component.diameter * exaggerationFactor; @@ -596,12 +597,12 @@ export class SchematicLayer extends PixiLayer { private drawSymbolComponent = ({ pathPoints, referenceDiameter, symbolKey }: SymbolRenderObject): void => { const texture = this.getSymbolTexture(symbolKey, referenceDiameter); // The rope renders fine in CANVAS/fallback mode - this.drawSVGRope(pathPoints, texture); + this.drawSVGRope(pathPoints, texture!); }; private drawSVGRope(path: Point[], texture: Texture): void { if (path.length === 0) { - return null; + return undefined; } const rope: UniformTextureStretchRope = new UniformTextureStretchRope(texture, path); @@ -609,8 +610,9 @@ export class SchematicLayer extends PixiLayer { this.addChild(rope); } - private getSymbolTexture(symbolKey: string, diameter: number): Texture { - return new Texture(this.textureSymbolCacheArray[symbolKey].baseTexture, null, new Rectangle(0, 0, 0, diameter), null, groupD8.MAIN_DIAGONAL); + private getSymbolTexture(symbolKey: string, diameter: number): Texture | undefined { + const baseTexture = this.textureSymbolCacheArray?.[symbolKey]?.baseTexture; + return baseTexture ? new Texture(baseTexture, undefined, new Rectangle(0, 0, 0, diameter), undefined, groupD8.MAIN_DIAGONAL) : undefined; } private drawHoleSize = (maxHoleDiameter: number, holeObject: HoleSize): void => { @@ -623,19 +625,19 @@ export class SchematicLayer extends PixiLayer { return; } - const { exaggerationFactor, holeOptions } = this.options as SchematicLayerOptions; + const { exaggerationFactor = 1, holeOptions } = this.options as SchematicLayerOptions; const exaggeratedDiameter = holeObject.diameter * exaggerationFactor; const { rightPath, leftPath } = createTubularRenderingObject(exaggeratedDiameter / 2, pathPoints); - const texture = this.getHoleTexture(holeOptions, exaggeratedDiameter, maxHoleDiameter); + const texture = this.getHoleTexture(holeOptions!, exaggeratedDiameter, maxHoleDiameter); this.drawHoleRope(pathPoints, texture, maxHoleDiameter); - this.drawOutline(leftPath, rightPath, convertColor(holeOptions.lineColor), HOLE_OUTLINE * exaggerationFactor, 'TopAndBottom', 0); + this.drawOutline(leftPath, rightPath, convertColor(holeOptions!.lineColor), HOLE_OUTLINE * exaggerationFactor, 'TopAndBottom', 0); }; private drawHoleRope(path: Point[], texture: Texture, maxHoleDiameter: number): void { if (path.length === 0) { - return null; + return undefined; } const rope: SimpleRope = new SimpleRope(texture, path, maxHoleDiameter / DEFAULT_TEXTURE_SIZE); @@ -694,7 +696,7 @@ export class SchematicLayer extends PixiLayer { casingRenderObject.zIndex = zIndex++; return { - result: [...acc.result, foundCementShape, casingRenderObject, ...foundCementSqueezes], + result: [...acc.result, foundCementShape!, casingRenderObject, ...foundCementSqueezes], remainingCement: acc.remainingCement.filter((c) => c !== foundCementShape), remainingCementSqueezes: acc.remainingCementSqueezes.filter((squeeze) => !foundCementSqueezes.includes(squeeze)), }; @@ -702,7 +704,7 @@ export class SchematicLayer extends PixiLayer { { result: [], remainingCement: cementRenderObject, remainingCementSqueezes: cementSqueezes }, ); - return result.filter((item) => item !== undefined).sort((a, b) => a.zIndex - b.zIndex); + return result.filter((item): item is InterlacedRenderObjects => item != null).sort((a, b) => a.zIndex! - b.zIndex!); } /** @@ -714,9 +716,9 @@ export class SchematicLayer extends PixiLayer { * @param getExaggerationFactor * @returns */ - private drawComplexRope(intervals: ComplexRopeSegment[], texture: Texture): ComplexRope { + private drawComplexRope(intervals: ComplexRopeSegment[], texture: Texture): ComplexRope | undefined { if (intervals.length === 0) { - return null; + return undefined; } const rope = new ComplexRope(texture, intervals); @@ -741,8 +743,8 @@ export class SchematicLayer extends PixiLayer { private drawCasing = (casingRenderObject: CasingRenderObject): void => { const { casingOptions } = this.options as SchematicLayerOptions; - const casingSolidColorNumber = convertColor(casingOptions.solidColor); - const casingLineColorNumber = convertColor(casingOptions.lineColor); + const casingSolidColorNumber = convertColor(casingOptions!.solidColor); + const casingLineColorNumber = convertColor(casingOptions!.lineColor); casingRenderObject.sections.forEach((section, index, list) => { const outlineClosureType = SchematicLayer.getOutlineClosureType(index, list.length - 1); @@ -751,7 +753,7 @@ export class SchematicLayer extends PixiLayer { this.drawRope(section.pathPoints, texture, casingSolidColorNumber); if (section.kind === 'casing-window') { - this.drawCasingWindowOutline(section.leftPath, section.rightPath, casingOptions, casingRenderObject.casingWallWidth); + this.drawCasingWindowOutline(section.leftPath, section.rightPath, casingOptions!, casingRenderObject.casingWallWidth); } else { this.drawOutline(section.leftPath, section.rightPath, casingLineColorNumber, casingRenderObject.casingWallWidth, outlineClosureType); } @@ -760,13 +762,13 @@ export class SchematicLayer extends PixiLayer { private createCasingTexture(diameter: number): Texture { const textureWidthPO2 = 16; - return new Texture(Texture.WHITE.baseTexture, null, new Rectangle(0, 0, textureWidthPO2, diameter)); + return new Texture(Texture.WHITE.baseTexture, undefined, new Rectangle(0, 0, textureWidthPO2, diameter)); } private drawShoe(casingEnd: number, casingRadius: number): void { - const { exaggerationFactor, casingOptions } = this.options as SchematicLayerOptions; - const shoeWidth = casingOptions.shoeSize.width * exaggerationFactor; - const shoeLength = casingOptions.shoeSize.length * exaggerationFactor; + const { exaggerationFactor = 1, casingOptions } = this.options as SchematicLayerOptions; + const shoeWidth = casingOptions!.shoeSize.width * exaggerationFactor; + const shoeLength = casingOptions!.shoeSize.length * exaggerationFactor; const shoeCoords = this.generateShoe(casingEnd, casingRadius, shoeLength, shoeWidth); const shoeCoords2 = this.generateShoe(casingEnd, casingRadius, shoeLength, -shoeWidth); @@ -783,8 +785,8 @@ export class SchematicLayer extends PixiLayer { const normal = createNormals(points); const shoeEdge: Point[] = offsetPoints(points, normal, casingRadius * (width < 0 ? -1 : 1)); - const shoeTipPoint = points[points.length - 1]; - const shoeTipNormal = normal[normal.length - 1]; + const shoeTipPoint = points[points.length - 1]!; + const shoeTipNormal = normal[normal.length - 1]!; const shoeTip: Point = offsetPoint(shoeTipPoint, shoeTipNormal, width + casingRadius * (width < 0 ? -1 : 1)); return [...shoeEdge, shoeTip]; @@ -796,50 +798,51 @@ export class SchematicLayer extends PixiLayer { completion: Completion[], holes: HoleSize[], ): ComplexRopeSegment[] => { - const { exaggerationFactor } = this.options as SchematicLayerOptions; + const { exaggerationFactor = 1 } = this.options as SchematicLayerOptions; return createComplexRopeSegmentsForCementSqueeze(squeeze, casings, completion, holes, exaggerationFactor, this.getZFactorScaledPathForPoints); }; - private getCementTexture(): Texture { + private getCementTexture(): Texture | null { if (!this.cementTextureCache) { const { cementOptions } = this.options as SchematicLayerOptions; - this.cementTextureCache = createCementTexture(cementOptions); + cementOptions && (this.cementTextureCache = createCementTexture(cementOptions)); } return this.cementTextureCache; } private createPerforationShape = (perforation: Perforation, casings: Casing[], holes: HoleSize[]): PerforationShape[] => { - const { exaggerationFactor } = this.options as SchematicLayerOptions; + const { exaggerationFactor = 1 } = this.options as SchematicLayerOptions; return createComplexRopeSegmentsForPerforation(perforation, casings, holes, exaggerationFactor, this.getZFactorScaledPathForPoints); }; - private getCementSqueezeTexture(): Texture { + private getCementSqueezeTexture(): Texture | null { if (!this.cementSqueezeTextureCache) { const { cementSqueezeOptions } = this.options as SchematicLayerOptions; - this.cementSqueezeTextureCache = createCementSqueezeTexture(cementSqueezeOptions); + cementSqueezeOptions && (this.cementSqueezeTextureCache = createCementSqueezeTexture(cementSqueezeOptions)); } return this.cementSqueezeTextureCache; } private drawScreen({ start, end, diameter }: Screen): void { - const { exaggerationFactor, screenOptions } = this.options as SchematicLayerOptions; + const { exaggerationFactor = 1, screenOptions } = this.options as SchematicLayerOptions; const exaggeratedDiameter = exaggerationFactor * diameter; const pathPoints = this.getZFactorScaledPathForPoints(start, end); const { leftPath, rightPath } = createTubularRenderingObject(exaggeratedDiameter / 2, pathPoints); const texture = this.getScreenTexture(); - - this.drawCompletionRope(pathPoints, texture, exaggeratedDiameter); - this.drawOutline(leftPath, rightPath, convertColor(screenOptions.lineColor), SCREEN_OUTLINE * exaggerationFactor, 'TopAndBottom'); + if (texture) { + this.drawCompletionRope(pathPoints, texture, exaggeratedDiameter); + this.drawOutline(leftPath, rightPath, convertColor(screenOptions!.lineColor), SCREEN_OUTLINE * exaggerationFactor, 'TopAndBottom'); + } } private drawTubing({ diameter, start, end }: Tubing): void { - const { exaggerationFactor, tubingOptions } = this.options as SchematicLayerOptions; + const { exaggerationFactor = 1, tubingOptions } = this.options as SchematicLayerOptions; const exaggeratedDiameter = exaggerationFactor * diameter; const pathPoints = this.getZFactorScaledPathForPoints(start, end); - const texture = this.getTubingTexture(tubingOptions); + const texture = this.getTubingTexture(tubingOptions!); this.drawCompletionRope(pathPoints, texture, exaggeratedDiameter); } @@ -851,10 +854,10 @@ export class SchematicLayer extends PixiLayer { return this.tubingTextureCache; } - private getScreenTexture(): Texture { + private getScreenTexture(): Texture | null { if (!this.screenTextureCache) { const { screenOptions } = this.options as SchematicLayerOptions; - this.screenTextureCache = createScreenTexture(screenOptions); + screenOptions && (this.screenTextureCache = createScreenTexture(screenOptions)); } return this.screenTextureCache; } diff --git a/src/layers/WellborePathLayer.ts b/src/layers/WellborePathLayer.ts index e2d8010c..4446420b 100644 --- a/src/layers/WellborePathLayer.ts +++ b/src/layers/WellborePathLayer.ts @@ -29,7 +29,7 @@ export interface WellborepathLayerOptions extends } export class WellborepathLayer extends SVGLayer { - rescaleEvent: OnRescaleEvent; + rescaleEvent: OnRescaleEvent | undefined; constructor(id?: string, options?: WellborepathLayerOptions) { super(id, options); @@ -78,52 +78,54 @@ export class WellborepathLayer extends SVGLayer } private renderWellborePath(data: [number, number][]): string { - const { xScale, yScale } = this.rescaleEvent; - const transformedData: [number, number][] = data.map((d) => [xScale(d[0]), yScale(d[1])]); + if (this.rescaleEvent != null) { + const { xScale, yScale } = this.rescaleEvent; + const transformedData: [number, number][] = data.map((d) => [xScale(d[0]), yScale(d[1])]); - // TODO: Might be a good idea to move something like this to a shared function in a base class - let curveFactory; - const { curveType, tension } = this.options as WellborepathLayerOptions; - switch (curveType) { - default: - case 'curveCatmullRom': - curveFactory = curveCatmullRom.alpha(tension || CURVE_CATMULL_ROM_ALPHA); - break; - case 'curveLinear': - curveFactory = curveLinear; - break; - case 'curveBasis': - curveFactory = curveBasis; - break; - case 'curveBasisClosed': - curveFactory = curveBasisClosed; - break; - case 'curveBundle': - curveFactory = curveBundle.beta(tension || CURVE_BUNDLE_BETA); - break; - case 'curveCardinal': - curveFactory = curveCardinal.tension(tension || CURVE_CARDINAL_TENSION); - break; - case 'curveMonotoneX': - curveFactory = curveMonotoneX; - break; - case 'curveMonotoneY': - curveFactory = curveMonotoneY; - break; - case 'curveNatural': - curveFactory = curveNatural; - break; - case 'curveStep': - curveFactory = curveStep; - break; - case 'curveStepAfter': - curveFactory = curveStepAfter; - break; - case 'curveStepBefore': - curveFactory = curveStepBefore; - break; + // TODO: Might be a good idea to move something like this to a shared function in a base class + let curveFactory; + const { curveType, tension } = this.options as WellborepathLayerOptions; + switch (curveType) { + default: + case 'curveCatmullRom': + curveFactory = curveCatmullRom.alpha(tension || CURVE_CATMULL_ROM_ALPHA); + break; + case 'curveLinear': + curveFactory = curveLinear; + break; + case 'curveBasis': + curveFactory = curveBasis; + break; + case 'curveBasisClosed': + curveFactory = curveBasisClosed; + break; + case 'curveBundle': + curveFactory = curveBundle.beta(tension || CURVE_BUNDLE_BETA); + break; + case 'curveCardinal': + curveFactory = curveCardinal.tension(tension || CURVE_CARDINAL_TENSION); + break; + case 'curveMonotoneX': + curveFactory = curveMonotoneX; + break; + case 'curveMonotoneY': + curveFactory = curveMonotoneY; + break; + case 'curveNatural': + curveFactory = curveNatural; + break; + case 'curveStep': + curveFactory = curveStep; + break; + case 'curveStepAfter': + curveFactory = curveStepAfter; + break; + case 'curveStepBefore': + curveFactory = curveStepBefore; + break; + } + return line().curve(curveFactory)(transformedData) ?? ''; } - - return line().curve(curveFactory)(transformedData); + return ''; } } diff --git a/src/layers/base/CanvasLayer.ts b/src/layers/base/CanvasLayer.ts index 602b2f8c..1d801e59 100644 --- a/src/layers/base/CanvasLayer.ts +++ b/src/layers/base/CanvasLayer.ts @@ -3,9 +3,9 @@ import { OnMountEvent, OnUpdateEvent, OnResizeEvent, OnRescaleEvent } from '../. import { DEFAULT_LAYER_HEIGHT, DEFAULT_LAYER_WIDTH } from '../../constants'; export abstract class CanvasLayer extends Layer { - ctx: CanvasRenderingContext2D; - elm: HTMLElement; - canvas: HTMLCanvasElement; + ctx: CanvasRenderingContext2D | undefined; + elm: HTMLElement | undefined; + canvas: HTMLCanvasElement | undefined; onOpacityChanged(_opacity: number): void { if (this.canvas) { @@ -36,7 +36,7 @@ export abstract class CanvasLayer extends Layer { const isVisible = visible || this.isVisible; const visibility = isVisible ? 'visible' : 'hidden'; const interactive = this.interactive ? 'auto' : 'none'; - this.canvas.setAttribute( + this.canvas?.setAttribute( 'style', `position:absolute;pointer-events:${interactive};z-index:${this.order};opacity:${this.opacity};visibility:${visibility}`, ); @@ -45,8 +45,8 @@ export abstract class CanvasLayer extends Layer { override onMount(event: OnMountEvent): void { super.onMount(event); const { elm } = event; - const width = event.width || parseInt(elm.getAttribute('width'), 10) || DEFAULT_LAYER_WIDTH; - const height = event.height || parseInt(elm.getAttribute('height'), 10) || DEFAULT_LAYER_HEIGHT; + const width = event.width || parseInt(elm?.getAttribute('width') ?? '', 10) || DEFAULT_LAYER_WIDTH; + const height = event.height || parseInt(elm?.getAttribute('height') ?? '', 10) || DEFAULT_LAYER_HEIGHT; this.elm = elm; let canvas: HTMLCanvasElement; if (!this.canvas) { @@ -59,21 +59,21 @@ export abstract class CanvasLayer extends Layer { this.canvas.setAttribute('height', `${height}px`); this.canvas.setAttribute('class', 'canvas-layer'); this.updateStyle(); - this.ctx = this.canvas.getContext('2d'); + this.ctx = this.canvas.getContext('2d') ?? undefined; } override onUnmount(): void { super.onUnmount(); - this.canvas.remove(); - this.canvas = null; + this.canvas?.remove(); + this.canvas = undefined; } override onResize(event: OnResizeEvent): void { const { ctx } = this; const { width, height } = event; - ctx.canvas.setAttribute('width', `${width}px`); - ctx.canvas.setAttribute('height', `${height}px`); + ctx?.canvas.setAttribute('width', `${width}px`); + ctx?.canvas.setAttribute('height', `${height}px`); } override onUpdate(event: OnUpdateEvent): void { @@ -81,22 +81,22 @@ export abstract class CanvasLayer extends Layer { } resetTransform(): void { - this.ctx.resetTransform(); + this.ctx?.resetTransform(); } setTransform(event: OnRescaleEvent): void { this.resetTransform(); const flippedX = event.xBounds[0] > event.xBounds[1]; const flippedY = event.yBounds[0] > event.yBounds[1]; - this.ctx.translate(event.xScale(0), event.yScale(0)); - this.ctx.scale(event.xRatio * (flippedX ? -1 : 1), event.yRatio * (flippedY ? -1 : 1)); + this.ctx?.translate(event.xScale(0), event.yScale(0)); + this.ctx?.scale(event.xRatio * (flippedX ? -1 : 1), event.yRatio * (flippedY ? -1 : 1)); } clearCanvas(): void { const { ctx, canvas } = this; - ctx.save(); - ctx.resetTransform(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.restore(); + ctx?.save(); + ctx?.resetTransform(); + ctx?.clearRect(0, 0, canvas?.width ?? 0, canvas?.height ?? 0); + ctx?.restore(); } } diff --git a/src/layers/base/HTMLLayer.ts b/src/layers/base/HTMLLayer.ts index 67f0c940..3a96f665 100644 --- a/src/layers/base/HTMLLayer.ts +++ b/src/layers/base/HTMLLayer.ts @@ -4,13 +4,13 @@ import { OnMountEvent, OnResizeEvent } from '../../interfaces'; import { DEFAULT_LAYER_HEIGHT, DEFAULT_LAYER_WIDTH } from '../../constants'; export abstract class HTMLLayer extends Layer { - elm: Selection; + elm: Selection | undefined; override onMount(event: OnMountEvent): void { super.onMount(event); const { elm } = event; - const width = event.width || parseInt(elm.getAttribute('width'), 10) || DEFAULT_LAYER_WIDTH; - const height = event.height || parseInt(elm.getAttribute('height'), 10) || DEFAULT_LAYER_HEIGHT; + const width = event.width || parseInt(elm?.getAttribute('width') ?? '', 10) || DEFAULT_LAYER_WIDTH; + const height = event.height || parseInt(elm?.getAttribute('height') ?? '', 10) || DEFAULT_LAYER_HEIGHT; if (!this.elm) { this.elm = select(elm).append('div'); @@ -30,8 +30,8 @@ export abstract class HTMLLayer extends Layer { override onUnmount(): void { super.onUnmount(); - this.elm.remove(); - this.elm = null; + this.elm?.remove(); + this.elm = undefined; } override onResize(event: OnResizeEvent): void { diff --git a/src/layers/base/Layer.ts b/src/layers/base/Layer.ts index b300a8cd..90056fbb 100644 --- a/src/layers/base/Layer.ts +++ b/src/layers/base/Layer.ts @@ -26,12 +26,12 @@ export abstract class Layer { private _order: number; protected _options: LayerOptions; private loading: boolean; - private _element?: HTMLElement; + private _element: HTMLElement | undefined; private _opacity: number; - private _referenceSystem?: IntersectionReferenceSystem = null; - private _data?: T; + private _referenceSystem: IntersectionReferenceSystem | undefined; + private _data: T | undefined; private _visible: boolean; - private _interactive: boolean = false; + private _interactive = false; constructor(id?: string, options?: LayerOptions) { this._id = id || `layer-${Math.floor(Math.random() * 1000)}`; @@ -41,7 +41,7 @@ export abstract class Layer { ...opts, }; this.loading = false; - this._element = null; + this._element = undefined; this._opacity = opts.layerOpacity || 1; this._visible = true; this._interactive = opts.interactive || false; @@ -49,7 +49,7 @@ export abstract class Layer { if (options && options.data) { this.setData(options.data); } - this._referenceSystem = options && options.referenceSystem; + this._referenceSystem = options?.referenceSystem; this.onMount = this.onMount.bind(this); this.onUnmount = this.onUnmount.bind(this); @@ -65,7 +65,7 @@ export abstract class Layer { return this._id; } - get element(): HTMLElement { + get element(): HTMLElement | undefined { return this._element; } @@ -112,19 +112,19 @@ export abstract class Layer { return this._interactive; } - get referenceSystem(): IntersectionReferenceSystem { + get referenceSystem(): IntersectionReferenceSystem | undefined { return this._referenceSystem; } - set referenceSystem(referenceSystem: IntersectionReferenceSystem) { + set referenceSystem(referenceSystem: IntersectionReferenceSystem | undefined) { this._referenceSystem = referenceSystem; } - get data(): T { + get data(): T | undefined { return this.getData(); } - set data(data: T) { + set data(data: T | undefined) { this.setData(data); } @@ -132,14 +132,14 @@ export abstract class Layer { return this._visible; } - getData(): T { + getData(): T | undefined { return this._data; } - setData(data: T): void { + setData(data: T | undefined): void { this._data = data; // should not be called when there is no visual element to work with - if (this.element) { + if (this.element && data != null) { this.onUpdate({ data }); } } @@ -148,10 +148,10 @@ export abstract class Layer { * Clears data and (optionally) the reference system * @param includeReferenceSystem - (optional) if true also removes reference system, default is true */ - clearData(includeReferenceSystem: boolean = true): void { - this._data = null; + clearData(includeReferenceSystem = true): void { + this._data = undefined; if (includeReferenceSystem) { - this.referenceSystem = null; + this.referenceSystem = undefined; } this.onUpdate({}); } @@ -168,7 +168,7 @@ export abstract class Layer { } onUnmount(event?: OnUnmountEvent): void { - if (this._options.onUnmount) { + if (this._options.onUnmount && event != null) { this._options.onUnmount(event, this); } } diff --git a/src/layers/base/PixiLayer.ts b/src/layers/base/PixiLayer.ts index ab69404a..a9fe5971 100644 --- a/src/layers/base/PixiLayer.ts +++ b/src/layers/base/PixiLayer.ts @@ -9,9 +9,9 @@ import { DEFAULT_LAYER_HEIGHT, DEFAULT_LAYER_WIDTH } from '../../constants'; // The plugin we are trying to avoid: // https://github.com/pixijs/pixijs/blob/dev/packages/ticker/src/TickerPlugin.ts export class PixiRenderApplication { - stage: Container; + stage: Container | undefined; - renderer: IRenderer; + renderer: IRenderer | undefined; constructor(pixiRenderOptions?: IRendererOptionsAuto) { const options = { @@ -29,15 +29,15 @@ export class PixiRenderApplication { } destroy() { - this.stage.destroy({ + this.stage?.destroy({ children: true, texture: true, baseTexture: true, }); - this.stage = null; + this.stage = undefined; // Get renderType and clContext before we destroy the renderer - const renderType = this.renderer.type; + const renderType = this.renderer?.type; const glContext = this.renderer instanceof Renderer ? this.renderer?.gl : undefined; /** @@ -50,21 +50,23 @@ export class PixiRenderApplication { glContext?.getExtension('WEBGL_lose_context')?.loseContext(); } - this.renderer.destroy(true); - this.renderer = null; + this.renderer?.destroy(true); + this.renderer = undefined; } get view() { - return this.renderer.view; + return this.renderer?.view; } render() { - this.renderer.render(this.stage); + if (this.stage != null) { + this.renderer?.render(this.stage); + } } } export abstract class PixiLayer extends Layer { - private pixiViewContainer: HTMLElement; + private pixiViewContainer: HTMLElement | undefined; private ctx: PixiRenderApplication; private container: Container; @@ -74,7 +76,7 @@ export abstract class PixiLayer extends Layer { this.ctx = ctx; this.container = new Container(); - this.ctx.stage.addChild(this.container); + this.ctx.stage?.addChild(this.container); } render(): void { @@ -95,16 +97,18 @@ export abstract class PixiLayer extends Layer { override onMount(event: OnMountEvent) { super.onMount(event); - this.pixiViewContainer = this.element.querySelector('#webgl-layer'); + this.pixiViewContainer = this.element?.querySelector('#webgl-layer') ?? undefined; if (!this.pixiViewContainer) { this.pixiViewContainer = document.createElement('div'); this.pixiViewContainer.setAttribute('id', `${this.id}`); this.pixiViewContainer.setAttribute('class', 'webgl-layer'); - this.pixiViewContainer.appendChild(this.ctx.view); + if (this.ctx.view != null) { + this.pixiViewContainer.appendChild(this.ctx.view); + } - this.element.appendChild(this.pixiViewContainer); + this.element?.appendChild(this.pixiViewContainer); this.updateStyle(); } @@ -114,15 +118,15 @@ export abstract class PixiLayer extends Layer { super.onUnmount(event); this.clearLayer(); - this.ctx.stage.removeChild(this.container); + this.ctx.stage?.removeChild(this.container); this.container.destroy(); - this.pixiViewContainer.remove(); + this.pixiViewContainer?.remove(); this.pixiViewContainer = undefined; } override onResize(event: OnResizeEvent): void { super.onResize(event); - this.ctx.renderer.resize(event.width, event.height); + this.ctx.renderer?.resize(event.width, event.height); } override onRescale(event: OnRescaleEvent): void { @@ -156,7 +160,7 @@ export abstract class PixiLayer extends Layer { .map((pair) => pair.join(':')) .join(';'); - this.pixiViewContainer.setAttribute('style', styles); + this.pixiViewContainer?.setAttribute('style', styles); } override setVisibility(visible: boolean, layerId?: string): void { @@ -184,7 +188,7 @@ export abstract class PixiLayer extends Layer { } } - renderType(): RENDERER_TYPE { - return this.ctx.renderer.type; + renderType(): RENDERER_TYPE | undefined { + return this.ctx.renderer?.type; } } diff --git a/src/layers/base/SVGLayer.ts b/src/layers/base/SVGLayer.ts index eb8425b2..b28fb453 100644 --- a/src/layers/base/SVGLayer.ts +++ b/src/layers/base/SVGLayer.ts @@ -4,13 +4,13 @@ import { OnMountEvent, OnResizeEvent } from '../../interfaces'; import { DEFAULT_LAYER_HEIGHT, DEFAULT_LAYER_WIDTH } from '../../constants'; export abstract class SVGLayer extends Layer { - elm: Selection; + elm: Selection | undefined; override onMount(event: OnMountEvent): void { super.onMount(event); const { elm } = event; - const width = event.width || parseInt(elm.getAttribute('width'), 10) || DEFAULT_LAYER_WIDTH; - const height = event.height || parseInt(elm.getAttribute('height'), 10) || DEFAULT_LAYER_HEIGHT; + const width = event.width || parseInt(elm.getAttribute('width') ?? '', 10) || DEFAULT_LAYER_WIDTH; + const height = event.height || parseInt(elm.getAttribute('height') ?? '', 10) || DEFAULT_LAYER_HEIGHT; if (!this.elm) { this.elm = select(elm).append('svg'); this.elm.attr('id', `${this.id}`); @@ -23,8 +23,8 @@ export abstract class SVGLayer extends Layer { override onUnmount(): void { super.onUnmount(); - this.elm.remove(); - this.elm = null; + this.elm?.remove(); + this.elm = undefined; } override onResize(event: OnResizeEvent): void { diff --git a/src/utils/arc-length.ts b/src/utils/arc-length.ts index 8dabc30e..2f22a06a 100644 --- a/src/utils/arc-length.ts +++ b/src/utils/arc-length.ts @@ -16,15 +16,8 @@ export class ArcLength { * @param {Number} minDepth Min recursive depth before accepting solution * @param {Number} maxDepth Max recursive depth */ - static bisect( - func: fx, - minLimit: number = 0, - maxLimit: number = 1, - tolerance: number = 0.005, - minDepth: number = 4, - maxDepth: number = 10, - ): number { - const calcRec = (a: number, b: number, aVal: number[], bVal: number[], span: number, tolerance: number, depth: number = 0): number => { + static bisect(func: fx, minLimit = 0, maxLimit = 1, tolerance = 0.005, minDepth = 4, maxDepth = 10): number { + const calcRec = (a: number, b: number, aVal: number[], bVal: number[], span: number, tolerance: number, depth = 0): number => { const mid = (a + b) / 2; const midVal = func(mid) as number[]; const spanA = Vector2.distance(aVal, midVal); @@ -51,7 +44,7 @@ export class ArcLength { * @param {Number} maxLimit Max limit * @param {Number} segments Number of segments */ - static trapezoid(func: fx, minLimit: number = 0, maxLimit: number = 1, segments: number = 1000): number { + static trapezoid(func: fx, minLimit = 0, maxLimit = 1, segments = 1000): number { let length = 0; let lastPos = func(minLimit) as number[]; const step = (maxLimit - minLimit) / (segments - 1); diff --git a/src/utils/binary-search.ts b/src/utils/binary-search.ts index c0f462c5..455fb786 100644 --- a/src/utils/binary-search.ts +++ b/src/utils/binary-search.ts @@ -11,10 +11,10 @@ export class BinarySearch { while (i > il && i < ih) { const v = values[i]; const v1 = values[i + 1]; - if (v <= searchValue && v1 >= searchValue) { + if (v != null && v1 != null && v <= searchValue && v1 >= searchValue) { return i; } - if (searchValue < v) { + if (v != null && searchValue < v) { ih = i; } else { il = i; diff --git a/src/utils/color.ts b/src/utils/color.ts index 8b725c42..76567eef 100644 --- a/src/utils/color.ts +++ b/src/utils/color.ts @@ -1,4 +1,4 @@ -import { color, Color } from 'd3-color'; +import { color } from 'd3-color'; const RADIX_SIXTEEN = 16; const HEX_STRING_LENGTH = 6; @@ -6,10 +6,14 @@ const HEX_STRING_LENGTH = 6; * Convert color string to number */ export function convertColor(colorStr: string): number { - const c: Color = color(colorStr); - const d: string = c.formatHex(); - const n: number = parseInt(d.replace('#', '0x')); - return n; + const c = color(colorStr); + if (c != null) { + const d: string = c?.formatHex(); + const n: number = parseInt(d.replace('#', '0x')); + return n; + } else { + throw Error(`Could not format string ${colorStr} to hex code.`); + } } export function colorToCSSColor(color: number | string): string { diff --git a/src/utils/root-finder.ts b/src/utils/root-finder.ts index 256920d3..298bb3bd 100644 --- a/src/utils/root-finder.ts +++ b/src/utils/root-finder.ts @@ -15,7 +15,7 @@ export class RootFinder { * @param {Number} minLimit Min limit of result * @param {Number} maxLimit Max limit of result */ - static newton(func: fx, precision: number = 0.01, maxIterations: number = 1000, start = 0.5, minLimit = 0, maxLimit = 1): number { + static newton(func: fx, precision = 0.01, maxIterations = 1000, start = 0.5, minLimit = 0, maxLimit = 1): number | undefined { const h = 0.0001; let t = start; for (let i = 0; i < maxIterations; i++) { @@ -26,7 +26,7 @@ export class RootFinder { const d = (func(t + h) - v) / h; t = t - v / d; } - return null; + return undefined; } /** @@ -38,7 +38,7 @@ export class RootFinder { * @param {Number} minLimit Min limit of result * @param {Number} maxLimit Max limit of result */ - static bisect(func: fx, precision: number = 0.01, maxIterations: number = 1000, start = 0.5, minLimit = 0, maxLimit = 1): number { + static bisect(func: fx, precision = 0.01, maxIterations = 1000, start = 0.5, minLimit = 0, maxLimit = 1): number { let tl = minLimit; let th = maxLimit; let t = start; @@ -68,7 +68,7 @@ export class RootFinder { * @param {Number} minLimit Min limit of result * @param {Number} maxLimit Max limit of result */ - static findRoot(func: fx, precision: number = 0.01, maxIterations: number = 1000, start = 0.5, minLimit = 0, maxLimit = 1): number { + static findRoot(func: fx, precision = 0.01, maxIterations = 1000, start = 0.5, minLimit = 0, maxLimit = 1): number { let t = RootFinder.newton(func, precision, maxIterations, start); if (t == null) { t = RootFinder.bisect(func, precision, maxIterations, start, minLimit, maxLimit); diff --git a/src/utils/text.ts b/src/utils/text.ts index 13c2b03a..5e6e1bb2 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -7,7 +7,7 @@ const DEFAULT_HORIZONTAL_PADDING = 4; const DEFAULT_VERTICAL_PADDING = 2; export function pixelsPerUnit(x: ScaleLinear): number { - const [min] = x.domain(); + const [min] = x.domain() as [number, number]; return Math.abs(x(min + 1)); } @@ -42,14 +42,14 @@ export function isOverlapping( return true; } -export function getOverlap(r1: BoundingBox, r2: BoundingBox): { dx: number; dy: number } { +export function getOverlap(r1: BoundingBox, r2: BoundingBox): { dx: number; dy: number } | undefined { const r1x2 = r1.x + r1.width; const r2x2 = r2.x + r2.width; const r1y2 = r1.y + r1.height; const r2y2 = r2.y + r2.height; if (r2.x > r1x2 || r2.y > r1y2 || r2x2 < r1.x || r2y2 < r1.y) { - return null; + return undefined; } const dx = Math.max(0, Math.min(r1.x + r1.width, r2.x + r2.width) - Math.max(r1.x, r2.x)); @@ -67,14 +67,14 @@ export function getOverlapOffset( r2: BoundingBox, horizontalPadding = DEFAULT_HORIZONTAL_PADDING, verticalPadding = DEFAULT_VERTICAL_PADDING, -): { dx: number; dy: number } { +): { dx: number; dy: number } | undefined { const r1x2 = r1.x + r1.width; const r2x2 = r2.x + r2.width; const r1y2 = r1.y + r1.height; const r2y2 = r2.y + r2.height; if (r2.x - horizontalPadding > r1x2 || r2.y - verticalPadding > r1y2 || r2x2 + horizontalPadding < r1.x || r2y2 + verticalPadding < r1.y) { - return null; + return undefined; } const dx = r1.x + r1.width - r2.x + horizontalPadding; diff --git a/src/utils/vectorUtils.ts b/src/utils/vectorUtils.ts index 65697717..742b14dc 100644 --- a/src/utils/vectorUtils.ts +++ b/src/utils/vectorUtils.ts @@ -4,9 +4,9 @@ import Vector2 from '@equinor/videx-vector2'; export const pointToVector = (p: IPoint): Vector2 => new Vector2(p.x, p.y); export const pointToArray = (p: IPoint): [number, number] => [p.x, p.y]; export const vectorToPoint = (v: Vector2): Point => new Point(v[0], v[1]); -export const vectorToArray = (v: Vector2): [number, number] => [v[0], v[1]]; +export const vectorToArray = (v: Vector2): [number, number] => [v[0] ?? 0, v[1] ?? 0]; export const arrayToPoint = (a: number[]): Point => new Point(a[0], a[1]); -export const arrayToVector = (a: number[]): Vector2 => new Vector2(a[0], a[1]); +export const arrayToVector = (a: number[]): Vector2 => new Vector2(a[0] ?? 0, a[1] ?? 0); export const calcDist = (prev: [number, number], point: [number, number]): number => { return arrayToVector(point).sub(prev).magnitude; @@ -35,9 +35,12 @@ export const createNormals = (points: IPoint[]): Vector2[] => { let n: Vector2; return points.map((_coord, i, list) => { - if (i < list.length - 1) { - const p = pointToVector(list[i]); - const q = pointToVector(list[i + 1]); + const curr = list[i]; + const next = list[i + 1]; + + if (i < list.length - 1 && curr != null && next != null) { + const p = pointToVector(curr); + const q = pointToVector(next); const np = q.sub(p); const rotate = np.rotate90(); n = rotate.normalized(); @@ -62,6 +65,10 @@ export const offsetPoints = (points: IPoint[], vectors: Vector2[], offset: numbe return points.map((point, index) => { const vector = vectors[index]; - return offsetPoint(point, vector, offset); + + if (vector != null) { + return offsetPoint(point, vector, offset); + } + throw new Error(`Trying to read index ${index} of point ${point}, but no such vector was found!`); }); }; diff --git a/src/vendor/pixi-dashed-line/index.ts b/src/vendor/pixi-dashed-line/index.ts index 36ee8716..98165779 100644 --- a/src/vendor/pixi-dashed-line/index.ts +++ b/src/vendor/pixi-dashed-line/index.ts @@ -1,3 +1,4 @@ +// @ts-nocheck // https://github.com/davidfig/pixi-dashed-line // // Copyright 2021 David Figatner @@ -153,8 +154,8 @@ export class DashLine { // find the first part of the dash for this line const place = this.lineLength % (this.dashSize * this.scale); - let dashIndex: number = 0, - dashStart: number = 0; + let dashIndex = 0, + dashStart = 0; let dashX = 0; for (let i = 0; i < this.dash.length; i++) { const dashSize = this.dash[i] * this.scale; diff --git a/test/callout-canvas-layer.test.ts b/test/callout-canvas-layer.test.ts index 4c4dc820..679dcfcf 100644 --- a/test/callout-canvas-layer.test.ts +++ b/test/callout-canvas-layer.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-magic-numbers */ import { describe, expect, it, beforeEach, afterEach, vi, SpyInstance } from 'vitest'; import { CanvasRenderingContext2DEvent } from 'jest-canvas-mock'; import createMockRaf from 'mock-raf'; @@ -47,14 +46,14 @@ describe('CalloutCanvasLayer', () => { layer.onUpdate({ data }); layer.onRescale(rescaleEventStub()); - layer.ctx.__clearEvents(); + layer.ctx?.__clearEvents(); // Act layer.data = data; mockRaf.step(); // Assert - const events: CanvasRenderingContext2DEvent[] = layer.ctx.__getEvents(); + const events: CanvasRenderingContext2DEvent[] = layer.ctx?.__getEvents() ?? []; const fillTextCalls = events.filter((call: CanvasRenderingContext2DEvent) => call.type === 'fillText'); expect(fillTextCalls.length).toBeGreaterThanOrEqual(1); }); @@ -68,14 +67,14 @@ describe('CalloutCanvasLayer', () => { layer.onUpdate({ data }); layer.onRescale(rescaleEventStub()); - layer.ctx.__clearEvents(); + layer.ctx?.__clearEvents(); // Act layer.data = data; mockRaf.step(); // Assert - const events: CanvasRenderingContext2DEvent[] = layer.ctx.__getEvents(); + const events: CanvasRenderingContext2DEvent[] = layer.ctx?.__getEvents() ?? []; const fillTextCalls = events.filter((call: CanvasRenderingContext2DEvent) => call.type === 'fillText'); expect(fillTextCalls.length).toBeGreaterThanOrEqual(1); }); @@ -87,7 +86,7 @@ describe('CalloutCanvasLayer', () => { layer.onUpdate({ data }); layer.onRescale(rescaleEventStub()); - layer.ctx.__clearEvents(); + layer.ctx?.__clearEvents(); // Act // Assert diff --git a/test/callout.test.ts b/test/callout.test.ts index 829cab70..121cb51f 100644 --- a/test/callout.test.ts +++ b/test/callout.test.ts @@ -119,9 +119,9 @@ describe('callout', () => { const overlap: GraphicObject[] = []; for (let i = 0; i < arr.length; i++) { - const eli = arr[i]; + const eli = arr[i]!; for (let j = 0; j < arr.length; j++) { - const elj = arr[j]; + const elj = arr[j]!; if (i !== j) { const overlapping = checkForOverlap(eli, elj); if (overlapping) { @@ -181,9 +181,9 @@ describe('callout', () => { const overlap: GraphicObjectWithId[] = []; for (let i = 0; i < arr.length; i++) { - const eli = arr[i]; + const eli = arr[i]!; for (let j = 0; j < arr.length; j++) { - const elj = arr[j]; + const elj = arr[j]!; if (i !== j) { const overlapping = checkForOverlap(eli, elj); if (overlapping) { diff --git a/test/html-layer.test.ts b/test/html-layer.test.ts index 7768f33c..0f06f770 100644 --- a/test/html-layer.test.ts +++ b/test/html-layer.test.ts @@ -52,31 +52,31 @@ describe('HTML', () => { it('should have interactive set to false and pointer-events:none by default', () => { const layer = new htmlLayer('well'); layer.onMount({ elm }); - expect(layer.elm.style('pointer-events')).toEqual('none'); + expect(layer.elm?.style('pointer-events')).toEqual('none'); expect(layer.interactive).toEqual(false); }); it('should set pointer-events:auto when setting interactive to true', () => { const layer = new htmlLayer('well'); layer.onMount({ elm }); layer.interactive = true; - expect(layer.elm.style('pointer-events')).toEqual('auto'); + expect(layer.elm?.style('pointer-events')).toEqual('auto'); }); it('should update z-index when changing order', () => { const layer = new htmlLayer('well'); layer.onMount({ elm }); expect(layer.order).toEqual(1); - expect(layer.elm.style('z-index')).toEqual('1'); + expect(layer.elm?.style('z-index')).toEqual('1'); layer.order = 2; expect(layer.order).toEqual(2); - expect(layer.elm.style('z-index')).toEqual('2'); + expect(layer.elm?.style('z-index')).toEqual('2'); }); it('should update opacity when changing its value', () => { const layer = new htmlLayer('well'); layer.onMount({ elm }); expect(layer.opacity).toEqual(1); - expect(layer.elm.style('opacity')).toEqual('1'); + expect(layer.elm?.style('opacity')).toEqual('1'); layer.opacity = 0.5; expect(layer.opacity).toEqual(0.5); - expect(layer.elm.style('opacity')).toEqual('0.5'); + expect(layer.elm?.style('opacity')).toEqual('0.5'); }); }); diff --git a/test/layer.test.ts b/test/layer.test.ts index b37b071c..a6a2fbb6 100644 --- a/test/layer.test.ts +++ b/test/layer.test.ts @@ -11,8 +11,8 @@ class TestLayer extends Layer { onInteractivityChanged(_interactive: boolean): void { throw new Error('Method not implemented.'); } - testString: string = ''; - updateWasCalled: boolean = false; + testString = ''; + updateWasCalled = false; override setData(data: string) { super.setData(data); this.testString = data; @@ -37,7 +37,7 @@ describe('Layer', () => { it('should set data upon construction and not call update when no element has been mounted', () => { const layer = new TestLayer('id', { data }); - expect(layer.element).toEqual(null); + expect(layer.element).toEqual(undefined); expect(layer.data).toEqual('test'); expect(layer.updateWasCalled).toEqual(false); }); @@ -46,7 +46,7 @@ describe('Layer', () => { layer.setData(data); - expect(layer.element).toEqual(null); + expect(layer.element).toEqual(undefined); expect(layer.data).toEqual('test'); expect(layer.updateWasCalled).toEqual(false); }); diff --git a/test/reference-system.test.ts b/test/reference-system.test.ts index 4d30c93b..10bdbca2 100644 --- a/test/reference-system.test.ts +++ b/test/reference-system.test.ts @@ -11,8 +11,8 @@ const wp = [ ]; function dist(a: number[], b: number[]): number { - const d0 = a[0] - b[0]; - const d1 = a[1] - b[1]; + const d0 = a[0]! - b[0]!; + const d1 = a[1]! - b[1]!; return Math.sqrt(d0 * d0 + d1 * d1); } @@ -74,10 +74,10 @@ describe('Reference system', () => { }); it('should have same distance between points in trajectory', () => { const trajectory = rs.getTrajectory(80); - const firstDistance = dist(trajectory.points[0], trajectory.points[1]); - let lastPoint = trajectory.points[1]; + const firstDistance = dist(trajectory.points[0]!, trajectory.points[1]!); + let lastPoint = trajectory.points[1]!; for (let i = 2; i < trajectory.points.length; i++) { - const point = trajectory.points[i]; + const point = trajectory.points[i]!; const currentDistance = dist(point, lastPoint); expect(currentDistance).toBeCloseTo(firstDistance); lastPoint = point; @@ -90,10 +90,10 @@ describe('Reference system', () => { it('should have same distance between points in extended trajectory', () => { const trajectory = rs.getExtendedTrajectory(200, 500.0, 500.0); - const firstDistance = dist(trajectory.points[0], trajectory.points[1]); - let lastPoint = trajectory.points[1]; + const firstDistance = dist(trajectory.points[0]!, trajectory.points[1]!); + let lastPoint = trajectory.points[1]!; for (let i = 2; i < trajectory.points.length; i++) { - const point = trajectory.points[i]; + const point = trajectory.points[i]!; const currentDistance = dist(point, lastPoint); expect(currentDistance).toBeCloseTo(firstDistance, 0); lastPoint = point; @@ -101,8 +101,8 @@ describe('Reference system', () => { }); it('should have correct length on extension', () => { const trajectory = rs.getExtendedTrajectory(100, 500.0, 500.0); - const startExtend = dist(trajectory.points[0], rs.interpolators.trajectory.getPointAt(0.0) as number[]); - const endExtend = dist(trajectory.points[99], rs.interpolators.trajectory.getPointAt(1.0) as number[]); + const startExtend = dist(trajectory.points[0]!, rs.interpolators.trajectory.getPointAt(0.0) as number[]); + const endExtend = dist(trajectory.points[99]!, rs.interpolators.trajectory.getPointAt(1.0) as number[]); expect(startExtend).toBeCloseTo(500.0); expect(endExtend).toBeCloseTo(500.0); }); @@ -127,11 +127,13 @@ describe('Reference system', () => { const trajectory = irs.getExtendedTrajectory(100, 1500.0, 1500.0); expect(trajectory.points.length).toEqual(100); - const startExtend = dist(trajectory.points[0], irs.interpolators.trajectory.getPointAt(0.0) as number[]); - const endExtend = dist(trajectory.points[99], irs.interpolators.trajectory.getPointAt(1.0) as number[]); + const startExtend = dist(trajectory.points[0]!, irs.interpolators.trajectory.getPointAt(0.0) as number[]); + const endExtend = dist(trajectory.points[99]!, irs.interpolators.trajectory.getPointAt(1.0) as number[]); expect(startExtend).toBeCloseTo(1500.0); expect(endExtend).toBeCloseTo(1500.0); - const angle = degrees(Math.atan((trajectory.points[99][0] - trajectory.points[0][0]) / (trajectory.points[99][1] - trajectory.points[0][1]))); + const angle = degrees( + Math.atan((trajectory.points[99]?.[0]! - trajectory.points[0]?.[0]!) / (trajectory.points[99]?.[1]! - trajectory.points[0]?.[1]!)), + ); expect(angle).toBeCloseTo(45.0); }); diff --git a/test/schematicShapeGenerator.test.ts b/test/schematicShapeGenerator.test.ts index fb1c34bf..58396fe0 100644 --- a/test/schematicShapeGenerator.test.ts +++ b/test/schematicShapeGenerator.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-magic-numbers */ import { describe, expect, it } from 'vitest'; import { Casing, CasingWindow, Completion, HoleSize } from '../src'; import * as SchematicShapeGenerator from '../src/datautils/schematicShapeGenerator'; diff --git a/test/svg-layer.test.ts b/test/svg-layer.test.ts index 1a985d2f..b56ff159 100644 --- a/test/svg-layer.test.ts +++ b/test/svg-layer.test.ts @@ -64,31 +64,31 @@ describe('SVG', () => { it('should have interactive set to false and pointer-events:none by default', () => { const layer = new WellborepathLayer('well'); layer.onMount({ elm }); - expect(layer.elm.style('pointer-events')).toEqual('none'); + expect(layer.elm?.style('pointer-events')).toEqual('none'); expect(layer.interactive).toEqual(false); }); it('should set pointer-events:auto when setting interactive to true', () => { const layer = new WellborepathLayer('well'); layer.onMount({ elm }); layer.interactive = true; - expect(layer.elm.style('pointer-events')).toEqual('auto'); + expect(layer.elm?.style('pointer-events')).toEqual('auto'); }); it('should update z-index when changing order', () => { const layer = new WellborepathLayer('well'); layer.onMount({ elm }); expect(layer.order).toEqual(1); - expect(layer.elm.style('z-index')).toEqual('1'); + expect(layer.elm?.style('z-index')).toEqual('1'); layer.order = 2; expect(layer.order).toEqual(2); - expect(layer.elm.style('z-index')).toEqual('2'); + expect(layer.elm?.style('z-index')).toEqual('2'); }); it('should update opacity when changing its value', () => { const layer = new WellborepathLayer('well'); layer.onMount({ elm }); expect(layer.opacity).toEqual(1); - expect(layer.elm.style('opacity')).toEqual('1'); + expect(layer.elm?.style('opacity')).toEqual('1'); layer.opacity = 0.5; expect(layer.opacity).toEqual(0.5); - expect(layer.elm.style('opacity')).toEqual('0.5'); + expect(layer.elm?.style('opacity')).toEqual('0.5'); }); }); diff --git a/test/vectorUtils.test.ts b/test/vectorUtils.test.ts index 73089603..aa2ff45f 100644 --- a/test/vectorUtils.test.ts +++ b/test/vectorUtils.test.ts @@ -14,9 +14,9 @@ describe('vectorUtils', () => { it('should return a 0 vector for list of only 1 point', () => { const normals = createNormals([new Point(1, 1)]); - expect(normals[0].x).toEqual(0); - expect(normals[0].y).toEqual(0); - expect(normals[0].magnitude).toEqual(0); + expect(normals[0]?.x).toEqual(0); + expect(normals[0]?.y).toEqual(0); + expect(normals[0]?.magnitude).toEqual(0); }); it('should create a normal for each point', () => { @@ -30,13 +30,13 @@ describe('vectorUtils', () => { it('should calculate vectors', () => { const normals = createNormals(points); const normal45 = new Vector2(-0.70710678118, 0.70710678118); - expect(normals[0].x).toBeCloseTo(normal45.x, 10); - expect(normals[0].y).toBeCloseTo(normal45.y, 10); + expect(normals[0]?.x).toBeCloseTo(normal45.x, 10); + expect(normals[0]?.y).toBeCloseTo(normal45.y, 10); }); it('should be normalized', () => { const normals = createNormals(points); - expect(normals[0].magnitude).toBeCloseTo(1, 10); + expect(normals[0]?.magnitude).toBeCloseTo(1, 10); }); }); diff --git a/tsconfig.base.json b/tsconfig.base.json index 5b907cd0..4fb58639 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,10 +1,10 @@ { "compilerOptions": { /* Type-Checking */ - /*"strict": true,*/ + "strict": true, "allowUnusedLabels": false, "allowUnreachableCode": false, - /*"exactOptionalPropertyTypes": true,*/ + "exactOptionalPropertyTypes": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true,