@geoblocks/elevation-profile 0.0.25-beta.5 → 0.0.25-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/elevation-profile.d.ts.map +1 -1
- package/dist/elevation-profile.js +51 -23
- package/dist/elevation-profile.js.map +1 -1
- package/elevation-profile.ts +80 -34
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -28,7 +28,6 @@ npm i --save @geoblocks/elevation-profile
|
|
|
28
28
|
| `points` | `number[][]` | | Points to be displayed on the profile
|
|
29
29
|
| `lineSegments` | `SegmentData` | `undefined` | Segments for the elevation line with different visual properties
|
|
30
30
|
| `xAxisSegments` | `SegmentData` | `undefined` | Segments for trail bands below the x-axis
|
|
31
|
-
| `areaSegments` | `SegmentData` | `undefined` | Segments for the area below the elevation line
|
|
32
31
|
| `margin` | `Object` | `{top: 20, right: 20, bottom: 20, left: 40}` | Margin in pixels around the elevation profile
|
|
33
32
|
| `pointerEvents` | `Boolean` | `true` | Whether to emit pointer events
|
|
34
33
|
| `tickSize` | `Object` | `{x: 100, y: 40}` | Size of the ticks in pixels
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":";;AAqBA,0BAA0B,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;AAEjE,0BAA0B;IACxB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;IACjC,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC;CACH,CAAC;AAEF,qCACsC,SAAQ,UAAU;IAC5B,SAAS,SAAK;IACd,MAAM,SAAsB;IAC7B,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAM;IACzB,MAAM,EAAE,MAAM,EAAE,EAAE,CAAM;IACrC,WAAW,GAAI,GAAG,WAAW,EAAE,GAAG,WAAW,EAAE,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAG,IAAI,CAAO;IAC5E,MAAM;;;;;MAA8C;IACpD,QAAQ;;;MAAmB;IAC1B,aAAa,UAAQ;IACvB,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,aAAa,CAAC,EAAE,WAAW,CAAC;IAG5C,OAAO;;;MAAgB;IA8BvB,OAAO,CAAC,iBAAiB,EAAE,cAAc;IAczC,UAAU,CAAC,iBAAiB,EAAE,cAAc;IA8B5C,MAAM;IAuGR,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,GAAG;IAQzC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,GAAG,GAAG;IAO5C,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc;IAI3D,YAAY;IAYZ,oBAAoB;IAuDpB,gBAAgB;CAG1B;AAkCD,QAAQ,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,mBAAmB,EAAE,gBAAgB,CAAC;KACvC;CACF","sources":["elevation-profile.ts"],"sourcesContent":["import {LitElement, svg} from 'lit';\nimport {customElement, state, property} from 'lit/decorators.js';\nimport {ResizeController} from '@lit-labs/observers/resize-controller.js';\nimport {guard} from 'lit/directives/guard.js';\nimport {ifDefined} from 'lit/directives/if-defined.js';\nimport type {PropertyValues, TemplateResult} from 'lit';\n\nimport {extent, bisector} from 'd3-array';\nimport {scaleLinear} from 'd3-scale';\nimport {line, area} from 'd3-shape';\nimport {axisBottom, axisLeft} from 'd3-axis';\nimport {select, pointer} from 'd3-selection';\n\nimport simplify from './simplify.js';\n\ntype PlotPoint = {\n x: number;\n y: number;\n coordinate: number[];\n};\n\nexport type SegmentData = Array<[number, number, string | null]>;\n\nexport type OverDetails = {\n coordinate: number[];\n position: {x: number; y: number};\n segments?: {\n line: string | null;\n xAxis: string | null;\n };\n};\n\n@customElement('elevation-profile')\nexport default class ElevationProfile extends LitElement {\n @property({type: Number}) tolerance = 1;\n @property({type: String}) locale = navigator.language;\n @property({type: Array}) lines: number[][][] = [];\n @property({type: Array}) points: number[][] = [];\n @property() updateScale = (x: scaleLinear, y: scaleLinear, width: number, height: number): void => {};\n @property({type: Object}) margin = {top: 20, right: 20, bottom: 20, left: 20};\n @property({type: Object}) tickSize = {x: 100, y: 40};\n @property({type: Boolean}) pointerEvents = true;\n @property({type: Array}) lineSegments?: SegmentData;\n @property({type: Array}) xAxisSegments?: SegmentData;\n private yAxisObserver: ResizeObserver | null = null;\n\n @state() pointer = {x: 0, y: 0};\n private resizeController = new ResizeController(this, {\n callback: () => [this.offsetWidth, this.offsetHeight],\n });\n\n private plotData: PlotPoint[] = [];\n private pointsData: PlotPoint[] = [];\n private lineSegmentsData: SegmentData = [];\n private xAxisSegmentsData: SegmentData = [];\n private scaleX = scaleLinear();\n private scaleY = scaleLinear();\n\n private bisectDistance = bisector((point: PlotPoint) => point.x);\n\n private line = line()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y((point: PlotPoint) => this.scaleY(point.y));\n private area = area()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y1((point: PlotPoint) => this.scaleY(point.y));\n private xAxis = axisBottom(this.scaleX).tickFormat((value: number) => this.tickFormat(value, 'x'));\n private yAxis = axisLeft(this.scaleY).tickFormat((value: number) => this.tickFormat(value, 'y'));\n private xGrid = axisBottom(this.scaleX).tickFormat(() => '');\n private yGrid = axisLeft(this.scaleY).tickFormat(() => '');\n\n private meterFormat: Intl.NumberFormat | null = null;\n private kilometerFormat: Intl.NumberFormat | null = null;\n\n override updated(changedProperties: PropertyValues) {\n if (changedProperties.has('locale')) {\n this.meterFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'meter',\n });\n\n this.kilometerFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'kilometer',\n });\n }\n }\n\n override willUpdate(changedProperties: PropertyValues) {\n if (changedProperties.has('lines')) {\n this.plotData.length = 0;\n this.lines.forEach((line, index) => {\n const data = line.map((coordinate) => ({x: coordinate[3], y: coordinate[2], coordinate}));\n this.plotData.push(...data);\n if (index < this.lines.length - 1) {\n this.plotData.push({x: line[line.length - 1][3], y: NaN, coordinate: []});\n }\n });\n\n this.scaleX.domain(extent(this.plotData, (data: PlotPoint) => data.x));\n this.scaleY.domain(extent(this.plotData, (data: PlotPoint) => data.y));\n\n this.updateScale(this.scaleX, this.scaleY, this.offsetWidth, this.offsetHeight);\n }\n if (changedProperties.has('points')) {\n this.pointsData.length = 0;\n for (const point of this.points) {\n this.pointsData.push({x: point[3], y: point[2], coordinate: point});\n }\n }\n if (changedProperties.has('lineSegments')) {\n this.lineSegmentsData = fillUnspecified(this.lineSegments || [], this.plotData.length);\n }\n if (changedProperties.has('xAxisSegments')) {\n this.xAxisSegmentsData = fillUnspecified(this.xAxisSegments || [], this.plotData.length);\n }\n }\n\n override render() {\n // FIXME: better handling of null this.resizeController.value\n const [width, height] = this.resizeController.value ?? [this.margin.left + this.margin.right, this.margin.top + this.margin.bottom];\n const ml = (this.querySelector('.axis.y')?.getBoundingClientRect().width || 0) + this.margin.left\n\n this.scaleX.range([ml, width - this.margin.right]);\n this.scaleY.range([height - this.margin.bottom, this.margin.top]);\n\n this.area.y0(height - this.margin.bottom);\n\n this.yGrid.tickSize(-width + ml + this.margin.right);\n this.xGrid.tickSize(height - this.margin.top - this.margin.bottom);\n\n const xTicks = width / this.tickSize.x;\n const yTicks = height / this.tickSize.y;\n this.xAxis.ticks(xTicks);\n this.xGrid.ticks(xTicks);\n this.yAxis.ticks(yTicks);\n this.yGrid.ticks(yTicks);\n\n select(this.querySelector('.axis.x')).call(this.xAxis);\n select(this.querySelector('.axis.y')).call(this.yAxis);\n select(this.querySelector('.grid.x')).call(this.xGrid);\n select(this.querySelector('.grid.y')).call(this.yGrid);\n\n const offset = this.yGrid.offset();\n\n return svg`\n <svg width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" xmlns=\"http://www.w3.org/2000/svg\">\n <g class=\"grid y\" transform=\"translate(${ml}, 0)\" />\n <g class=\"grid x\" transform=\"translate(0, ${this.margin.bottom})\" />\n <g class=\"axis x\" transform=\"translate(0, ${height - this.margin.bottom})\" />\n <g class=\"axis y\" transform=\"translate(${ml}, 0)\" />\n\n ${guard([this.lines, width, height, ml], () => svg`\n <path class=\"area\" d=\"${this.area(this.plotData)}\" />\n ${this.renderLineSegments('elevation')}`\n )}\n\n <g style=\"visibility: ${this.pointer.x > 0 ? 'visible' : 'hidden'}\">\n <g clip-path=\"polygon(0 0, ${this.pointer.x - ml} 0, ${this.pointer.x - ml} 100%, 0 100%)\">\n ${guard([this.lines, width, height, ml], () => this.renderLineSegments('elevation highlight'))}\n </g>\n <line\n class=\"pointer-line x\"\n x1=\"${this.pointer.x}\"\n y1=\"${this.margin.top}\"\n x2=\"${this.pointer.x}\"\n y2=\"${height - this.margin.bottom}\"\n />\n <line\n class=\"pointer-line y\"\n x1=\"${ml}\"\n y1=\"${this.pointer.y}\"\n x2=\"${width - this.margin.right}\"\n y2=\"${this.pointer.y}\"\n />\n <circle class=\"pointer-circle-outline\" cx=\"${this.pointer.x}\" cy=\"${this.pointer.y}\" r=\"16\"/>\n <circle class=\"pointer-circle\" cx=\"${this.pointer.x}\" cy=\"${this.pointer.y}\" r=\"6\"/>\n </g>\n\n ${this.pointsData.map((point, index) => this.pointSvg(this.scaleX(point.x), this.scaleY(point.y), index))}\n\n <rect\n width=\"${width}\"\n height=\"${height}\"\n fill=\"none\"\n pointer-events=\"${this.pointerEvents ? 'all' : 'none'}\"\n style=\"display: block; touch-action: none;\"\n @pointermove=\"${this.pointerMove}\"\n @pointerout=\"${this.pointerOut}\"\n />\n <g\n transform=\"translate(${ml},${height - this.margin.bottom + offset})\"\n class=\"axis\"\n style=\"visibility: ${this.lines.length ? 'visible' : 'hidden'}\">\n <line x2=\"${width - ml - this.margin.right}\"></line>\n </g>\n <g transform=\"translate(0,${height - this.margin.bottom + offset})\">\n ${this.renderTrailBands()}\n </g>\n </svg>\n `;\n }\n\n private renderLineSegments(className: string) {\n return this.lineSegmentsData.map(([start, end, value]) => {\n const segmentData = this.plotData.slice(start, end + 1);\n console.assert(segmentData.length >= 2);\n return svg`<path class=\"${className}\" data-value=\"${ifDefined(value)}\" d=\"${this.line(segmentData)}\" fill=\"none\" />`;\n });\n }\n\n private renderTrailBands() {\n return this.xAxisSegmentsData.map(([start, end, value]) => {\n const x1 = this.scaleX(this.plotData[start].x);\n const x2 = this.scaleX(this.plotData[end].x);\n console.assert(this.plotData[start].x < this.plotData[end].x);\n const bandWidth = x2 - x1;\n return svg`<rect class=\"trail-band\" data-value=\"${ifDefined(value)}\" x=\"${x1}\" width=\"${bandWidth}\" />`;\n });\n }\n\n public tickFormat(value: number, axis: 'x' | 'y') {\n if (axis === 'y' || value < 1000) {\n return this.meterFormat!.format(value);\n } else {\n return this.kilometerFormat!.format(value / 1000);\n }\n }\n\n public tickValues(values: number[], axis: 'x' | 'y') {\n if (values.length === 0 || (axis !== 'x' && axis !== 'y')) {\n return;\n }\n axis === 'x' ? this.xAxis.tickValues(values) : this.yAxis.tickValues(values);\n }\n\n public pointSvg(x: number, y: number, index: number): TemplateResult {\n return svg`<circle class=\"point\" cx=\"${x}\" cy=\"${y}\" r=\"10\"/>`;\n }\n\n override firstUpdated() {\n const axisY = this.querySelector('.axis.y');\n if (axisY) {\n this.yAxisObserver = new ResizeObserver(() => {\n this.requestUpdate()\n })\n this.yAxisObserver.observe(axisY);\n }\n // FIXME: because the ref element are used before render is done, we need to force an update\n this.requestUpdate();\n }\n\n override disconnectedCallback() {\n if (this.yAxisObserver) {\n this.yAxisObserver.disconnect();\n }\n super.disconnectedCallback();\n }\n\n private pointerMove(event: PointerEvent) {\n const pointerDistance = this.scaleX.invert(pointer(event)[0]);\n const index = Math.min(this.bisectDistance.left(this.plotData, pointerDistance), this.plotData.length - 1);\n\n if (index < 0) {\n return;\n }\n // FIXME:\n // var d0 = this.plotData[index - 1]\n // var d1 = this.plotData[index];\n // // work out which date value is closest to the mouse\n // var d = mouseDate - d0[0] > d1[0] - mouseDate ? d1 : d0;\n\n const data = this.plotData[index];\n\n if (isNaN(data.y)) {\n return;\n }\n\n this.pointer = {\n x: this.scaleX(data.x),\n y: this.scaleY(data.y),\n };\n\n const segments: OverDetails['segments'] = {\n line: getSegmentValueAtIndex(this.lineSegmentsData, index),\n xAxis: getSegmentValueAtIndex(this.xAxisSegmentsData, index),\n };\n\n this.dispatchEvent(\n new CustomEvent<OverDetails>('over', {\n detail: {\n coordinate: this.plotData[index].coordinate,\n position: this.pointer,\n ...(Object.keys(segments).length > 0 && { segments })\n }\n }),\n );\n }\n\n private pointerOut() {\n this.pointer = {\n x: 0,\n y: 0,\n };\n this.dispatchEvent(new CustomEvent('out'));\n }\n\n override createRenderRoot() {\n return this;\n }\n}\n\nfunction fillUnspecified(segment: SegmentData, length: number): SegmentData {\n // FIXME: we must take into account the NaN in the plotData:\n // * insert null segments where needed\n // * shift the indices accordingly\n const filledSegments: SegmentData = [];\n let currentIndex = 0;\n\n for (const [start, end, value] of segment) {\n if (start > currentIndex) {\n filledSegments.push([currentIndex, start, null]);\n }\n filledSegments.push([start, end, value]);\n currentIndex = end;\n }\n\n if (currentIndex < length - 1) {\n filledSegments.push([currentIndex, length - 1, null]);\n }\n\n return filledSegments;\n}\n\nfunction getSegmentValueAtIndex(segments: SegmentData, index: number): string | null {\n for (const [start, end, value] of segments) {\n if (index >= start && index < end) {\n return value;\n }\n }\n // we must never be here because of fillUnspecified\n return null;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'elevation-profile': ElevationProfile;\n }\n}\n"],"names":[],"version":3,"file":"elevation-profile.d.ts.map"}
|
|
1
|
+
{"mappings":";;AAsBA,0BAA0B,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;AAEjE,0BAA0B;IACxB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;IACjC,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC;CACH,CAAC;AAEF,qCACsC,SAAQ,UAAU;IAC5B,SAAS,SAAK;IACd,MAAM,SAAsB;IAC7B,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAM;IACzB,MAAM,EAAE,MAAM,EAAE,EAAE,CAAM;IACrC,WAAW,GAAI,GAAG,WAAW,EAAE,GAAG,WAAW,EAAE,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAG,IAAI,CAAO;IAC5E,MAAM;;;;;MAA8C;IACpD,QAAQ;;;MAAmB;IAC1B,aAAa,UAAQ;IACvB,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,aAAa,CAAC,EAAE,WAAW,CAAC;IAG5C,OAAO;;;MAAgB;IA+BvB,OAAO,CAAC,iBAAiB,EAAE,cAAc;IAcxC,UAAU,CAAC,iBAAiB,EAAE,cAAc;IAiC7C,MAAM;IAuGR,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,GAAG;IAQzC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,GAAG,GAAG;IAO5C,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc;IAI3D,YAAY;IAYZ,oBAAoB;IAuDpB,gBAAgB;CAG1B;AA2ED,QAAQ,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,mBAAmB,EAAE,gBAAgB,CAAC;KACvC;CACF","sources":["elevation-profile.ts"],"sourcesContent":["import {LitElement, svg} from 'lit';\nimport {customElement, state, property} from 'lit/decorators.js';\nimport {ResizeController} from '@lit-labs/observers/resize-controller.js';\nimport {guard} from 'lit/directives/guard.js';\nimport {ifDefined} from 'lit/directives/if-defined.js';\nimport type {PropertyValues, TemplateResult} from 'lit';\n\nimport {extent, bisector} from 'd3-array';\nimport {scaleLinear} from 'd3-scale';\nimport {line, area} from 'd3-shape';\nimport {axisBottom, axisLeft} from 'd3-axis';\nimport {select, pointer} from 'd3-selection';\n\n// FIXME: use simplify to reduce number of points based on tolerance\nimport simplify from './simplify.js';\n\ntype PlotPoint = {\n x: number;\n y: number;\n coordinate: number[];\n};\n\nexport type SegmentData = Array<[number, number, string | null]>;\n\nexport type OverDetails = {\n coordinate: number[];\n position: {x: number; y: number};\n segments?: {\n line: string | null;\n xAxis: string | null;\n };\n};\n\n@customElement('elevation-profile')\nexport default class ElevationProfile extends LitElement {\n @property({type: Number}) tolerance = 1;\n @property({type: String}) locale = navigator.language;\n @property({type: Array}) lines: number[][][] = [];\n @property({type: Array}) points: number[][] = [];\n @property() updateScale = (x: scaleLinear, y: scaleLinear, width: number, height: number): void => {};\n @property({type: Object}) margin = {top: 20, right: 20, bottom: 20, left: 20};\n @property({type: Object}) tickSize = {x: 100, y: 40};\n @property({type: Boolean}) pointerEvents = true;\n @property({type: Array}) lineSegments?: SegmentData;\n @property({type: Array}) xAxisSegments?: SegmentData;\n private yAxisObserver: ResizeObserver | null = null;\n\n @state() pointer = {x: 0, y: 0};\n private resizeController = new ResizeController(this, {\n callback: () => [this.offsetWidth, this.offsetHeight],\n });\n\n private plotData: PlotPoint[] = [];\n private pointsData: PlotPoint[] = [];\n private lineSegmentsData: SegmentData = [];\n private xAxisSegmentsData: SegmentData = [];\n private gapPositions: number[] = [];\n private scaleX = scaleLinear();\n private scaleY = scaleLinear();\n\n private bisectDistance = bisector((point: PlotPoint) => point.x);\n\n private line = line()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y((point: PlotPoint) => this.scaleY(point.y));\n private area = area()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y1((point: PlotPoint) => this.scaleY(point.y));\n private xAxis = axisBottom(this.scaleX).tickFormat((value: number) => this.tickFormat(value, 'x'));\n private yAxis = axisLeft(this.scaleY).tickFormat((value: number) => this.tickFormat(value, 'y'));\n private xGrid = axisBottom(this.scaleX).tickFormat(() => '');\n private yGrid = axisLeft(this.scaleY).tickFormat(() => '');\n\n private meterFormat: Intl.NumberFormat | null = null;\n private kilometerFormat: Intl.NumberFormat | null = null;\n\n override updated(changedProperties: PropertyValues) {\n if (changedProperties.has('locale')) {\n this.meterFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'meter',\n });\n\n this.kilometerFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'kilometer',\n });\n }\n }\n\n override willUpdate(changedProperties: PropertyValues) {\n if (changedProperties.has('lines')) {\n this.plotData.length = 0;\n this.gapPositions.length = 0;\n this.lines.forEach((line, index) => {\n const data = line.map((coordinate) => ({x: coordinate[3], y: coordinate[2], coordinate}));\n this.plotData.push(...data);\n if (index < this.lines.length - 1) {\n // insert a gap between lines\n this.gapPositions.push(this.plotData.length);\n this.plotData.push({x: line[line.length - 1][3], y: NaN, coordinate: []});\n }\n });\n\n this.scaleX.domain(extent(this.plotData, (data: PlotPoint) => data.x));\n this.scaleY.domain(extent(this.plotData, (data: PlotPoint) => data.y));\n\n this.updateScale(this.scaleX, this.scaleY, this.offsetWidth, this.offsetHeight);\n }\n if (changedProperties.has('points')) {\n this.pointsData.length = 0;\n for (const point of this.points) {\n this.pointsData.push({x: point[3], y: point[2], coordinate: point});\n }\n }\n if (changedProperties.has('lineSegments')) {\n this.lineSegmentsData = fillUnspecified(this.lineSegments || [], this.plotData.length, this.gapPositions);\n }\n if (changedProperties.has('xAxisSegments')) {\n this.xAxisSegmentsData = fillUnspecified(this.xAxisSegments || [], this.plotData.length, this.gapPositions);\n }\n }\n\n override render() {\n // FIXME: better handling of null this.resizeController.value\n const [width, height] = this.resizeController.value ?? [this.margin.left + this.margin.right, this.margin.top + this.margin.bottom];\n const ml = (this.querySelector('.axis.y')?.getBoundingClientRect().width || 0) + this.margin.left\n\n this.scaleX.range([ml, width - this.margin.right]);\n this.scaleY.range([height - this.margin.bottom, this.margin.top]);\n\n this.area.y0(height - this.margin.bottom);\n\n this.yGrid.tickSize(-width + ml + this.margin.right);\n this.xGrid.tickSize(height - this.margin.top - this.margin.bottom);\n\n const xTicks = width / this.tickSize.x;\n const yTicks = height / this.tickSize.y;\n this.xAxis.ticks(xTicks);\n this.xGrid.ticks(xTicks);\n this.yAxis.ticks(yTicks);\n this.yGrid.ticks(yTicks);\n\n select(this.querySelector('.axis.x')).call(this.xAxis);\n select(this.querySelector('.axis.y')).call(this.yAxis);\n select(this.querySelector('.grid.x')).call(this.xGrid);\n select(this.querySelector('.grid.y')).call(this.yGrid);\n\n const offset = this.yGrid.offset();\n\n return svg`\n <svg width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" xmlns=\"http://www.w3.org/2000/svg\">\n <g class=\"grid y\" transform=\"translate(${ml}, 0)\" />\n <g class=\"grid x\" transform=\"translate(0, ${this.margin.bottom})\" />\n <g class=\"axis x\" transform=\"translate(0, ${height - this.margin.bottom})\" />\n <g class=\"axis y\" transform=\"translate(${ml}, 0)\" />\n\n ${guard([this.lines, width, height, ml], () => svg`\n <path class=\"area\" d=\"${this.area(this.plotData)}\" />\n ${this.renderLineSegments('elevation')}`\n )}\n\n <g style=\"visibility: ${this.pointer.x > 0 ? 'visible' : 'hidden'}\">\n <g clip-path=\"polygon(0 0, ${this.pointer.x - ml} 0, ${this.pointer.x - ml} 100%, 0 100%)\">\n ${guard([this.lines, width, height, ml], () => this.renderLineSegments('elevation highlight'))}\n </g>\n <line\n class=\"pointer-line x\"\n x1=\"${this.pointer.x}\"\n y1=\"${this.margin.top}\"\n x2=\"${this.pointer.x}\"\n y2=\"${height - this.margin.bottom}\"\n />\n <line\n class=\"pointer-line y\"\n x1=\"${ml}\"\n y1=\"${this.pointer.y}\"\n x2=\"${width - this.margin.right}\"\n y2=\"${this.pointer.y}\"\n />\n <circle class=\"pointer-circle-outline\" cx=\"${this.pointer.x}\" cy=\"${this.pointer.y}\" r=\"16\"/>\n <circle class=\"pointer-circle\" cx=\"${this.pointer.x}\" cy=\"${this.pointer.y}\" r=\"6\"/>\n </g>\n\n ${this.pointsData.map((point, index) => this.pointSvg(this.scaleX(point.x), this.scaleY(point.y), index))}\n\n <rect\n width=\"${width}\"\n height=\"${height}\"\n fill=\"none\"\n pointer-events=\"${this.pointerEvents ? 'all' : 'none'}\"\n style=\"display: block; touch-action: none;\"\n @pointermove=\"${this.pointerMove}\"\n @pointerout=\"${this.pointerOut}\"\n />\n <g\n transform=\"translate(${ml},${height - this.margin.bottom + offset})\"\n class=\"axis\"\n style=\"visibility: ${this.lines.length ? 'visible' : 'hidden'}\">\n <line x2=\"${width - ml - this.margin.right}\"></line>\n </g>\n <g transform=\"translate(0,${height - this.margin.bottom + offset})\">\n ${this.renderTrailBands()}\n </g>\n </svg>\n `;\n }\n\n private renderLineSegments(className: string) {\n return this.lineSegmentsData.map(([start, end, value]) => {\n const segmentData = this.plotData.slice(start, end + 1);\n console.assert(segmentData.length >= 2);\n return svg`<path class=\"${className}\" data-value=\"${ifDefined(value)}\" d=\"${this.line(segmentData)}\" fill=\"none\" />`;\n });\n }\n\n private renderTrailBands() {\n return this.xAxisSegmentsData.map(([start, end, value]) => {\n const x1 = this.scaleX(this.plotData[start].x);\n const x2 = this.scaleX(this.plotData[end].x);\n console.assert(this.plotData[start].x < this.plotData[end].x);\n const bandWidth = x2 - x1;\n return svg`<rect class=\"trail-band\" data-value=\"${ifDefined(value)}\" x=\"${x1}\" width=\"${bandWidth}\" />`;\n });\n }\n\n public tickFormat(value: number, axis: 'x' | 'y') {\n if (axis === 'y' || value < 1000) {\n return this.meterFormat!.format(value);\n } else {\n return this.kilometerFormat!.format(value / 1000);\n }\n }\n\n public tickValues(values: number[], axis: 'x' | 'y') {\n if (values.length === 0 || (axis !== 'x' && axis !== 'y')) {\n return;\n }\n axis === 'x' ? this.xAxis.tickValues(values) : this.yAxis.tickValues(values);\n }\n\n public pointSvg(x: number, y: number, index: number): TemplateResult {\n return svg`<circle class=\"point\" cx=\"${x}\" cy=\"${y}\" r=\"10\"/>`;\n }\n\n override firstUpdated() {\n const axisY = this.querySelector('.axis.y');\n if (axisY) {\n this.yAxisObserver = new ResizeObserver(() => {\n this.requestUpdate()\n })\n this.yAxisObserver.observe(axisY);\n }\n // FIXME: because the ref element are used before render is done, we need to force an update\n this.requestUpdate();\n }\n\n override disconnectedCallback() {\n if (this.yAxisObserver) {\n this.yAxisObserver.disconnect();\n }\n super.disconnectedCallback();\n }\n\n private pointerMove(event: PointerEvent) {\n const pointerDistance = this.scaleX.invert(pointer(event)[0]);\n const index = Math.min(this.bisectDistance.left(this.plotData, pointerDistance), this.plotData.length - 1);\n\n if (index < 0) {\n return;\n }\n // FIXME:\n // var d0 = this.plotData[index - 1]\n // var d1 = this.plotData[index];\n // // work out which date value is closest to the mouse\n // var d = mouseDate - d0[0] > d1[0] - mouseDate ? d1 : d0;\n\n const data = this.plotData[index];\n\n if (isNaN(data.y)) {\n return;\n }\n\n this.pointer = {\n x: this.scaleX(data.x),\n y: this.scaleY(data.y),\n };\n\n const segments: OverDetails['segments'] = {\n line: getSegmentValueAtIndex(this.lineSegmentsData, index),\n xAxis: getSegmentValueAtIndex(this.xAxisSegmentsData, index),\n };\n\n this.dispatchEvent(\n new CustomEvent<OverDetails>('over', {\n detail: {\n coordinate: this.plotData[index].coordinate,\n position: this.pointer,\n ...(Object.keys(segments).length > 0 && { segments })\n }\n }),\n );\n }\n\n private pointerOut() {\n this.pointer = {\n x: 0,\n y: 0,\n };\n this.dispatchEvent(new CustomEvent('out'));\n }\n\n override createRenderRoot() {\n return this;\n }\n}\n\nfunction fillUnspecified(segment: SegmentData, length: number, gapPositions: number[] = []): SegmentData {\n // Create a set of gap positions for quick lookup\n const gapSet = new Set(gapPositions);\n\n const filledSegments: SegmentData = [];\n let currentIndex = 0;\n\n for (const [start, end, value] of segment) {\n // Fill gap from currentIndex to start, skipping gap positions\n if (start > currentIndex) {\n let fillStart = currentIndex;\n\n // Skip leading gaps\n while (fillStart < start && gapSet.has(fillStart)) {\n fillStart++;\n }\n\n if (fillStart < start) {\n // Find end position before start, avoiding gaps\n let fillEnd = start - 1;\n while (fillEnd >= fillStart && gapSet.has(fillEnd)) {\n fillEnd--;\n }\n\n if (fillEnd >= fillStart) {\n filledSegments.push([fillStart, fillEnd, null]);\n }\n }\n }\n\n // Add the segment, skipping if it points to a gap\n if (!gapSet.has(start) && !gapSet.has(end)) {\n filledSegments.push([start, end, value]);\n }\n\n currentIndex = end + 1;\n }\n\n // Fill remaining range from currentIndex to length, skipping gaps\n if (currentIndex < length) {\n let fillStart = currentIndex;\n\n // Skip leading gaps\n while (fillStart < length && gapSet.has(fillStart)) {\n fillStart++;\n }\n\n if (fillStart < length) {\n // Find last valid index\n let fillEnd = length - 1;\n while (fillEnd >= fillStart && gapSet.has(fillEnd)) {\n fillEnd--;\n }\n\n if (fillEnd >= fillStart) {\n filledSegments.push([fillStart, fillEnd, null]);\n }\n }\n }\n\n return filledSegments;\n}\n\nfunction getSegmentValueAtIndex(segments: SegmentData, index: number): string | null {\n for (const [start, end, value] of segments) {\n if (index >= start && index < end) {\n return value;\n }\n }\n // we must never be here because of fillUnspecified\n return null;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'elevation-profile': ElevationProfile;\n }\n}\n"],"names":[],"version":3,"file":"elevation-profile.d.ts.map"}
|
|
@@ -59,6 +59,7 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
|
|
|
59
59
|
this.pointsData = [];
|
|
60
60
|
this.lineSegmentsData = [];
|
|
61
61
|
this.xAxisSegmentsData = [];
|
|
62
|
+
this.gapPositions = [];
|
|
62
63
|
this.scaleX = (0, $agntW$scaleLinear)();
|
|
63
64
|
this.scaleY = (0, $agntW$scaleLinear)();
|
|
64
65
|
this.bisectDistance = (0, $agntW$bisector)((point)=>point.x);
|
|
@@ -86,6 +87,7 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
|
|
|
86
87
|
willUpdate(changedProperties) {
|
|
87
88
|
if (changedProperties.has('lines')) {
|
|
88
89
|
this.plotData.length = 0;
|
|
90
|
+
this.gapPositions.length = 0;
|
|
89
91
|
this.lines.forEach((line, index)=>{
|
|
90
92
|
const data = line.map((coordinate)=>({
|
|
91
93
|
x: coordinate[3],
|
|
@@ -93,11 +95,15 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
|
|
|
93
95
|
coordinate: coordinate
|
|
94
96
|
}));
|
|
95
97
|
this.plotData.push(...data);
|
|
96
|
-
if (index < this.lines.length - 1)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
if (index < this.lines.length - 1) {
|
|
99
|
+
// insert a gap between lines
|
|
100
|
+
this.gapPositions.push(this.plotData.length);
|
|
101
|
+
this.plotData.push({
|
|
102
|
+
x: line[line.length - 1][3],
|
|
103
|
+
y: NaN,
|
|
104
|
+
coordinate: []
|
|
105
|
+
});
|
|
106
|
+
}
|
|
101
107
|
});
|
|
102
108
|
this.scaleX.domain((0, $agntW$extent)(this.plotData, (data)=>data.x));
|
|
103
109
|
this.scaleY.domain((0, $agntW$extent)(this.plotData, (data)=>data.y));
|
|
@@ -111,8 +117,8 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
|
|
|
111
117
|
coordinate: point
|
|
112
118
|
});
|
|
113
119
|
}
|
|
114
|
-
if (changedProperties.has('lineSegments')) this.lineSegmentsData = $916babf1e6dc2c08$var$fillUnspecified(this.lineSegments || [], this.plotData.length);
|
|
115
|
-
if (changedProperties.has('xAxisSegments')) this.xAxisSegmentsData = $916babf1e6dc2c08$var$fillUnspecified(this.xAxisSegments || [], this.plotData.length);
|
|
120
|
+
if (changedProperties.has('lineSegments')) this.lineSegmentsData = $916babf1e6dc2c08$var$fillUnspecified(this.lineSegments || [], this.plotData.length, this.gapPositions);
|
|
121
|
+
if (changedProperties.has('xAxisSegments')) this.xAxisSegmentsData = $916babf1e6dc2c08$var$fillUnspecified(this.xAxisSegments || [], this.plotData.length, this.gapPositions);
|
|
116
122
|
}
|
|
117
123
|
render() {
|
|
118
124
|
// FIXME: better handling of null this.resizeController.value
|
|
@@ -346,30 +352,52 @@ $916babf1e6dc2c08$var$ElevationProfile = $916babf1e6dc2c08$var$__decorate([
|
|
|
346
352
|
(0, $agntW$customElement)('elevation-profile')
|
|
347
353
|
], $916babf1e6dc2c08$var$ElevationProfile);
|
|
348
354
|
var $916babf1e6dc2c08$export$2e2bcd8739ae039 = $916babf1e6dc2c08$var$ElevationProfile;
|
|
349
|
-
function $916babf1e6dc2c08$var$fillUnspecified(segment, length) {
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
// * shift the indices accordingly
|
|
355
|
+
function $916babf1e6dc2c08$var$fillUnspecified(segment, length, gapPositions = []) {
|
|
356
|
+
// Create a set of gap positions for quick lookup
|
|
357
|
+
const gapSet = new Set(gapPositions);
|
|
353
358
|
const filledSegments = [];
|
|
354
359
|
let currentIndex = 0;
|
|
355
360
|
for (const [start, end, value] of segment){
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
361
|
+
// Fill gap from currentIndex to start, skipping gap positions
|
|
362
|
+
if (start > currentIndex) {
|
|
363
|
+
let fillStart = currentIndex;
|
|
364
|
+
// Skip leading gaps
|
|
365
|
+
while(fillStart < start && gapSet.has(fillStart))fillStart++;
|
|
366
|
+
if (fillStart < start) {
|
|
367
|
+
// Find end position before start, avoiding gaps
|
|
368
|
+
let fillEnd = start - 1;
|
|
369
|
+
while(fillEnd >= fillStart && gapSet.has(fillEnd))fillEnd--;
|
|
370
|
+
if (fillEnd >= fillStart) filledSegments.push([
|
|
371
|
+
fillStart,
|
|
372
|
+
fillEnd,
|
|
373
|
+
null
|
|
374
|
+
]);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Add the segment, skipping if it points to a gap
|
|
378
|
+
if (!gapSet.has(start) && !gapSet.has(end)) filledSegments.push([
|
|
362
379
|
start,
|
|
363
380
|
end,
|
|
364
381
|
value
|
|
365
382
|
]);
|
|
366
|
-
currentIndex = end;
|
|
383
|
+
currentIndex = end + 1;
|
|
384
|
+
}
|
|
385
|
+
// Fill remaining range from currentIndex to length, skipping gaps
|
|
386
|
+
if (currentIndex < length) {
|
|
387
|
+
let fillStart = currentIndex;
|
|
388
|
+
// Skip leading gaps
|
|
389
|
+
while(fillStart < length && gapSet.has(fillStart))fillStart++;
|
|
390
|
+
if (fillStart < length) {
|
|
391
|
+
// Find last valid index
|
|
392
|
+
let fillEnd = length - 1;
|
|
393
|
+
while(fillEnd >= fillStart && gapSet.has(fillEnd))fillEnd--;
|
|
394
|
+
if (fillEnd >= fillStart) filledSegments.push([
|
|
395
|
+
fillStart,
|
|
396
|
+
fillEnd,
|
|
397
|
+
null
|
|
398
|
+
]);
|
|
399
|
+
}
|
|
367
400
|
}
|
|
368
|
-
if (currentIndex < length - 1) filledSegments.push([
|
|
369
|
-
currentIndex,
|
|
370
|
-
length - 1,
|
|
371
|
-
null
|
|
372
|
-
]);
|
|
373
401
|
return filledSegments;
|
|
374
402
|
}
|
|
375
403
|
function $916babf1e6dc2c08$var$getSegmentValueAtIndex(segments, index) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"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","sources":["elevation-profile.ts"],"sourcesContent":["import {LitElement, svg} from 'lit';\nimport {customElement, state, property} from 'lit/decorators.js';\nimport {ResizeController} from '@lit-labs/observers/resize-controller.js';\nimport {guard} from 'lit/directives/guard.js';\nimport {ifDefined} from 'lit/directives/if-defined.js';\nimport type {PropertyValues, TemplateResult} from 'lit';\n\nimport {extent, bisector} from 'd3-array';\nimport {scaleLinear} from 'd3-scale';\nimport {line, area} from 'd3-shape';\nimport {axisBottom, axisLeft} from 'd3-axis';\nimport {select, pointer} from 'd3-selection';\n\nimport simplify from './simplify.js';\n\ntype PlotPoint = {\n x: number;\n y: number;\n coordinate: number[];\n};\n\nexport type SegmentData = Array<[number, number, string | null]>;\n\nexport type OverDetails = {\n coordinate: number[];\n position: {x: number; y: number};\n segments?: {\n line: string | null;\n xAxis: string | null;\n };\n};\n\n@customElement('elevation-profile')\nexport default class ElevationProfile extends LitElement {\n @property({type: Number}) tolerance = 1;\n @property({type: String}) locale = navigator.language;\n @property({type: Array}) lines: number[][][] = [];\n @property({type: Array}) points: number[][] = [];\n @property() updateScale = (x: scaleLinear, y: scaleLinear, width: number, height: number): void => {};\n @property({type: Object}) margin = {top: 20, right: 20, bottom: 20, left: 20};\n @property({type: Object}) tickSize = {x: 100, y: 40};\n @property({type: Boolean}) pointerEvents = true;\n @property({type: Array}) lineSegments?: SegmentData;\n @property({type: Array}) xAxisSegments?: SegmentData;\n private yAxisObserver: ResizeObserver | null = null;\n\n @state() pointer = {x: 0, y: 0};\n private resizeController = new ResizeController(this, {\n callback: () => [this.offsetWidth, this.offsetHeight],\n });\n\n private plotData: PlotPoint[] = [];\n private pointsData: PlotPoint[] = [];\n private lineSegmentsData: SegmentData = [];\n private xAxisSegmentsData: SegmentData = [];\n private scaleX = scaleLinear();\n private scaleY = scaleLinear();\n\n private bisectDistance = bisector((point: PlotPoint) => point.x);\n\n private line = line()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y((point: PlotPoint) => this.scaleY(point.y));\n private area = area()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y1((point: PlotPoint) => this.scaleY(point.y));\n private xAxis = axisBottom(this.scaleX).tickFormat((value: number) => this.tickFormat(value, 'x'));\n private yAxis = axisLeft(this.scaleY).tickFormat((value: number) => this.tickFormat(value, 'y'));\n private xGrid = axisBottom(this.scaleX).tickFormat(() => '');\n private yGrid = axisLeft(this.scaleY).tickFormat(() => '');\n\n private meterFormat: Intl.NumberFormat | null = null;\n private kilometerFormat: Intl.NumberFormat | null = null;\n\n override updated(changedProperties: PropertyValues) {\n if (changedProperties.has('locale')) {\n this.meterFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'meter',\n });\n\n this.kilometerFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'kilometer',\n });\n }\n }\n\n override willUpdate(changedProperties: PropertyValues) {\n if (changedProperties.has('lines')) {\n this.plotData.length = 0;\n this.lines.forEach((line, index) => {\n const data = line.map((coordinate) => ({x: coordinate[3], y: coordinate[2], coordinate}));\n this.plotData.push(...data);\n if (index < this.lines.length - 1) {\n this.plotData.push({x: line[line.length - 1][3], y: NaN, coordinate: []});\n }\n });\n\n this.scaleX.domain(extent(this.plotData, (data: PlotPoint) => data.x));\n this.scaleY.domain(extent(this.plotData, (data: PlotPoint) => data.y));\n\n this.updateScale(this.scaleX, this.scaleY, this.offsetWidth, this.offsetHeight);\n }\n if (changedProperties.has('points')) {\n this.pointsData.length = 0;\n for (const point of this.points) {\n this.pointsData.push({x: point[3], y: point[2], coordinate: point});\n }\n }\n if (changedProperties.has('lineSegments')) {\n this.lineSegmentsData = fillUnspecified(this.lineSegments || [], this.plotData.length);\n }\n if (changedProperties.has('xAxisSegments')) {\n this.xAxisSegmentsData = fillUnspecified(this.xAxisSegments || [], this.plotData.length);\n }\n }\n\n override render() {\n // FIXME: better handling of null this.resizeController.value\n const [width, height] = this.resizeController.value ?? [this.margin.left + this.margin.right, this.margin.top + this.margin.bottom];\n const ml = (this.querySelector('.axis.y')?.getBoundingClientRect().width || 0) + this.margin.left\n\n this.scaleX.range([ml, width - this.margin.right]);\n this.scaleY.range([height - this.margin.bottom, this.margin.top]);\n\n this.area.y0(height - this.margin.bottom);\n\n this.yGrid.tickSize(-width + ml + this.margin.right);\n this.xGrid.tickSize(height - this.margin.top - this.margin.bottom);\n\n const xTicks = width / this.tickSize.x;\n const yTicks = height / this.tickSize.y;\n this.xAxis.ticks(xTicks);\n this.xGrid.ticks(xTicks);\n this.yAxis.ticks(yTicks);\n this.yGrid.ticks(yTicks);\n\n select(this.querySelector('.axis.x')).call(this.xAxis);\n select(this.querySelector('.axis.y')).call(this.yAxis);\n select(this.querySelector('.grid.x')).call(this.xGrid);\n select(this.querySelector('.grid.y')).call(this.yGrid);\n\n const offset = this.yGrid.offset();\n\n return svg`\n <svg width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" xmlns=\"http://www.w3.org/2000/svg\">\n <g class=\"grid y\" transform=\"translate(${ml}, 0)\" />\n <g class=\"grid x\" transform=\"translate(0, ${this.margin.bottom})\" />\n <g class=\"axis x\" transform=\"translate(0, ${height - this.margin.bottom})\" />\n <g class=\"axis y\" transform=\"translate(${ml}, 0)\" />\n\n ${guard([this.lines, width, height, ml], () => svg`\n <path class=\"area\" d=\"${this.area(this.plotData)}\" />\n ${this.renderLineSegments('elevation')}`\n )}\n\n <g style=\"visibility: ${this.pointer.x > 0 ? 'visible' : 'hidden'}\">\n <g clip-path=\"polygon(0 0, ${this.pointer.x - ml} 0, ${this.pointer.x - ml} 100%, 0 100%)\">\n ${guard([this.lines, width, height, ml], () => this.renderLineSegments('elevation highlight'))}\n </g>\n <line\n class=\"pointer-line x\"\n x1=\"${this.pointer.x}\"\n y1=\"${this.margin.top}\"\n x2=\"${this.pointer.x}\"\n y2=\"${height - this.margin.bottom}\"\n />\n <line\n class=\"pointer-line y\"\n x1=\"${ml}\"\n y1=\"${this.pointer.y}\"\n x2=\"${width - this.margin.right}\"\n y2=\"${this.pointer.y}\"\n />\n <circle class=\"pointer-circle-outline\" cx=\"${this.pointer.x}\" cy=\"${this.pointer.y}\" r=\"16\"/>\n <circle class=\"pointer-circle\" cx=\"${this.pointer.x}\" cy=\"${this.pointer.y}\" r=\"6\"/>\n </g>\n\n ${this.pointsData.map((point, index) => this.pointSvg(this.scaleX(point.x), this.scaleY(point.y), index))}\n\n <rect\n width=\"${width}\"\n height=\"${height}\"\n fill=\"none\"\n pointer-events=\"${this.pointerEvents ? 'all' : 'none'}\"\n style=\"display: block; touch-action: none;\"\n @pointermove=\"${this.pointerMove}\"\n @pointerout=\"${this.pointerOut}\"\n />\n <g\n transform=\"translate(${ml},${height - this.margin.bottom + offset})\"\n class=\"axis\"\n style=\"visibility: ${this.lines.length ? 'visible' : 'hidden'}\">\n <line x2=\"${width - ml - this.margin.right}\"></line>\n </g>\n <g transform=\"translate(0,${height - this.margin.bottom + offset})\">\n ${this.renderTrailBands()}\n </g>\n </svg>\n `;\n }\n\n private renderLineSegments(className: string) {\n return this.lineSegmentsData.map(([start, end, value]) => {\n const segmentData = this.plotData.slice(start, end + 1);\n console.assert(segmentData.length >= 2);\n return svg`<path class=\"${className}\" data-value=\"${ifDefined(value)}\" d=\"${this.line(segmentData)}\" fill=\"none\" />`;\n });\n }\n\n private renderTrailBands() {\n return this.xAxisSegmentsData.map(([start, end, value]) => {\n const x1 = this.scaleX(this.plotData[start].x);\n const x2 = this.scaleX(this.plotData[end].x);\n console.assert(this.plotData[start].x < this.plotData[end].x);\n const bandWidth = x2 - x1;\n return svg`<rect class=\"trail-band\" data-value=\"${ifDefined(value)}\" x=\"${x1}\" width=\"${bandWidth}\" />`;\n });\n }\n\n public tickFormat(value: number, axis: 'x' | 'y') {\n if (axis === 'y' || value < 1000) {\n return this.meterFormat!.format(value);\n } else {\n return this.kilometerFormat!.format(value / 1000);\n }\n }\n\n public tickValues(values: number[], axis: 'x' | 'y') {\n if (values.length === 0 || (axis !== 'x' && axis !== 'y')) {\n return;\n }\n axis === 'x' ? this.xAxis.tickValues(values) : this.yAxis.tickValues(values);\n }\n\n public pointSvg(x: number, y: number, index: number): TemplateResult {\n return svg`<circle class=\"point\" cx=\"${x}\" cy=\"${y}\" r=\"10\"/>`;\n }\n\n override firstUpdated() {\n const axisY = this.querySelector('.axis.y');\n if (axisY) {\n this.yAxisObserver = new ResizeObserver(() => {\n this.requestUpdate()\n })\n this.yAxisObserver.observe(axisY);\n }\n // FIXME: because the ref element are used before render is done, we need to force an update\n this.requestUpdate();\n }\n\n override disconnectedCallback() {\n if (this.yAxisObserver) {\n this.yAxisObserver.disconnect();\n }\n super.disconnectedCallback();\n }\n\n private pointerMove(event: PointerEvent) {\n const pointerDistance = this.scaleX.invert(pointer(event)[0]);\n const index = Math.min(this.bisectDistance.left(this.plotData, pointerDistance), this.plotData.length - 1);\n\n if (index < 0) {\n return;\n }\n // FIXME:\n // var d0 = this.plotData[index - 1]\n // var d1 = this.plotData[index];\n // // work out which date value is closest to the mouse\n // var d = mouseDate - d0[0] > d1[0] - mouseDate ? d1 : d0;\n\n const data = this.plotData[index];\n\n if (isNaN(data.y)) {\n return;\n }\n\n this.pointer = {\n x: this.scaleX(data.x),\n y: this.scaleY(data.y),\n };\n\n const segments: OverDetails['segments'] = {\n line: getSegmentValueAtIndex(this.lineSegmentsData, index),\n xAxis: getSegmentValueAtIndex(this.xAxisSegmentsData, index),\n };\n\n this.dispatchEvent(\n new CustomEvent<OverDetails>('over', {\n detail: {\n coordinate: this.plotData[index].coordinate,\n position: this.pointer,\n ...(Object.keys(segments).length > 0 && { segments })\n }\n }),\n );\n }\n\n private pointerOut() {\n this.pointer = {\n x: 0,\n y: 0,\n };\n this.dispatchEvent(new CustomEvent('out'));\n }\n\n override createRenderRoot() {\n return this;\n }\n}\n\nfunction fillUnspecified(segment: SegmentData, length: number): SegmentData {\n // FIXME: we must take into account the NaN in the plotData:\n // * insert null segments where needed\n // * shift the indices accordingly\n const filledSegments: SegmentData = [];\n let currentIndex = 0;\n\n for (const [start, end, value] of segment) {\n if (start > currentIndex) {\n filledSegments.push([currentIndex, start, null]);\n }\n filledSegments.push([start, end, value]);\n currentIndex = end;\n }\n\n if (currentIndex < length - 1) {\n filledSegments.push([currentIndex, length - 1, null]);\n }\n\n return filledSegments;\n}\n\nfunction getSegmentValueAtIndex(segments: SegmentData, index: number): string | null {\n for (const [start, end, value] of segments) {\n if (index >= start && index < end) {\n return value;\n }\n }\n // we must never be here because of fillUnspecified\n return null;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'elevation-profile': ElevationProfile;\n }\n}\n"],"names":[],"version":3,"file":"elevation-profile.js.map"}
|
|
1
|
+
{"mappings":"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","sources":["elevation-profile.ts"],"sourcesContent":["import {LitElement, svg} from 'lit';\nimport {customElement, state, property} from 'lit/decorators.js';\nimport {ResizeController} from '@lit-labs/observers/resize-controller.js';\nimport {guard} from 'lit/directives/guard.js';\nimport {ifDefined} from 'lit/directives/if-defined.js';\nimport type {PropertyValues, TemplateResult} from 'lit';\n\nimport {extent, bisector} from 'd3-array';\nimport {scaleLinear} from 'd3-scale';\nimport {line, area} from 'd3-shape';\nimport {axisBottom, axisLeft} from 'd3-axis';\nimport {select, pointer} from 'd3-selection';\n\n// FIXME: use simplify to reduce number of points based on tolerance\nimport simplify from './simplify.js';\n\ntype PlotPoint = {\n x: number;\n y: number;\n coordinate: number[];\n};\n\nexport type SegmentData = Array<[number, number, string | null]>;\n\nexport type OverDetails = {\n coordinate: number[];\n position: {x: number; y: number};\n segments?: {\n line: string | null;\n xAxis: string | null;\n };\n};\n\n@customElement('elevation-profile')\nexport default class ElevationProfile extends LitElement {\n @property({type: Number}) tolerance = 1;\n @property({type: String}) locale = navigator.language;\n @property({type: Array}) lines: number[][][] = [];\n @property({type: Array}) points: number[][] = [];\n @property() updateScale = (x: scaleLinear, y: scaleLinear, width: number, height: number): void => {};\n @property({type: Object}) margin = {top: 20, right: 20, bottom: 20, left: 20};\n @property({type: Object}) tickSize = {x: 100, y: 40};\n @property({type: Boolean}) pointerEvents = true;\n @property({type: Array}) lineSegments?: SegmentData;\n @property({type: Array}) xAxisSegments?: SegmentData;\n private yAxisObserver: ResizeObserver | null = null;\n\n @state() pointer = {x: 0, y: 0};\n private resizeController = new ResizeController(this, {\n callback: () => [this.offsetWidth, this.offsetHeight],\n });\n\n private plotData: PlotPoint[] = [];\n private pointsData: PlotPoint[] = [];\n private lineSegmentsData: SegmentData = [];\n private xAxisSegmentsData: SegmentData = [];\n private gapPositions: number[] = [];\n private scaleX = scaleLinear();\n private scaleY = scaleLinear();\n\n private bisectDistance = bisector((point: PlotPoint) => point.x);\n\n private line = line()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y((point: PlotPoint) => this.scaleY(point.y));\n private area = area()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y1((point: PlotPoint) => this.scaleY(point.y));\n private xAxis = axisBottom(this.scaleX).tickFormat((value: number) => this.tickFormat(value, 'x'));\n private yAxis = axisLeft(this.scaleY).tickFormat((value: number) => this.tickFormat(value, 'y'));\n private xGrid = axisBottom(this.scaleX).tickFormat(() => '');\n private yGrid = axisLeft(this.scaleY).tickFormat(() => '');\n\n private meterFormat: Intl.NumberFormat | null = null;\n private kilometerFormat: Intl.NumberFormat | null = null;\n\n override updated(changedProperties: PropertyValues) {\n if (changedProperties.has('locale')) {\n this.meterFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'meter',\n });\n\n this.kilometerFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'kilometer',\n });\n }\n }\n\n override willUpdate(changedProperties: PropertyValues) {\n if (changedProperties.has('lines')) {\n this.plotData.length = 0;\n this.gapPositions.length = 0;\n this.lines.forEach((line, index) => {\n const data = line.map((coordinate) => ({x: coordinate[3], y: coordinate[2], coordinate}));\n this.plotData.push(...data);\n if (index < this.lines.length - 1) {\n // insert a gap between lines\n this.gapPositions.push(this.plotData.length);\n this.plotData.push({x: line[line.length - 1][3], y: NaN, coordinate: []});\n }\n });\n\n this.scaleX.domain(extent(this.plotData, (data: PlotPoint) => data.x));\n this.scaleY.domain(extent(this.plotData, (data: PlotPoint) => data.y));\n\n this.updateScale(this.scaleX, this.scaleY, this.offsetWidth, this.offsetHeight);\n }\n if (changedProperties.has('points')) {\n this.pointsData.length = 0;\n for (const point of this.points) {\n this.pointsData.push({x: point[3], y: point[2], coordinate: point});\n }\n }\n if (changedProperties.has('lineSegments')) {\n this.lineSegmentsData = fillUnspecified(this.lineSegments || [], this.plotData.length, this.gapPositions);\n }\n if (changedProperties.has('xAxisSegments')) {\n this.xAxisSegmentsData = fillUnspecified(this.xAxisSegments || [], this.plotData.length, this.gapPositions);\n }\n }\n\n override render() {\n // FIXME: better handling of null this.resizeController.value\n const [width, height] = this.resizeController.value ?? [this.margin.left + this.margin.right, this.margin.top + this.margin.bottom];\n const ml = (this.querySelector('.axis.y')?.getBoundingClientRect().width || 0) + this.margin.left\n\n this.scaleX.range([ml, width - this.margin.right]);\n this.scaleY.range([height - this.margin.bottom, this.margin.top]);\n\n this.area.y0(height - this.margin.bottom);\n\n this.yGrid.tickSize(-width + ml + this.margin.right);\n this.xGrid.tickSize(height - this.margin.top - this.margin.bottom);\n\n const xTicks = width / this.tickSize.x;\n const yTicks = height / this.tickSize.y;\n this.xAxis.ticks(xTicks);\n this.xGrid.ticks(xTicks);\n this.yAxis.ticks(yTicks);\n this.yGrid.ticks(yTicks);\n\n select(this.querySelector('.axis.x')).call(this.xAxis);\n select(this.querySelector('.axis.y')).call(this.yAxis);\n select(this.querySelector('.grid.x')).call(this.xGrid);\n select(this.querySelector('.grid.y')).call(this.yGrid);\n\n const offset = this.yGrid.offset();\n\n return svg`\n <svg width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\" xmlns=\"http://www.w3.org/2000/svg\">\n <g class=\"grid y\" transform=\"translate(${ml}, 0)\" />\n <g class=\"grid x\" transform=\"translate(0, ${this.margin.bottom})\" />\n <g class=\"axis x\" transform=\"translate(0, ${height - this.margin.bottom})\" />\n <g class=\"axis y\" transform=\"translate(${ml}, 0)\" />\n\n ${guard([this.lines, width, height, ml], () => svg`\n <path class=\"area\" d=\"${this.area(this.plotData)}\" />\n ${this.renderLineSegments('elevation')}`\n )}\n\n <g style=\"visibility: ${this.pointer.x > 0 ? 'visible' : 'hidden'}\">\n <g clip-path=\"polygon(0 0, ${this.pointer.x - ml} 0, ${this.pointer.x - ml} 100%, 0 100%)\">\n ${guard([this.lines, width, height, ml], () => this.renderLineSegments('elevation highlight'))}\n </g>\n <line\n class=\"pointer-line x\"\n x1=\"${this.pointer.x}\"\n y1=\"${this.margin.top}\"\n x2=\"${this.pointer.x}\"\n y2=\"${height - this.margin.bottom}\"\n />\n <line\n class=\"pointer-line y\"\n x1=\"${ml}\"\n y1=\"${this.pointer.y}\"\n x2=\"${width - this.margin.right}\"\n y2=\"${this.pointer.y}\"\n />\n <circle class=\"pointer-circle-outline\" cx=\"${this.pointer.x}\" cy=\"${this.pointer.y}\" r=\"16\"/>\n <circle class=\"pointer-circle\" cx=\"${this.pointer.x}\" cy=\"${this.pointer.y}\" r=\"6\"/>\n </g>\n\n ${this.pointsData.map((point, index) => this.pointSvg(this.scaleX(point.x), this.scaleY(point.y), index))}\n\n <rect\n width=\"${width}\"\n height=\"${height}\"\n fill=\"none\"\n pointer-events=\"${this.pointerEvents ? 'all' : 'none'}\"\n style=\"display: block; touch-action: none;\"\n @pointermove=\"${this.pointerMove}\"\n @pointerout=\"${this.pointerOut}\"\n />\n <g\n transform=\"translate(${ml},${height - this.margin.bottom + offset})\"\n class=\"axis\"\n style=\"visibility: ${this.lines.length ? 'visible' : 'hidden'}\">\n <line x2=\"${width - ml - this.margin.right}\"></line>\n </g>\n <g transform=\"translate(0,${height - this.margin.bottom + offset})\">\n ${this.renderTrailBands()}\n </g>\n </svg>\n `;\n }\n\n private renderLineSegments(className: string) {\n return this.lineSegmentsData.map(([start, end, value]) => {\n const segmentData = this.plotData.slice(start, end + 1);\n console.assert(segmentData.length >= 2);\n return svg`<path class=\"${className}\" data-value=\"${ifDefined(value)}\" d=\"${this.line(segmentData)}\" fill=\"none\" />`;\n });\n }\n\n private renderTrailBands() {\n return this.xAxisSegmentsData.map(([start, end, value]) => {\n const x1 = this.scaleX(this.plotData[start].x);\n const x2 = this.scaleX(this.plotData[end].x);\n console.assert(this.plotData[start].x < this.plotData[end].x);\n const bandWidth = x2 - x1;\n return svg`<rect class=\"trail-band\" data-value=\"${ifDefined(value)}\" x=\"${x1}\" width=\"${bandWidth}\" />`;\n });\n }\n\n public tickFormat(value: number, axis: 'x' | 'y') {\n if (axis === 'y' || value < 1000) {\n return this.meterFormat!.format(value);\n } else {\n return this.kilometerFormat!.format(value / 1000);\n }\n }\n\n public tickValues(values: number[], axis: 'x' | 'y') {\n if (values.length === 0 || (axis !== 'x' && axis !== 'y')) {\n return;\n }\n axis === 'x' ? this.xAxis.tickValues(values) : this.yAxis.tickValues(values);\n }\n\n public pointSvg(x: number, y: number, index: number): TemplateResult {\n return svg`<circle class=\"point\" cx=\"${x}\" cy=\"${y}\" r=\"10\"/>`;\n }\n\n override firstUpdated() {\n const axisY = this.querySelector('.axis.y');\n if (axisY) {\n this.yAxisObserver = new ResizeObserver(() => {\n this.requestUpdate()\n })\n this.yAxisObserver.observe(axisY);\n }\n // FIXME: because the ref element are used before render is done, we need to force an update\n this.requestUpdate();\n }\n\n override disconnectedCallback() {\n if (this.yAxisObserver) {\n this.yAxisObserver.disconnect();\n }\n super.disconnectedCallback();\n }\n\n private pointerMove(event: PointerEvent) {\n const pointerDistance = this.scaleX.invert(pointer(event)[0]);\n const index = Math.min(this.bisectDistance.left(this.plotData, pointerDistance), this.plotData.length - 1);\n\n if (index < 0) {\n return;\n }\n // FIXME:\n // var d0 = this.plotData[index - 1]\n // var d1 = this.plotData[index];\n // // work out which date value is closest to the mouse\n // var d = mouseDate - d0[0] > d1[0] - mouseDate ? d1 : d0;\n\n const data = this.plotData[index];\n\n if (isNaN(data.y)) {\n return;\n }\n\n this.pointer = {\n x: this.scaleX(data.x),\n y: this.scaleY(data.y),\n };\n\n const segments: OverDetails['segments'] = {\n line: getSegmentValueAtIndex(this.lineSegmentsData, index),\n xAxis: getSegmentValueAtIndex(this.xAxisSegmentsData, index),\n };\n\n this.dispatchEvent(\n new CustomEvent<OverDetails>('over', {\n detail: {\n coordinate: this.plotData[index].coordinate,\n position: this.pointer,\n ...(Object.keys(segments).length > 0 && { segments })\n }\n }),\n );\n }\n\n private pointerOut() {\n this.pointer = {\n x: 0,\n y: 0,\n };\n this.dispatchEvent(new CustomEvent('out'));\n }\n\n override createRenderRoot() {\n return this;\n }\n}\n\nfunction fillUnspecified(segment: SegmentData, length: number, gapPositions: number[] = []): SegmentData {\n // Create a set of gap positions for quick lookup\n const gapSet = new Set(gapPositions);\n \n const filledSegments: SegmentData = [];\n let currentIndex = 0;\n\n for (const [start, end, value] of segment) {\n // Fill gap from currentIndex to start, skipping gap positions\n if (start > currentIndex) {\n let fillStart = currentIndex;\n \n // Skip leading gaps\n while (fillStart < start && gapSet.has(fillStart)) {\n fillStart++;\n }\n \n if (fillStart < start) {\n // Find end position before start, avoiding gaps\n let fillEnd = start - 1;\n while (fillEnd >= fillStart && gapSet.has(fillEnd)) {\n fillEnd--;\n }\n \n if (fillEnd >= fillStart) {\n filledSegments.push([fillStart, fillEnd, null]);\n }\n }\n }\n \n // Add the segment, skipping if it points to a gap\n if (!gapSet.has(start) && !gapSet.has(end)) {\n filledSegments.push([start, end, value]);\n }\n \n currentIndex = end + 1;\n }\n\n // Fill remaining range from currentIndex to length, skipping gaps\n if (currentIndex < length) {\n let fillStart = currentIndex;\n \n // Skip leading gaps\n while (fillStart < length && gapSet.has(fillStart)) {\n fillStart++;\n }\n \n if (fillStart < length) {\n // Find last valid index\n let fillEnd = length - 1;\n while (fillEnd >= fillStart && gapSet.has(fillEnd)) {\n fillEnd--;\n }\n \n if (fillEnd >= fillStart) {\n filledSegments.push([fillStart, fillEnd, null]);\n }\n }\n }\n\n return filledSegments;\n}\n\nfunction getSegmentValueAtIndex(segments: SegmentData, index: number): string | null {\n for (const [start, end, value] of segments) {\n if (index >= start && index < end) {\n return value;\n }\n }\n // we must never be here because of fillUnspecified\n return null;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'elevation-profile': ElevationProfile;\n }\n}\n"],"names":[],"version":3,"file":"elevation-profile.js.map"}
|
package/elevation-profile.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {line, area} from 'd3-shape';
|
|
|
11
11
|
import {axisBottom, axisLeft} from 'd3-axis';
|
|
12
12
|
import {select, pointer} from 'd3-selection';
|
|
13
13
|
|
|
14
|
+
// FIXME: use simplify to reduce number of points based on tolerance
|
|
14
15
|
import simplify from './simplify.js';
|
|
15
16
|
|
|
16
17
|
type PlotPoint = {
|
|
@@ -53,6 +54,7 @@ export default class ElevationProfile extends LitElement {
|
|
|
53
54
|
private pointsData: PlotPoint[] = [];
|
|
54
55
|
private lineSegmentsData: SegmentData = [];
|
|
55
56
|
private xAxisSegmentsData: SegmentData = [];
|
|
57
|
+
private gapPositions: number[] = [];
|
|
56
58
|
private scaleX = scaleLinear();
|
|
57
59
|
private scaleY = scaleLinear();
|
|
58
60
|
|
|
@@ -88,16 +90,19 @@ export default class ElevationProfile extends LitElement {
|
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
override willUpdate(changedProperties: PropertyValues) {
|
|
94
|
+
if (changedProperties.has('lines')) {
|
|
95
|
+
this.plotData.length = 0;
|
|
96
|
+
this.gapPositions.length = 0;
|
|
97
|
+
this.lines.forEach((line, index) => {
|
|
98
|
+
const data = line.map((coordinate) => ({x: coordinate[3], y: coordinate[2], coordinate}));
|
|
99
|
+
this.plotData.push(...data);
|
|
100
|
+
if (index < this.lines.length - 1) {
|
|
101
|
+
// insert a gap between lines
|
|
102
|
+
this.gapPositions.push(this.plotData.length);
|
|
103
|
+
this.plotData.push({x: line[line.length - 1][3], y: NaN, coordinate: []});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
101
106
|
|
|
102
107
|
this.scaleX.domain(extent(this.plotData, (data: PlotPoint) => data.x));
|
|
103
108
|
this.scaleY.domain(extent(this.plotData, (data: PlotPoint) => data.y));
|
|
@@ -110,12 +115,12 @@ export default class ElevationProfile extends LitElement {
|
|
|
110
115
|
this.pointsData.push({x: point[3], y: point[2], coordinate: point});
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
if (changedProperties.has('lineSegments')) {
|
|
119
|
+
this.lineSegmentsData = fillUnspecified(this.lineSegments || [], this.plotData.length, this.gapPositions);
|
|
120
|
+
}
|
|
121
|
+
if (changedProperties.has('xAxisSegments')) {
|
|
122
|
+
this.xAxisSegmentsData = fillUnspecified(this.xAxisSegments || [], this.plotData.length, this.gapPositions);
|
|
123
|
+
}
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
override render() {
|
|
@@ -211,15 +216,15 @@ export default class ElevationProfile extends LitElement {
|
|
|
211
216
|
});
|
|
212
217
|
}
|
|
213
218
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
private renderTrailBands() {
|
|
220
|
+
return this.xAxisSegmentsData.map(([start, end, value]) => {
|
|
221
|
+
const x1 = this.scaleX(this.plotData[start].x);
|
|
222
|
+
const x2 = this.scaleX(this.plotData[end].x);
|
|
223
|
+
console.assert(this.plotData[start].x < this.plotData[end].x);
|
|
224
|
+
const bandWidth = x2 - x1;
|
|
225
|
+
return svg`<rect class="trail-band" data-value="${ifDefined(value)}" x="${x1}" width="${bandWidth}" />`;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
223
228
|
|
|
224
229
|
public tickFormat(value: number, axis: 'x' | 'y') {
|
|
225
230
|
if (axis === 'y' || value < 1000) {
|
|
@@ -312,23 +317,64 @@ export default class ElevationProfile extends LitElement {
|
|
|
312
317
|
}
|
|
313
318
|
}
|
|
314
319
|
|
|
315
|
-
function fillUnspecified(segment: SegmentData, length: number): SegmentData {
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
320
|
+
function fillUnspecified(segment: SegmentData, length: number, gapPositions: number[] = []): SegmentData {
|
|
321
|
+
// Create a set of gap positions for quick lookup
|
|
322
|
+
const gapSet = new Set(gapPositions);
|
|
323
|
+
|
|
319
324
|
const filledSegments: SegmentData = [];
|
|
320
325
|
let currentIndex = 0;
|
|
321
326
|
|
|
322
327
|
for (const [start, end, value] of segment) {
|
|
328
|
+
// Fill gap from currentIndex to start, skipping gap positions
|
|
323
329
|
if (start > currentIndex) {
|
|
324
|
-
|
|
330
|
+
let fillStart = currentIndex;
|
|
331
|
+
|
|
332
|
+
// Skip leading gaps
|
|
333
|
+
while (fillStart < start && gapSet.has(fillStart)) {
|
|
334
|
+
fillStart++;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (fillStart < start) {
|
|
338
|
+
// Find end position before start, avoiding gaps
|
|
339
|
+
let fillEnd = start - 1;
|
|
340
|
+
while (fillEnd >= fillStart && gapSet.has(fillEnd)) {
|
|
341
|
+
fillEnd--;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (fillEnd >= fillStart) {
|
|
345
|
+
filledSegments.push([fillStart, fillEnd, null]);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Add the segment, skipping if it points to a gap
|
|
351
|
+
if (!gapSet.has(start) && !gapSet.has(end)) {
|
|
352
|
+
filledSegments.push([start, end, value]);
|
|
325
353
|
}
|
|
326
|
-
|
|
327
|
-
currentIndex = end;
|
|
354
|
+
|
|
355
|
+
currentIndex = end + 1;
|
|
328
356
|
}
|
|
329
357
|
|
|
330
|
-
|
|
331
|
-
|
|
358
|
+
// Fill remaining range from currentIndex to length, skipping gaps
|
|
359
|
+
if (currentIndex < length) {
|
|
360
|
+
let fillStart = currentIndex;
|
|
361
|
+
|
|
362
|
+
// Skip leading gaps
|
|
363
|
+
while (fillStart < length && gapSet.has(fillStart)) {
|
|
364
|
+
fillStart++;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (fillStart < length) {
|
|
368
|
+
// Find last valid index
|
|
369
|
+
let fillEnd = length - 1;
|
|
370
|
+
while (fillEnd >= fillStart && gapSet.has(fillEnd)) {
|
|
371
|
+
fillEnd--;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (fillEnd >= fillStart) {
|
|
375
|
+
filledSegments.push([fillStart, fillEnd, null]);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
332
378
|
}
|
|
333
379
|
|
|
334
380
|
return filledSegments;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geoblocks/elevation-profile",
|
|
3
|
-
"version": "0.0.25-beta.
|
|
3
|
+
"version": "0.0.25-beta.7",
|
|
4
4
|
"license": "BSD-3-Clause",
|
|
5
5
|
"repository": "github:geoblocks/elevation-profile",
|
|
6
6
|
"type": "module",
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
"publish-demo": "rm -rf build && npm run build-demo && gh-pages -d build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@lit-labs/observers": "2.0
|
|
32
|
+
"@lit-labs/observers": "2.1.0",
|
|
33
33
|
"d3-array": "3.2.4",
|
|
34
34
|
"d3-axis": "3.0.0",
|
|
35
35
|
"d3-scale": "4.0.2",
|
|
36
36
|
"d3-selection": "3.0.0",
|
|
37
37
|
"d3-shape": "3.2.0",
|
|
38
|
-
"lit": "3.3.
|
|
38
|
+
"lit": "3.3.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@parcel/packager-ts": "2.16.3",
|