@geoblocks/elevation-profile 0.0.25 → 0.0.26

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 CHANGED
@@ -140,7 +140,7 @@ If `pointerEvents` is `true`, the component will emit the following custom event
140
140
 
141
141
  | Name | When | Detail type | Description
142
142
  | --------------- | ------------------------------------------- | ---------------------------------------------------------- | -----------
143
- | `over` | The pointer is over the profile | `{coordinate: number[], position: {x: number, y: number}, segments?: {line: string | null, xAxis: string | null}}` | `coordinate` is the coordinate of the point on the MultiLineString, `position` is the position of the pointer relative to the component, and `segments` contains the segment values at the pointer location
143
+ | `over` | The pointer is over the profile | `{coordinate: number[], position: {x: number, y: number}, segments?: {line: string \| null, xAxis: string \| null}}` | `coordinate` is the coordinate of the point on the MultiLineString, `position` is the position of the pointer relative to the component, and `segments` contains the segment values at the pointer location
144
144
  | `out` | The pointer leaves the profile | |
145
145
 
146
146
  ### Styling
@@ -1 +1 @@
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;IA4GR,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 // If no line segments are defined, render the entire line as a single path\n if (this.lineSegmentsData.length === 0 && this.plotData.length >= 2) {\n return svg`<path class=\"${className}\" d=\"${this.line(this.plotData)}\" fill=\"none\" />`;\n }\n \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"}
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;IA6GR,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 @click=\"${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 // If no line segments are defined, render the entire line as a single path\n if (this.lineSegmentsData.length === 0 && this.plotData.length >= 2) {\n return svg`<path class=\"${className}\" d=\"${this.line(this.plotData)}\" fill=\"none\" />`;\n }\n\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"}
@@ -201,6 +201,7 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
201
201
  pointer-events="${this.pointerEvents ? 'all' : 'none'}"
202
202
  style="display: block; touch-action: none;"
203
203
  @pointermove="${this.pointerMove}"
204
+ @click="${this.pointerMove}"
204
205
  @pointerout="${this.pointerOut}"
205
206
  />
206
207
  <g
@@ -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\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 // If no line segments are defined, render the entire line as a single path\n if (this.lineSegmentsData.length === 0 && this.plotData.length >= 2) {\n return svg`<path class=\"${className}\" d=\"${this.line(this.plotData)}\" fill=\"none\" />`;\n }\n \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"}
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 @click=\"${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 // If no line segments are defined, render the entire line as a single path\n if (this.lineSegmentsData.length === 0 && this.plotData.length >= 2) {\n return svg`<path class=\"${className}\" d=\"${this.line(this.plotData)}\" fill=\"none\" />`;\n }\n\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"}
@@ -193,6 +193,7 @@ export default class ElevationProfile extends LitElement {
193
193
  pointer-events="${this.pointerEvents ? 'all' : 'none'}"
194
194
  style="display: block; touch-action: none;"
195
195
  @pointermove="${this.pointerMove}"
196
+ @click="${this.pointerMove}"
196
197
  @pointerout="${this.pointerOut}"
197
198
  />
198
199
  <g
@@ -213,7 +214,7 @@ export default class ElevationProfile extends LitElement {
213
214
  if (this.lineSegmentsData.length === 0 && this.plotData.length >= 2) {
214
215
  return svg`<path class="${className}" d="${this.line(this.plotData)}" fill="none" />`;
215
216
  }
216
-
217
+
217
218
  return this.lineSegmentsData.map(([start, end, value]) => {
218
219
  const segmentData = this.plotData.slice(start, end + 1);
219
220
  console.assert(segmentData.length >= 2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoblocks/elevation-profile",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "license": "BSD-3-Clause",
5
5
  "repository": "github:geoblocks/elevation-profile",
6
6
  "type": "module",
@@ -38,11 +38,11 @@
38
38
  "lit": "3.3.2"
39
39
  },
40
40
  "devDependencies": {
41
- "@parcel/packager-ts": "2.16.3",
42
- "@parcel/transformer-typescript-tsc": "2.16.3",
43
- "@parcel/transformer-typescript-types": "2.16.3",
41
+ "@parcel/packager-ts": "2.16.4",
42
+ "@parcel/transformer-typescript-tsc": "2.16.4",
43
+ "@parcel/transformer-typescript-types": "2.16.4",
44
44
  "gh-pages": "6.3.0",
45
- "parcel": "2.16.3",
45
+ "parcel": "2.16.4",
46
46
  "typescript": "5.9.3"
47
47
  }
48
48
  }