@geoblocks/elevation-profile 0.0.24 → 0.0.25-beta.2

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
@@ -26,6 +26,9 @@ npm i --save @geoblocks/elevation-profile
26
26
  | --------------- | ---------------- | -------------------------------------------- | -----------
27
27
  | `lines` | `number[][][]` | | **required** MultiLineString coordinates
28
28
  | `points` | `number[][]` | | Points to be displayed on the profile
29
+ | `lineSegments` | `SegmentData` | `undefined` | Segments for the elevation line with different visual properties
30
+ | `xAxisSegments` | `SegmentData` | `undefined` | Segments for trail bands below the x-axis
31
+ | `areaSegments` | `SegmentData` | `undefined` | Segments for the area below the elevation line
29
32
  | `margin` | `Object` | `{top: 20, right: 20, bottom: 20, left: 40}` | Margin in pixels around the elevation profile
30
33
  | `pointerEvents` | `Boolean` | `true` | Whether to emit pointer events
31
34
  | `tickSize` | `Object` | `{x: 100, y: 40}` | Size of the ticks in pixels
@@ -66,13 +69,79 @@ profile.pointSvg = (x, y, index) => {
66
69
 
67
70
  Where `x` and `y` are the position in pixels and `index` is the index of the point in the `points` array.
68
71
 
72
+ ### Segments
73
+
74
+ Segments allow you to style different parts of the elevation profile based on properties like surface type, hiking category, etc.
75
+
76
+ **Type definition:**
77
+ ```typescript
78
+ type SegmentData = Array<[number, number, string | null]>;
79
+ // Format: [startIndex, endIndex, value]
80
+ // Use null as value to create "holes" (segments without styling)
81
+ ```
82
+
83
+ **Example usage:**
84
+ ```javascript
85
+ profile.lineSegments = [
86
+ [0, 175, "paved"],
87
+ [175, 195, "unpaved"],
88
+ [195, 196, "paved"],
89
+ [196, 213, "unpaved"]
90
+ ];
91
+
92
+ profile.xAxisSegments = [
93
+ [0, 5, "bergwanderweg"],
94
+ [5, 102, "other"],
95
+ [102, 113, "bergwanderweg"]
96
+ ];
97
+
98
+ // Example with holes (null values won't render)
99
+ profile.lineSegments = [
100
+ [0, 50, "paved"],
101
+ [50, 100, null], // No segment rendered for this range
102
+ [100, 150, "unpaved"]
103
+ ];
104
+ ```
105
+
106
+ **Styling segments:**
107
+
108
+ Segments are rendered with `data-value` attributes that you can style externally:
109
+
110
+ - **Line segments** (`lineSegments`): Multiple `<path>` elements with class `elevation` and `data-value` attribute
111
+ - **Trail bands** (`xAxisSegments`): Horizontal `<rect>` elements with class `trail-band` and `data-value` attribute
112
+
113
+ ```css
114
+ /* Style line segments */
115
+ .elevation {
116
+ /* style for segments without a specific value */
117
+ stroke: #000;
118
+ }
119
+ .elevation[data-value="paved"] {
120
+ stroke: #2ecc71;
121
+ }
122
+ .elevation[data-value="unpaved"] {
123
+ stroke: #e67e22;
124
+ stroke-dasharray: 5, 5;
125
+ }
126
+
127
+ /* Style trail bands */
128
+ .trail-band[data-value="bergwanderweg"] {
129
+ fill: #e74c3c;
130
+ }
131
+ .trail-band[data-value="other"] {
132
+ fill: #3498db;
133
+ }
134
+ ```
135
+
136
+ **Note:** Segment indices should match the input coordinate array (before simplification).
137
+
69
138
  ### Events
70
139
 
71
140
  If `pointerEvents` is `true`, the component will emit the following custom events on pointer interaction:
72
141
 
73
142
  | Name | When | Detail type | Description
74
143
  | --------------- | ------------------------------------------- | ---------------------------------------------------------- | -----------
75
- | `over` | The pointer is over the profile | `{coordinate: number[], position: {x: number, y: number}}` | `coordinate` is the coordinate of the point on the MultiLineString and `position` is the position of the pointer relative to the component
144
+ | `over` | The pointer is over the profile | `{coordinate: number[], position: {x: number, y: number}, segments?: {line?: string, xAxis?: string, area?: string}}` | `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
76
145
  | `out` | The pointer leaves the profile | |
77
146
 
78
147
  ### Styling
@@ -1,11 +1,16 @@
1
1
  import { LitElement, PropertyValues, TemplateResult } from "lit";
2
2
  import { scaleLinear } from "d3-scale";
3
+ export type SegmentData = Array<[number, number, string | null]>;
3
4
  export type OverDetails = {
4
5
  coordinate: number[];
5
6
  position: {
6
7
  x: number;
7
8
  y: number;
8
9
  };
10
+ segments?: {
11
+ line: string | null;
12
+ xAxis: string | null;
13
+ };
9
14
  };
10
15
  export default class ElevationProfile extends LitElement {
11
16
  tolerance: number;
@@ -24,6 +29,8 @@ export default class ElevationProfile extends LitElement {
24
29
  y: number;
25
30
  };
26
31
  pointerEvents: boolean;
32
+ lineSegments?: SegmentData;
33
+ xAxisSegments?: SegmentData;
27
34
  pointer: {
28
35
  x: number;
29
36
  y: number;
@@ -1 +1 @@
1
- {"mappings":";;AAoBA,0BAA0B;IACxB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;CAClC,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,MAAO,WAAW,KAAK,WAAW,SAAS,MAAM,UAAU,MAAM,KAAG,IAAI,CAAO;IAC5E,MAAM;;;;;MAA8C;IACpD,QAAQ;;;MAAmB;IAC1B,aAAa,UAAQ;IAGvC,OAAO;;;MAAgB;IA4BvB,OAAO,CAAC,iBAAiB,EAAE,cAAc;IAczC,UAAU,CAAC,iBAAiB,EAAE,cAAc;IAsB5C,MAAM;IAiFR,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;IAiDpB,gBAAgB;CAG1B;AAGD,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 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 OverDetails = {\n coordinate: number[];\n position: {x: number; y: number};\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 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 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 for (const line of this.lines) {\n const data = line.map((coordinate) => ({x: coordinate[3], y: coordinate[2], coordinate}));\n this.plotData.push(...simplify(data, this.tolerance));\n this.plotData.push({x: line[line.length - 1][3], y: NaN, coordinate: []});\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 }\n\n override render() {\n const [width, height] = this.resizeController.value ?? [0, 0];\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 <path class=\"elevation\" d=\"${this.line(this.plotData)}\" fill=\"none\" />`\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], () => svg`<path class=\"elevation highlight\" d=\"${this.line(this.plotData)}\" fill=\"none\" />`)}\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 </svg>\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 this.dispatchEvent(\n new CustomEvent<OverDetails>('over', {\n detail: {\n coordinate: this.plotData[index].coordinate,\n position: this.pointer\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\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'elevation-profile': ElevationProfile;\n }\n}\n"],"names":[],"version":3,"file":"elevation-profile.d.ts.map"}
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;IA+BvB,OAAO,CAAC,iBAAiB,EAAE,cAAc;IAczC,UAAU,CAAC,iBAAiB,EAAE,cAAc;IAyD5C,MAAM;IAoGR,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;AA2DD,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 originalToSimplifiedIndexMap: Map<number, number> = new Map();\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.originalToSimplifiedIndexMap.clear();\n let originalIndex = 0;\n let simplifiedIndex = 0;\n\n for (const line of this.lines) {\n const data = line.map((coordinate, i) => {\n this.originalToSimplifiedIndexMap.set(originalIndex + i, -1); // mark all as not included initially\n return {x: coordinate[3], y: coordinate[2], coordinate};\n });\n\n const simplified = simplify(data, this.tolerance);\n\n // Map which original points made it through simplification\n for (const simplifiedPoint of simplified) {\n const origIdx = line.findIndex(coord =>\n coord[3] === simplifiedPoint.x && coord[2] === simplifiedPoint.y\n );\n if (origIdx >= 0) {\n this.originalToSimplifiedIndexMap.set(originalIndex + origIdx, simplifiedIndex);\n simplifiedIndex++;\n }\n }\n\n this.plotData.push(...simplified);\n this.plotData.push({x: line[line.length - 1][3], y: NaN, coordinate: []});\n simplifiedIndex++; // account for the NaN separator\n\n originalIndex += line.length;\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 // FIXME: what if this.lines is not set yet?\n if (changedProperties.has('lineSegments')) {\n const totalOriginalPoints = this.lines.reduce((sum, line) => sum + line.length, 0);\n const originalSegments = fillUnspecified(this.lineSegments || [], totalOriginalPoints);\n this.lineSegmentsData = remapSegments(originalSegments, this.originalToSimplifiedIndexMap);\n }\n if (changedProperties.has('xAxisSegments')) {\n const totalOriginalPoints = this.lines.reduce((sum, line) => sum + line.length, 0);\n const originalSegments = fillUnspecified(this.xAxisSegments || [], totalOriginalPoints);\n this.xAxisSegmentsData = remapSegments(originalSegments, this.originalToSimplifiedIndexMap);\n }\n }\n\n override render() {\n const [width, height] = this.resizeController.value ?? [0, 0];\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);\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 - 1].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 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) {\n filledSegments.push([currentIndex, length, 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\nfunction remapSegments(segments: SegmentData, indexMap: Map<number, number>): SegmentData {\n const remappedSegments: SegmentData = [];\n\n for (const [start, end, value] of segments) {\n // Find the first simplified index that corresponds to an original index >= start\n let newStart = -1;\n let newEnd = -1;\n\n for (let i = start; i <= end; i++) {\n const simplifiedIdx = indexMap.get(i);\n if (simplifiedIdx !== undefined && simplifiedIdx >= 0) {\n if (newStart === -1) {\n newStart = simplifiedIdx;\n }\n newEnd = simplifiedIdx;\n }\n }\n\n // Only add the segment if we found at least one point that survived simplification\n if (newStart >= 0 && newEnd >= 0) {\n remappedSegments.push([newStart, newEnd + 1, value]);\n }\n }\n\n return remappedSegments;\n}\n\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'elevation-profile': ElevationProfile;\n }\n}\n"],"names":[],"version":3,"file":"elevation-profile.d.ts.map"}
@@ -2,6 +2,7 @@ import {svg as $agntW$svg, LitElement as $agntW$LitElement} from "lit";
2
2
  import {property as $agntW$property, state as $agntW$state, customElement as $agntW$customElement} from "lit/decorators.js";
3
3
  import {ResizeController as $agntW$ResizeController} from "@lit-labs/observers/resize-controller.js";
4
4
  import {guard as $agntW$guard} from "lit/directives/guard.js";
5
+ import {ifDefined as $agntW$ifDefined} from "lit/directives/if-defined.js";
5
6
  import {bisector as $agntW$bisector, extent as $agntW$extent} from "d3-array";
6
7
  import {scaleLinear as $agntW$scaleLinear} from "d3-scale";
7
8
  import {line as $agntW$line, area as $agntW$area} from "d3-shape";
@@ -17,6 +18,7 @@ import {select as $agntW$select, pointer as $agntW$pointer} from "d3-selection";
17
18
 
18
19
 
19
20
 
21
+
20
22
  /*
21
23
  (c) 2017, Vladimir Agafonkin
22
24
  Simplify.js, a high-performance JS polyline simplification library
@@ -133,6 +135,9 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
133
135
  });
134
136
  this.plotData = [];
135
137
  this.pointsData = [];
138
+ this.lineSegmentsData = [];
139
+ this.xAxisSegmentsData = [];
140
+ this.originalToSimplifiedIndexMap = new Map();
136
141
  this.scaleX = (0, $agntW$scaleLinear)();
137
142
  this.scaleY = (0, $agntW$scaleLinear)();
138
143
  this.bisectDistance = (0, $agntW$bisector)((point)=>point.x);
@@ -160,18 +165,35 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
160
165
  willUpdate(changedProperties) {
161
166
  if (changedProperties.has('lines')) {
162
167
  this.plotData.length = 0;
168
+ this.originalToSimplifiedIndexMap.clear();
169
+ let originalIndex = 0;
170
+ let simplifiedIndex = 0;
163
171
  for (const line of this.lines){
164
- const data = line.map((coordinate)=>({
172
+ const data = line.map((coordinate, i)=>{
173
+ this.originalToSimplifiedIndexMap.set(originalIndex + i, -1); // mark all as not included initially
174
+ return {
165
175
  x: coordinate[3],
166
176
  y: coordinate[2],
167
177
  coordinate: coordinate
168
- }));
169
- this.plotData.push(...(0, $db5774c5ea265d30$export$2e2bcd8739ae039)(data, this.tolerance));
178
+ };
179
+ });
180
+ const simplified = (0, $db5774c5ea265d30$export$2e2bcd8739ae039)(data, this.tolerance);
181
+ // Map which original points made it through simplification
182
+ for (const simplifiedPoint of simplified){
183
+ const origIdx = line.findIndex((coord)=>coord[3] === simplifiedPoint.x && coord[2] === simplifiedPoint.y);
184
+ if (origIdx >= 0) {
185
+ this.originalToSimplifiedIndexMap.set(originalIndex + origIdx, simplifiedIndex);
186
+ simplifiedIndex++;
187
+ }
188
+ }
189
+ this.plotData.push(...simplified);
170
190
  this.plotData.push({
171
191
  x: line[line.length - 1][3],
172
192
  y: NaN,
173
193
  coordinate: []
174
194
  });
195
+ simplifiedIndex++; // account for the NaN separator
196
+ originalIndex += line.length;
175
197
  }
176
198
  this.scaleX.domain((0, $agntW$extent)(this.plotData, (data)=>data.x));
177
199
  this.scaleY.domain((0, $agntW$extent)(this.plotData, (data)=>data.y));
@@ -185,6 +207,17 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
185
207
  coordinate: point
186
208
  });
187
209
  }
210
+ // FIXME: what if this.lines is not set yet?
211
+ if (changedProperties.has('lineSegments')) {
212
+ const totalOriginalPoints = this.lines.reduce((sum, line)=>sum + line.length, 0);
213
+ const originalSegments = $916babf1e6dc2c08$var$fillUnspecified(this.lineSegments || [], totalOriginalPoints);
214
+ this.lineSegmentsData = $916babf1e6dc2c08$var$remapSegments(originalSegments, this.originalToSimplifiedIndexMap);
215
+ }
216
+ if (changedProperties.has('xAxisSegments')) {
217
+ const totalOriginalPoints = this.lines.reduce((sum, line)=>sum + line.length, 0);
218
+ const originalSegments = $916babf1e6dc2c08$var$fillUnspecified(this.xAxisSegments || [], totalOriginalPoints);
219
+ this.xAxisSegmentsData = $916babf1e6dc2c08$var$remapSegments(originalSegments, this.originalToSimplifiedIndexMap);
220
+ }
188
221
  }
189
222
  render() {
190
223
  const [width, height] = this.resizeController.value ?? [
@@ -228,7 +261,7 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
228
261
  ml
229
262
  ], ()=>(0, $agntW$svg)`
230
263
  <path class="area" d="${this.area(this.plotData)}" />
231
- <path class="elevation" d="${this.line(this.plotData)}" fill="none" />`)}
264
+ ${this.renderLineSegments('elevation')}`)}
232
265
 
233
266
  <g style="visibility: ${this.pointer.x > 0 ? 'visible' : 'hidden'}">
234
267
  <g clip-path="polygon(0 0, ${this.pointer.x - ml} 0, ${this.pointer.x - ml} 100%, 0 100%)">
@@ -237,7 +270,7 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
237
270
  width,
238
271
  height,
239
272
  ml
240
- ], ()=>(0, $agntW$svg)`<path class="elevation highlight" d="${this.line(this.plotData)}" fill="none" />`)}
273
+ ], ()=>this.renderLineSegments('elevation highlight'))}
241
274
  </g>
242
275
  <line
243
276
  class="pointer-line x"
@@ -274,9 +307,26 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
274
307
  style="visibility: ${this.lines.length ? 'visible' : 'hidden'}">
275
308
  <line x2="${width - ml - this.margin.right}"></line>
276
309
  </g>
310
+ <g transform="translate(0,${height - this.margin.bottom + offset})">
311
+ ${this.renderTrailBands()}
312
+ </g>
277
313
  </svg>
278
314
  `;
279
315
  }
316
+ renderLineSegments(className) {
317
+ return this.lineSegmentsData.map(([start, end, value])=>{
318
+ const segmentData = this.plotData.slice(start, end);
319
+ return (0, $agntW$svg)`<path class="${className}" data-value="${(0, $agntW$ifDefined)(value)}" d="${this.line(segmentData)}" fill="none" />`;
320
+ });
321
+ }
322
+ renderTrailBands() {
323
+ return this.xAxisSegmentsData.map(([start, end, value])=>{
324
+ const x1 = this.scaleX(this.plotData[start].x);
325
+ const x2 = this.scaleX(this.plotData[end - 1].x);
326
+ const bandWidth = x2 - x1;
327
+ return (0, $agntW$svg)`<rect class="trail-band" data-value="${(0, $agntW$ifDefined)(value)}" x="${x1}" width="${bandWidth}" />`;
328
+ });
329
+ }
280
330
  tickFormat(value, axis) {
281
331
  if (axis === 'y' || value < 1000) return this.meterFormat.format(value);
282
332
  else return this.kilometerFormat.format(value / 1000);
@@ -318,10 +368,17 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
318
368
  x: this.scaleX(data.x),
319
369
  y: this.scaleY(data.y)
320
370
  };
371
+ const segments = {
372
+ line: $916babf1e6dc2c08$var$getSegmentValueAtIndex(this.lineSegmentsData, index),
373
+ xAxis: $916babf1e6dc2c08$var$getSegmentValueAtIndex(this.xAxisSegmentsData, index)
374
+ };
321
375
  this.dispatchEvent(new CustomEvent('over', {
322
376
  detail: {
323
377
  coordinate: this.plotData[index].coordinate,
324
- position: this.pointer
378
+ position: this.pointer,
379
+ ...Object.keys(segments).length > 0 && {
380
+ segments: segments
381
+ }
325
382
  }
326
383
  }));
327
384
  }
@@ -374,6 +431,16 @@ $916babf1e6dc2c08$var$__decorate([
374
431
  type: Boolean
375
432
  })
376
433
  ], $916babf1e6dc2c08$var$ElevationProfile.prototype, "pointerEvents", void 0);
434
+ $916babf1e6dc2c08$var$__decorate([
435
+ (0, $agntW$property)({
436
+ type: Array
437
+ })
438
+ ], $916babf1e6dc2c08$var$ElevationProfile.prototype, "lineSegments", void 0);
439
+ $916babf1e6dc2c08$var$__decorate([
440
+ (0, $agntW$property)({
441
+ type: Array
442
+ })
443
+ ], $916babf1e6dc2c08$var$ElevationProfile.prototype, "xAxisSegments", void 0);
377
444
  $916babf1e6dc2c08$var$__decorate([
378
445
  (0, $agntW$state)()
379
446
  ], $916babf1e6dc2c08$var$ElevationProfile.prototype, "pointer", void 0);
@@ -381,6 +448,58 @@ $916babf1e6dc2c08$var$ElevationProfile = $916babf1e6dc2c08$var$__decorate([
381
448
  (0, $agntW$customElement)('elevation-profile')
382
449
  ], $916babf1e6dc2c08$var$ElevationProfile);
383
450
  var $916babf1e6dc2c08$export$2e2bcd8739ae039 = $916babf1e6dc2c08$var$ElevationProfile;
451
+ function $916babf1e6dc2c08$var$fillUnspecified(segment, length) {
452
+ const filledSegments = [];
453
+ let currentIndex = 0;
454
+ for (const [start, end, value] of segment){
455
+ if (start > currentIndex) filledSegments.push([
456
+ currentIndex,
457
+ start,
458
+ null
459
+ ]);
460
+ filledSegments.push([
461
+ start,
462
+ end,
463
+ value
464
+ ]);
465
+ currentIndex = end;
466
+ }
467
+ if (currentIndex < length) filledSegments.push([
468
+ currentIndex,
469
+ length,
470
+ null
471
+ ]);
472
+ return filledSegments;
473
+ }
474
+ function $916babf1e6dc2c08$var$getSegmentValueAtIndex(segments, index) {
475
+ for (const [start, end, value] of segments){
476
+ if (index >= start && index < end) return value;
477
+ }
478
+ // we must never be here because of fillUnspecified
479
+ return null;
480
+ }
481
+ function $916babf1e6dc2c08$var$remapSegments(segments, indexMap) {
482
+ const remappedSegments = [];
483
+ for (const [start, end, value] of segments){
484
+ // Find the first simplified index that corresponds to an original index >= start
485
+ let newStart = -1;
486
+ let newEnd = -1;
487
+ for(let i = start; i <= end; i++){
488
+ const simplifiedIdx = indexMap.get(i);
489
+ if (simplifiedIdx !== undefined && simplifiedIdx >= 0) {
490
+ if (newStart === -1) newStart = simplifiedIdx;
491
+ newEnd = simplifiedIdx;
492
+ }
493
+ }
494
+ // Only add the segment if we found at least one point that survived simplification
495
+ if (newStart >= 0 && newEnd >= 0) remappedSegments.push([
496
+ newStart,
497
+ newEnd + 1,
498
+ value
499
+ ]);
500
+ }
501
+ return remappedSegments;
502
+ }
384
503
 
385
504
 
386
505
  export {$916babf1e6dc2c08$export$2e2bcd8739ae039 as default};
@@ -1 +1 @@
1
- {"mappings":"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","sources":["elevation-profile.ts","simplify.js"],"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 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 OverDetails = {\n coordinate: number[];\n position: {x: number; y: number};\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 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 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 for (const line of this.lines) {\n const data = line.map((coordinate) => ({x: coordinate[3], y: coordinate[2], coordinate}));\n this.plotData.push(...simplify(data, this.tolerance));\n this.plotData.push({x: line[line.length - 1][3], y: NaN, coordinate: []});\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 }\n\n override render() {\n const [width, height] = this.resizeController.value ?? [0, 0];\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 <path class=\"elevation\" d=\"${this.line(this.plotData)}\" fill=\"none\" />`\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], () => svg`<path class=\"elevation highlight\" d=\"${this.line(this.plotData)}\" fill=\"none\" />`)}\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 </svg>\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 this.dispatchEvent(\n new CustomEvent<OverDetails>('over', {\n detail: {\n coordinate: this.plotData[index].coordinate,\n position: this.pointer\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\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'elevation-profile': ElevationProfile;\n }\n}\n","/*\n (c) 2017, Vladimir Agafonkin\n Simplify.js, a high-performance JS polyline simplification library\n mourner.github.io/simplify-js\n*/\n\n// to suit your point format, run search/replace for '.x' and '.y';\n// for 3D version, see 3d branch (configurability would draw significant performance overhead)\n\n// square distance between 2 points\nfunction getSqDist(p1, p2) {\n var dx = p1.x - p2.x,\n dy = p1.y - p2.y;\n\n return dx * dx + dy * dy;\n}\n\n// square distance from a point to a segment\nfunction getSqSegDist(p, p1, p2) {\n var x = p1.x,\n y = p1.y,\n dx = p2.x - x,\n dy = p2.y - y;\n\n if (dx !== 0 || dy !== 0) {\n var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);\n\n if (t > 1) {\n x = p2.x;\n y = p2.y;\n } else if (t > 0) {\n x += dx * t;\n y += dy * t;\n }\n }\n\n dx = p.x - x;\n dy = p.y - y;\n\n return dx * dx + dy * dy;\n}\n// rest of the code doesn't care about point format\n\n// basic distance-based simplification\nfunction simplifyRadialDist(points, sqTolerance) {\n var prevPoint = points[0],\n newPoints = [prevPoint],\n point;\n\n for (var i = 1, len = points.length; i < len; i++) {\n point = points[i];\n\n if (getSqDist(point, prevPoint) > sqTolerance) {\n newPoints.push(point);\n prevPoint = point;\n }\n }\n\n if (prevPoint !== point) newPoints.push(point);\n\n return newPoints;\n}\n\nfunction simplifyDPStep(points, first, last, sqTolerance, simplified) {\n var maxSqDist = sqTolerance,\n index;\n\n for (var i = first + 1; i < last; i++) {\n var sqDist = getSqSegDist(points[i], points[first], points[last]);\n\n if (sqDist > maxSqDist) {\n index = i;\n maxSqDist = sqDist;\n }\n }\n\n if (maxSqDist > sqTolerance) {\n if (index - first > 1)\n simplifyDPStep(points, first, index, sqTolerance, simplified);\n simplified.push(points[index]);\n if (last - index > 1)\n simplifyDPStep(points, index, last, sqTolerance, simplified);\n }\n}\n\n// simplification using Ramer-Douglas-Peucker algorithm\nfunction simplifyDouglasPeucker(points, sqTolerance) {\n var last = points.length - 1;\n\n var simplified = [points[0]];\n simplifyDPStep(points, 0, last, sqTolerance, simplified);\n simplified.push(points[last]);\n\n return simplified;\n}\n\n// both algorithms combined for awesome performance\nexport default function simplify(points, tolerance, highestQuality) {\n if (points.length <= 2) return points;\n\n var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;\n\n points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);\n points = simplifyDouglasPeucker(points, sqTolerance);\n\n return points;\n}\n"],"names":[],"version":3,"file":"elevation-profile.js.map"}
1
+ {"mappings":";;;;;;;;;;;;;;;;;;;;;ACAA;;;;AAIA,GAEA,mEAAmE;AACnE,8FAA8F;AAE9F,mCAAmC;AACnC,SAAS,gCAAU,EAAE,EAAE,EAAE;IACvB,IAAI,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC,EAClB,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC;IAElB,OAAO,KAAK,KAAK,KAAK;AACxB;AAEA,4CAA4C;AAC5C,SAAS,mCAAa,CAAC,EAAE,EAAE,EAAE,EAAE;IAC7B,IAAI,IAAI,GAAG,CAAC,EACV,IAAI,GAAG,CAAC,EACR,KAAK,GAAG,CAAC,GAAG,GACZ,KAAK,GAAG,CAAC,GAAG;IAEd,IAAI,OAAO,KAAK,OAAO,GAAG;QACxB,IAAI,IAAI,AAAC,CAAA,AAAC,CAAA,EAAE,CAAC,GAAG,CAAA,IAAK,KAAK,AAAC,CAAA,EAAE,CAAC,GAAG,CAAA,IAAK,EAAC,IAAM,CAAA,KAAK,KAAK,KAAK,EAAC;QAE7D,IAAI,IAAI,GAAG;YACT,IAAI,GAAG,CAAC;YACR,IAAI,GAAG,CAAC;QACV,OAAO,IAAI,IAAI,GAAG;YAChB,KAAK,KAAK;YACV,KAAK,KAAK;QACZ;IACF;IAEA,KAAK,EAAE,CAAC,GAAG;IACX,KAAK,EAAE,CAAC,GAAG;IAEX,OAAO,KAAK,KAAK,KAAK;AACxB;AACA,mDAAmD;AAEnD,sCAAsC;AACtC,SAAS,yCAAmB,MAAM,EAAE,WAAW;IAC7C,IAAI,YAAY,MAAM,CAAC,EAAE,EACvB,YAAY;QAAC;KAAU,EACvB;IAEF,IAAK,IAAI,IAAI,GAAG,MAAM,OAAO,MAAM,EAAE,IAAI,KAAK,IAAK;QACjD,QAAQ,MAAM,CAAC,EAAE;QAEjB,IAAI,gCAAU,OAAO,aAAa,aAAa;YAC7C,UAAU,IAAI,CAAC;YACf,YAAY;QACd;IACF;IAEA,IAAI,cAAc,OAAO,UAAU,IAAI,CAAC;IAExC,OAAO;AACT;AAEA,SAAS,qCAAe,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU;IAClE,IAAI,YAAY,aACd;IAEF,IAAK,IAAI,IAAI,QAAQ,GAAG,IAAI,MAAM,IAAK;QACrC,IAAI,SAAS,mCAAa,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK;QAEhE,IAAI,SAAS,WAAW;YACtB,QAAQ;YACR,YAAY;QACd;IACF;IAEA,IAAI,YAAY,aAAa;QAC3B,IAAI,QAAQ,QAAQ,GAClB,qCAAe,QAAQ,OAAO,OAAO,aAAa;QACpD,WAAW,IAAI,CAAC,MAAM,CAAC,MAAM;QAC7B,IAAI,OAAO,QAAQ,GACjB,qCAAe,QAAQ,OAAO,MAAM,aAAa;IACrD;AACF;AAEA,uDAAuD;AACvD,SAAS,6CAAuB,MAAM,EAAE,WAAW;IACjD,IAAI,OAAO,OAAO,MAAM,GAAG;IAE3B,IAAI,aAAa;QAAC,MAAM,CAAC,EAAE;KAAC;IAC5B,qCAAe,QAAQ,GAAG,MAAM,aAAa;IAC7C,WAAW,IAAI,CAAC,MAAM,CAAC,KAAK;IAE5B,OAAO;AACT;AAGe,kDAAkB,MAAM,EAAE,SAAS,EAAE,cAAc;IAChE,IAAI,OAAO,MAAM,IAAI,GAAG,OAAO;IAE/B,IAAI,cAAc,cAAc,YAAY,YAAY,YAAY;IAEpE,SAAS,iBAAiB,SAAS,yCAAmB,QAAQ;IAC9D,SAAS,6CAAuB,QAAQ;IAExC,OAAO;AACT;;;A,I,mC,a,U,U,I,S,U,E,M,E,G,E,I;I,I,I,U,M,E,I,I,I,S,S,O,O,O,wB,C,Q,O,M;I,I,O,Y,Y,O,Q,Q,K,Y,I,Q,Q,C,Y,Q,K;S,I,I,I,W,M,G,G,K,G,I,I,I,U,C,E,E,I,A,C,I,I,E,K,I,I,E,Q,K,K,E,Q,I,K;I,O,I,K,K,O,c,C,Q,K,I;A;ADzEe,IAAM,yCAAN,MAAM,yBAAyB,CAAA,GAAA,iBAAA;IAA/B,aAAA;Q,K,I;QACa,IAAA,CAAA,SAAS,GAAG;QACZ,IAAA,CAAA,MAAM,GAAG,UAAU,QAAQ;QAC5B,IAAA,CAAA,KAAK,GAAiB,EAAE;QACxB,IAAA,CAAA,MAAM,GAAe,EAAE;QACpC,IAAA,CAAA,WAAW,GAAG,CAAC,GAAgB,GAAgB,OAAe,UAA0B;QAC1E,IAAA,CAAA,MAAM,GAAG;YAAC,KAAK;YAAI,OAAO;YAAI,QAAQ;YAAI,MAAM;QAAE;QAClD,IAAA,CAAA,QAAQ,GAAG;YAAC,GAAG;YAAK,GAAG;QAAE;QACxB,IAAA,CAAA,aAAa,GAAG;QAGnC,IAAA,CAAA,aAAa,GAA0B;QAEtC,IAAA,CAAA,OAAO,GAAG;YAAC,GAAG;YAAG,GAAG;QAAC;QACtB,IAAA,CAAA,gBAAgB,GAAG,IAAI,CAAA,GAAA,uBAAA,EAAiB,IAAI,EAAE;YACpD,UAAU,IAAM;oBAAC,IAAI,CAAC,WAAW;oBAAE,IAAI,CAAC,YAAY;iBAAC;QACtD;QAEO,IAAA,CAAA,QAAQ,GAAgB,EAAE;QAC1B,IAAA,CAAA,UAAU,GAAgB,EAAE;QAC5B,IAAA,CAAA,gBAAgB,GAAgB,EAAE;QAClC,IAAA,CAAA,iBAAiB,GAAgB,EAAE;QACnC,IAAA,CAAA,4BAA4B,GAAwB,IAAI;QACxD,IAAA,CAAA,MAAM,GAAG,CAAA,GAAA,kBAAA;QACT,IAAA,CAAA,MAAM,GAAG,CAAA,GAAA,kBAAA;QAET,IAAA,CAAA,cAAc,GAAG,CAAA,GAAA,eAAA,EAAS,CAAC,QAAqB,MAAM,CAAC;QAEvD,IAAA,CAAA,IAAI,GAAG,CAAA,GAAA,WAAA,IACZ,OAAO,CAAC,CAAC,QAAqB,CAAC,MAAM,MAAM,CAAC,GAC5C,CAAC,CAAC,CAAC,QAAqB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAC3C,CAAC,CAAC,CAAC,QAAqB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;QACtC,IAAA,CAAA,IAAI,GAAG,CAAA,GAAA,WAAA,IACZ,OAAO,CAAC,CAAC,QAAqB,CAAC,MAAM,MAAM,CAAC,GAC5C,CAAC,CAAC,CAAC,QAAqB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAC3C,EAAE,CAAC,CAAC,QAAqB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;QACvC,IAAA,CAAA,KAAK,GAAG,CAAA,GAAA,iBAAA,EAAW,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,QAAkB,IAAI,CAAC,UAAU,CAAC,OAAO;QACrF,IAAA,CAAA,KAAK,GAAG,CAAA,GAAA,eAAA,EAAS,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,QAAkB,IAAI,CAAC,UAAU,CAAC,OAAO;QACnF,IAAA,CAAA,KAAK,GAAG,CAAA,GAAA,iBAAA,EAAW,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,IAAM;QACjD,IAAA,CAAA,KAAK,GAAG,CAAA,GAAA,eAAA,EAAS,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,IAAM;QAE/C,IAAA,CAAA,WAAW,GAA6B;QACxC,IAAA,CAAA,eAAe,GAA6B;IAsQtD;IApQW,QAAQ,iBAAiC,EAAzC;QACP,IAAI,kBAAkB,GAAG,CAAC,WAAW;YACnC,IAAI,CAAC,WAAW,GAAG,IAAI,KAAK,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE;gBACpD,OAAO;gBACP,MAAM;YACP;YAED,IAAI,CAAC,eAAe,GAAG,IAAI,KAAK,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE;gBACxD,OAAO;gBACP,MAAM;YACP;QACH;IACF;IAES,WAAW,iBAAiC,EAA5C;QACP,IAAI,kBAAkB,GAAG,CAAC,UAAU;YAClC,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG;YACvB,IAAI,CAAC,4BAA4B,CAAC,KAAK;YACvC,IAAI,gBAAgB;YACpB,IAAI,kBAAkB;YAEtB,KAAK,MAAM,QAAQ,IAAI,CAAC,KAAK,CAAE;gBAC7B,MAAM,OAAO,KAAK,GAAG,CAAC,CAAC,YAAY;oBACjC,IAAI,CAAC,4BAA4B,CAAC,GAAG,CAAC,gBAAgB,GAAG,KAAK,qCAAqC;oBACnG,OAAO;wBAAC,GAAG,UAAU,CAAC,EAAE;wBAAE,GAAG,UAAU,CAAC,EAAE;oCAAE;oBAAU;gBACxD;gBAEA,MAAM,aAAa,CAAA,GAAA,wCAAA,EAAS,MAAM,IAAI,CAAC,SAAS;gBAEhD,2DAA2D;gBAC3D,KAAK,MAAM,mBAAmB,WAAY;oBACxC,MAAM,UAAU,KAAK,SAAS,CAAC,CAAA,QAC7B,KAAK,CAAC,EAAE,KAAK,gBAAgB,CAAC,IAAI,KAAK,CAAC,EAAE,KAAK,gBAAgB,CAAC;oBAElE,IAAI,WAAW,GAAG;wBAChB,IAAI,CAAC,4BAA4B,CAAC,GAAG,CAAC,gBAAgB,SAAS;wBAC/D;oBACF;gBACF;gBAEA,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI;gBACtB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAAC,GAAG,IAAI,CAAC,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE;oBAAE,GAAG;oBAAK,YAAY,EAAE;gBAAA;gBACvE,mBAAmB,gCAAgC;gBAEnD,iBAAiB,KAAK,MAAM;YAC9B;YAEA,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA,GAAA,aAAA,EAAO,IAAI,CAAC,QAAQ,EAAE,CAAC,OAAoB,KAAK,CAAC;YACpE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA,GAAA,aAAA,EAAO,IAAI,CAAC,QAAQ,EAAE,CAAC,OAAoB,KAAK,CAAC;YAEpE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,YAAY;QAChF;QACA,IAAI,kBAAkB,GAAG,CAAC,WAAW;YACnC,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG;YACzB,KAAK,MAAM,SAAS,IAAI,CAAC,MAAM,CAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAC,GAAG,KAAK,CAAC,EAAE;gBAAE,GAAG,KAAK,CAAC,EAAE;gBAAE,YAAY;YAAK;QAErE;QACA,4CAA4C;QAC5C,IAAI,kBAAkB,GAAG,CAAC,iBAAiB;YACzC,MAAM,sBAAsB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,OAAS,MAAM,KAAK,MAAM,EAAE;YAChF,MAAM,mBAAmB,sCAAgB,IAAI,CAAC,YAAY,IAAI,EAAE,EAAE;YAClE,IAAI,CAAC,gBAAgB,GAAG,oCAAc,kBAAkB,IAAI,CAAC,4BAA4B;QAC3F;QACA,IAAI,kBAAkB,GAAG,CAAC,kBAAkB;YAC1C,MAAM,sBAAsB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,OAAS,MAAM,KAAK,MAAM,EAAE;YAChF,MAAM,mBAAmB,sCAAgB,IAAI,CAAC,aAAa,IAAI,EAAE,EAAE;YACnE,IAAI,CAAC,iBAAiB,GAAG,oCAAc,kBAAkB,IAAI,CAAC,4BAA4B;QAC5F;IACF;IAES,SAAA;QACP,MAAM,CAAC,OAAO,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI;YAAC;YAAG;SAAE;QAC7D,MAAM,KAAK,AAAC,CAAA,IAAI,CAAC,aAAa,CAAC,YAAY,wBAAwB,SAAS,CAAA,IAAK,IAAI,CAAC,MAAM,CAAC,IAAI;QAEjG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAAC;YAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK;SAAC;QACjD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAAC,SAAS,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG;SAAC;QAEhE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,MAAM;QAExC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK;QACnD,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM;QAEjE,MAAM,SAAS,QAAQ,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,SAAS,SAAS,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QACjB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QACjB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QACjB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAEjB,CAAA,GAAA,aAAA,EAAO,IAAI,CAAC,aAAa,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,KAAK;QACrD,CAAA,GAAA,aAAA,EAAO,IAAI,CAAC,aAAa,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,KAAK;QACrD,CAAA,GAAA,aAAA,EAAO,IAAI,CAAC,aAAa,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,KAAK;QACrD,CAAA,GAAA,aAAA,EAAO,IAAI,CAAC,aAAa,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,KAAK;QAErD,MAAM,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM;QAEhC,OAAO,CAAA,GAAA,UAAA,CAAG,CAAV;kBACgB,EAAA,MAAK,UAAA,EAAa,OAAM,eAAA,EAAkB,MAAK,CAAA,EAAI,OAAnD;+CAC6B,EAAA,GAAA;kDACG,EAAA,IAAI,CAAC,MAAM,CAAC,MAAM,CAAlB;kDACA,EAAA,SAAS,IAAI,CAAC,MAAM,CAAC,MAAM,CAA3B;+CACH,EAAA,GAAA;;QAEvC,EAAA,CAAA,GAAA,YAAA,EAAM;YAAC,IAAI,CAAC,KAAK;YAAE;YAAO;YAAQ;SAAG,EAAE,IAAM,CAAA,GAAA,UAAA,CAAG,CAAhD;gCACwB,EAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAvB;UACtB,EAAA,IAAI,CAAC,kBAAkB,CAAC,aAAY,CAAE,EAAtC;;8BAGoB,EAAA,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,YAAY,SAAjC;qCACO,EAAA,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,GAAE,IAAA,EAAO,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,GAA3C;YACzB,EAAA,CAAA,GAAA,YAAA,EAAM;YAAC,IAAI,CAAC,KAAK;YAAE;YAAO;YAAQ;SAAG,EAAE,IAAM,IAAI,CAAC,kBAAkB,CAAC,wBAArE;;;;gBAII,EAAA,IAAI,CAAC,OAAO,CAAC,CAAC,CAAd;gBACA,EAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAf;gBACA,EAAA,IAAI,CAAC,OAAO,CAAC,CAAC,CAAd;gBACA,EAAA,SAAS,IAAI,CAAC,MAAM,CAAC,MAAM,CAA3B;;;;gBAIA,EAAA,GAAA;gBACA,EAAA,IAAI,CAAC,OAAO,CAAC,CAAC,CAAd;gBACA,EAAA,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAzB;gBACA,EAAA,IAAI,CAAC,OAAO,CAAC,CAAC,CAAd;;qDAEqC,EAAA,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA,MAAA,EAAS,IAAI,CAAC,OAAO,CAAC,CAAC,CAArC;6CACR,EAAA,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA,MAAA,EAAS,IAAI,CAAC,OAAO,CAAC,CAAC,CAArC;;;QAGrC,EAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,OAAO,QAAU,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,QAAhG;;;iBAGS,EAAA,MAAA;kBACC,EAAA,OAAA;;0BAEQ,EAAA,IAAI,CAAC,aAAa,GAAG,QAAQ,OAA7B;;wBAEF,EAAA,IAAI,CAAC,WAAW,CAAhB;uBACD,EAAA,IAAI,CAAC,UAAU,CAAf;;;+BAGQ,EAAA,GAAE,CAAA,EAAI,SAAS,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,OAApC;;6BAEF,EAAA,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,YAAY,SAAhC;oBACT,EAAA,QAAQ,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK,CAA9B;;kCAEc,EAAA,SAAS,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,OAA9B;UACxB,EAAA,IAAI,CAAC,gBAAgB,GAArB;;;IAGP,CAAA;IACH;IAEQ,mBAAmB,SAAiB,EAApC;QACN,OAAO,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM;YACnD,MAAM,cAAc,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO;YAC/C,OAAO,CAAA,GAAA,UAAA,CAAG,CAAA,aAAA,EAAgB,UAAS,cAAA,EAAiB,CAAA,GAAA,gBAAA,EAAU,OAAM,KAAA,EAAQ,IAAI,CAAC,IAAI,CAAC,aAAY,gBAAA,CAAkB;QACtH;IACF;IAEQ,mBAAA;QACN,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM;YACpD,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC7C,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YAC/C,MAAM,YAAY,KAAK;YACvB,OAAO,CAAA,GAAA,UAAA,CAAG,CAAA,qCAAA,EAAwC,CAAA,GAAA,gBAAA,EAAU,OAAM,KAAA,EAAQ,GAAE,SAAA,EAAY,UAAS,IAAA,CAAM;QACzG;IACF;IAEO,WAAW,KAAa,EAAE,IAAe,EAAzC;QACL,IAAI,SAAS,OAAO,QAAQ,MAC1B,OAAO,IAAI,CAAC,WAAY,CAAC,MAAM,CAAC;aAEhC,OAAO,IAAI,CAAC,eAAgB,CAAC,MAAM,CAAC,QAAQ;IAEhD;IAEO,WAAW,MAAgB,EAAE,IAAe,EAA5C;QACL,IAAI,OAAO,MAAM,KAAK,KAAM,SAAS,OAAO,SAAS,KACnD;QAEF,SAAS,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;IACvE;IAEO,SAAS,CAAS,EAAE,CAAS,EAAE,KAAa,EAA5C;QACL,OAAO,CAAA,GAAA,UAAA,CAAG,CAAA,0BAAA,EAA6B,EAAC,MAAA,EAAS,EAAC,UAAA,CAAY;IAChE;IAES,eAAA;QACP,MAAM,QAAQ,IAAI,CAAC,aAAa,CAAC;QACjC,IAAI,OAAO;YACT,IAAI,CAAC,aAAa,GAAG,IAAI,eAAe;gBACtC,IAAI,CAAC,aAAa;YACpB;YACA,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;QAC7B;QACA,4FAA4F;QAC5F,IAAI,CAAC,aAAa;IACpB;IAES,uBAAA;QACP,IAAI,IAAI,CAAC,aAAa,EACpB,IAAI,CAAC,aAAa,CAAC,UAAU;QAE/B,KAAK,CAAC;IACR;IAEQ,YAAY,KAAmB,EAA/B;QACN,MAAM,kBAAkB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA,GAAA,cAAA,EAAQ,MAAM,CAAC,EAAE;QAC5D,MAAM,QAAQ,KAAK,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,kBAAkB,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG;QAExG,IAAI,QAAQ,GACV;QAEF,SAAS;QACT,oCAAoC;QACpC,iCAAiC;QACjC,uDAAuD;QACvD,2DAA2D;QAE3D,MAAM,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM;QAEjC,IAAI,MAAM,KAAK,CAAC,GACd;QAGF,IAAI,CAAC,OAAO,GAAG;YACb,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YACrB,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QACtB;QAED,MAAM,WAAoC;YACxC,MAAM,6CAAuB,IAAI,CAAC,gBAAgB,EAAE;YACpD,OAAO,6CAAuB,IAAI,CAAC,iBAAiB,EAAE;QACvD;QAED,IAAI,CAAC,aAAa,CAChB,IAAI,YAAyB,QAAQ;YACnC,QAAQ;gBACN,YAAY,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU;gBAC3C,UAAU,IAAI,CAAC,OAAO;gBACtB,GAAI,OAAO,IAAI,CAAC,UAAU,MAAM,GAAG,KAAK;8BAAE;gBAAQ,CAAE;YACrD;QACF;IAEL;IAEQ,aAAA;QACN,IAAI,CAAC,OAAO,GAAG;YACb,GAAG;YACH,GAAG;QACJ;QACD,IAAI,CAAC,aAAa,CAAC,IAAI,YAAY;IACrC;IAES,mBAAA;QACP,OAAO,IAAI;IACb;AACD;AA/S2B,iCAAA;IAAzB,CAAA,GAAA,eAAA,EAAS;QAAC,MAAM;IAAM;CAAiB,EAAA,uCAAA,SAAA,EAAA,aAAA,KAAA;AACd,iCAAA;IAAzB,CAAA,GAAA,eAAA,EAAS;QAAC,MAAM;IAAM;CAA+B,EAAA,uCAAA,SAAA,EAAA,UAAA,KAAA;AAC7B,iCAAA;IAAxB,CAAA,GAAA,eAAA,EAAS;QAAC,MAAM;IAAK;CAA4B,EAAA,uCAAA,SAAA,EAAA,SAAA,KAAA;AACzB,iCAAA;IAAxB,CAAA,GAAA,eAAA,EAAS;QAAC,MAAM;IAAK;CAA2B,EAAA,uCAAA,SAAA,EAAA,UAAA,KAAA;AACrC,iCAAA;IAAX,CAAA,GAAA,eAAA;CAAqG,EAAA,uCAAA,SAAA,EAAA,eAAA,KAAA;AAC5E,iCAAA;IAAzB,CAAA,GAAA,eAAA,EAAS;QAAC,MAAM;IAAM;CAAuD,EAAA,uCAAA,SAAA,EAAA,UAAA,KAAA;AACpD,iCAAA;IAAzB,CAAA,GAAA,eAAA,EAAS;QAAC,MAAM;IAAM;CAA8B,EAAA,uCAAA,SAAA,EAAA,YAAA,KAAA;AAC1B,iCAAA;IAA1B,CAAA,GAAA,eAAA,EAAS;QAAC,MAAM;IAAO;CAAwB,EAAA,uCAAA,SAAA,EAAA,iBAAA,KAAA;AACvB,iCAAA;IAAxB,CAAA,GAAA,eAAA,EAAS;QAAC,MAAM;IAAK;CAA8B,EAAA,uCAAA,SAAA,EAAA,gBAAA,KAAA;AAC3B,iCAAA;IAAxB,CAAA,GAAA,eAAA,EAAS;QAAC,MAAM;IAAK;CAA+B,EAAA,uCAAA,SAAA,EAAA,iBAAA,KAAA;AAG5C,iCAAA;IAAR,CAAA,GAAA,YAAA;CAA+B,EAAA,uCAAA,SAAA,EAAA,WAAA,KAAA;AAbb,yCAAA,iCAAA;IADpB,CAAA,GAAA,oBAAA,EAAc;CACM,EAAA;IAAA,2CAAA;AAkTrB,SAAS,sCAAgB,OAAoB,EAAE,MAAc;IAC3D,MAAM,iBAA8B,EAAE;IACtC,IAAI,eAAe;IAEnB,KAAK,MAAM,CAAC,OAAO,KAAK,MAAM,IAAI,QAAS;QACzC,IAAI,QAAQ,cACV,eAAe,IAAI,CAAC;YAAC;YAAc;YAAO;SAAK;QAEjD,eAAe,IAAI,CAAC;YAAC;YAAO;YAAK;SAAM;QACvC,eAAe;IACjB;IAEA,IAAI,eAAe,QACjB,eAAe,IAAI,CAAC;QAAC;QAAc;QAAQ;KAAK;IAGlD,OAAO;AACT;AAEA,SAAS,6CAAuB,QAAqB,EAAE,KAAa;IAClE,KAAK,MAAM,CAAC,OAAO,KAAK,MAAM,IAAI,SAAU;QAC1C,IAAI,SAAS,SAAS,QAAQ,KAC5B,OAAO;IAEX;IACA,mDAAmD;IACnD,OAAO;AACT;AAEA,SAAS,oCAAc,QAAqB,EAAE,QAA6B;IACzE,MAAM,mBAAgC,EAAE;IAExC,KAAK,MAAM,CAAC,OAAO,KAAK,MAAM,IAAI,SAAU;QAC1C,iFAAiF;QACjF,IAAI,WAAW;QACf,IAAI,SAAS;QAEb,IAAK,IAAI,IAAI,OAAO,KAAK,KAAK,IAAK;YACjC,MAAM,gBAAgB,SAAS,GAAG,CAAC;YACnC,IAAI,kBAAkB,aAAa,iBAAiB,GAAG;gBACrD,IAAI,aAAa,IACf,WAAW;gBAEb,SAAS;YACX;QACF;QAEA,mFAAmF;QACnF,IAAI,YAAY,KAAK,UAAU,GAC7B,iBAAiB,IAAI,CAAC;YAAC;YAAU,SAAS;YAAG;SAAM;IAEvD;IAEA,OAAO;AACT","sources":["elevation-profile.ts","simplify.js"],"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 originalToSimplifiedIndexMap: Map<number, number> = new Map();\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.originalToSimplifiedIndexMap.clear();\n let originalIndex = 0;\n let simplifiedIndex = 0;\n\n for (const line of this.lines) {\n const data = line.map((coordinate, i) => {\n this.originalToSimplifiedIndexMap.set(originalIndex + i, -1); // mark all as not included initially\n return {x: coordinate[3], y: coordinate[2], coordinate};\n });\n\n const simplified = simplify(data, this.tolerance);\n\n // Map which original points made it through simplification\n for (const simplifiedPoint of simplified) {\n const origIdx = line.findIndex(coord =>\n coord[3] === simplifiedPoint.x && coord[2] === simplifiedPoint.y\n );\n if (origIdx >= 0) {\n this.originalToSimplifiedIndexMap.set(originalIndex + origIdx, simplifiedIndex);\n simplifiedIndex++;\n }\n }\n\n this.plotData.push(...simplified);\n this.plotData.push({x: line[line.length - 1][3], y: NaN, coordinate: []});\n simplifiedIndex++; // account for the NaN separator\n\n originalIndex += line.length;\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 // FIXME: what if this.lines is not set yet?\n if (changedProperties.has('lineSegments')) {\n const totalOriginalPoints = this.lines.reduce((sum, line) => sum + line.length, 0);\n const originalSegments = fillUnspecified(this.lineSegments || [], totalOriginalPoints);\n this.lineSegmentsData = remapSegments(originalSegments, this.originalToSimplifiedIndexMap);\n }\n if (changedProperties.has('xAxisSegments')) {\n const totalOriginalPoints = this.lines.reduce((sum, line) => sum + line.length, 0);\n const originalSegments = fillUnspecified(this.xAxisSegments || [], totalOriginalPoints);\n this.xAxisSegmentsData = remapSegments(originalSegments, this.originalToSimplifiedIndexMap);\n }\n }\n\n override render() {\n const [width, height] = this.resizeController.value ?? [0, 0];\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);\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 - 1].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 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) {\n filledSegments.push([currentIndex, length, 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\nfunction remapSegments(segments: SegmentData, indexMap: Map<number, number>): SegmentData {\n const remappedSegments: SegmentData = [];\n\n for (const [start, end, value] of segments) {\n // Find the first simplified index that corresponds to an original index >= start\n let newStart = -1;\n let newEnd = -1;\n\n for (let i = start; i <= end; i++) {\n const simplifiedIdx = indexMap.get(i);\n if (simplifiedIdx !== undefined && simplifiedIdx >= 0) {\n if (newStart === -1) {\n newStart = simplifiedIdx;\n }\n newEnd = simplifiedIdx;\n }\n }\n\n // Only add the segment if we found at least one point that survived simplification\n if (newStart >= 0 && newEnd >= 0) {\n remappedSegments.push([newStart, newEnd + 1, value]);\n }\n }\n\n return remappedSegments;\n}\n\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'elevation-profile': ElevationProfile;\n }\n}\n","/*\n (c) 2017, Vladimir Agafonkin\n Simplify.js, a high-performance JS polyline simplification library\n mourner.github.io/simplify-js\n*/\n\n// to suit your point format, run search/replace for '.x' and '.y';\n// for 3D version, see 3d branch (configurability would draw significant performance overhead)\n\n// square distance between 2 points\nfunction getSqDist(p1, p2) {\n var dx = p1.x - p2.x,\n dy = p1.y - p2.y;\n\n return dx * dx + dy * dy;\n}\n\n// square distance from a point to a segment\nfunction getSqSegDist(p, p1, p2) {\n var x = p1.x,\n y = p1.y,\n dx = p2.x - x,\n dy = p2.y - y;\n\n if (dx !== 0 || dy !== 0) {\n var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);\n\n if (t > 1) {\n x = p2.x;\n y = p2.y;\n } else if (t > 0) {\n x += dx * t;\n y += dy * t;\n }\n }\n\n dx = p.x - x;\n dy = p.y - y;\n\n return dx * dx + dy * dy;\n}\n// rest of the code doesn't care about point format\n\n// basic distance-based simplification\nfunction simplifyRadialDist(points, sqTolerance) {\n var prevPoint = points[0],\n newPoints = [prevPoint],\n point;\n\n for (var i = 1, len = points.length; i < len; i++) {\n point = points[i];\n\n if (getSqDist(point, prevPoint) > sqTolerance) {\n newPoints.push(point);\n prevPoint = point;\n }\n }\n\n if (prevPoint !== point) newPoints.push(point);\n\n return newPoints;\n}\n\nfunction simplifyDPStep(points, first, last, sqTolerance, simplified) {\n var maxSqDist = sqTolerance,\n index;\n\n for (var i = first + 1; i < last; i++) {\n var sqDist = getSqSegDist(points[i], points[first], points[last]);\n\n if (sqDist > maxSqDist) {\n index = i;\n maxSqDist = sqDist;\n }\n }\n\n if (maxSqDist > sqTolerance) {\n if (index - first > 1)\n simplifyDPStep(points, first, index, sqTolerance, simplified);\n simplified.push(points[index]);\n if (last - index > 1)\n simplifyDPStep(points, index, last, sqTolerance, simplified);\n }\n}\n\n// simplification using Ramer-Douglas-Peucker algorithm\nfunction simplifyDouglasPeucker(points, sqTolerance) {\n var last = points.length - 1;\n\n var simplified = [points[0]];\n simplifyDPStep(points, 0, last, sqTolerance, simplified);\n simplified.push(points[last]);\n\n return simplified;\n}\n\n// both algorithms combined for awesome performance\nexport default function simplify(points, tolerance, highestQuality) {\n if (points.length <= 2) return points;\n\n var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;\n\n points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);\n points = simplifyDouglasPeucker(points, sqTolerance);\n\n return points;\n}\n"],"names":[],"version":3,"file":"elevation-profile.js.map"}
@@ -2,6 +2,7 @@ import {LitElement, svg} from 'lit';
2
2
  import {customElement, state, property} from 'lit/decorators.js';
3
3
  import {ResizeController} from '@lit-labs/observers/resize-controller.js';
4
4
  import {guard} from 'lit/directives/guard.js';
5
+ import {ifDefined} from 'lit/directives/if-defined.js';
5
6
  import type {PropertyValues, TemplateResult} from 'lit';
6
7
 
7
8
  import {extent, bisector} from 'd3-array';
@@ -18,9 +19,15 @@ type PlotPoint = {
18
19
  coordinate: number[];
19
20
  };
20
21
 
22
+ export type SegmentData = Array<[number, number, string | null]>;
23
+
21
24
  export type OverDetails = {
22
25
  coordinate: number[];
23
26
  position: {x: number; y: number};
27
+ segments?: {
28
+ line: string | null;
29
+ xAxis: string | null;
30
+ };
24
31
  };
25
32
 
26
33
  @customElement('elevation-profile')
@@ -33,6 +40,8 @@ export default class ElevationProfile extends LitElement {
33
40
  @property({type: Object}) margin = {top: 20, right: 20, bottom: 20, left: 20};
34
41
  @property({type: Object}) tickSize = {x: 100, y: 40};
35
42
  @property({type: Boolean}) pointerEvents = true;
43
+ @property({type: Array}) lineSegments?: SegmentData;
44
+ @property({type: Array}) xAxisSegments?: SegmentData;
36
45
  private yAxisObserver: ResizeObserver | null = null;
37
46
 
38
47
  @state() pointer = {x: 0, y: 0};
@@ -42,6 +51,9 @@ export default class ElevationProfile extends LitElement {
42
51
 
43
52
  private plotData: PlotPoint[] = [];
44
53
  private pointsData: PlotPoint[] = [];
54
+ private lineSegmentsData: SegmentData = [];
55
+ private xAxisSegmentsData: SegmentData = [];
56
+ private originalToSimplifiedIndexMap: Map<number, number> = new Map();
45
57
  private scaleX = scaleLinear();
46
58
  private scaleY = scaleLinear();
47
59
 
@@ -80,10 +92,34 @@ export default class ElevationProfile extends LitElement {
80
92
  override willUpdate(changedProperties: PropertyValues) {
81
93
  if (changedProperties.has('lines')) {
82
94
  this.plotData.length = 0;
95
+ this.originalToSimplifiedIndexMap.clear();
96
+ let originalIndex = 0;
97
+ let simplifiedIndex = 0;
98
+
83
99
  for (const line of this.lines) {
84
- const data = line.map((coordinate) => ({x: coordinate[3], y: coordinate[2], coordinate}));
85
- this.plotData.push(...simplify(data, this.tolerance));
100
+ const data = line.map((coordinate, i) => {
101
+ this.originalToSimplifiedIndexMap.set(originalIndex + i, -1); // mark all as not included initially
102
+ return {x: coordinate[3], y: coordinate[2], coordinate};
103
+ });
104
+
105
+ const simplified = simplify(data, this.tolerance);
106
+
107
+ // Map which original points made it through simplification
108
+ for (const simplifiedPoint of simplified) {
109
+ const origIdx = line.findIndex(coord =>
110
+ coord[3] === simplifiedPoint.x && coord[2] === simplifiedPoint.y
111
+ );
112
+ if (origIdx >= 0) {
113
+ this.originalToSimplifiedIndexMap.set(originalIndex + origIdx, simplifiedIndex);
114
+ simplifiedIndex++;
115
+ }
116
+ }
117
+
118
+ this.plotData.push(...simplified);
86
119
  this.plotData.push({x: line[line.length - 1][3], y: NaN, coordinate: []});
120
+ simplifiedIndex++; // account for the NaN separator
121
+
122
+ originalIndex += line.length;
87
123
  }
88
124
 
89
125
  this.scaleX.domain(extent(this.plotData, (data: PlotPoint) => data.x));
@@ -97,6 +133,17 @@ export default class ElevationProfile extends LitElement {
97
133
  this.pointsData.push({x: point[3], y: point[2], coordinate: point});
98
134
  }
99
135
  }
136
+ // FIXME: what if this.lines is not set yet?
137
+ if (changedProperties.has('lineSegments')) {
138
+ const totalOriginalPoints = this.lines.reduce((sum, line) => sum + line.length, 0);
139
+ const originalSegments = fillUnspecified(this.lineSegments || [], totalOriginalPoints);
140
+ this.lineSegmentsData = remapSegments(originalSegments, this.originalToSimplifiedIndexMap);
141
+ }
142
+ if (changedProperties.has('xAxisSegments')) {
143
+ const totalOriginalPoints = this.lines.reduce((sum, line) => sum + line.length, 0);
144
+ const originalSegments = fillUnspecified(this.xAxisSegments || [], totalOriginalPoints);
145
+ this.xAxisSegmentsData = remapSegments(originalSegments, this.originalToSimplifiedIndexMap);
146
+ }
100
147
  }
101
148
 
102
149
  override render() {
@@ -134,12 +181,12 @@ export default class ElevationProfile extends LitElement {
134
181
 
135
182
  ${guard([this.lines, width, height, ml], () => svg`
136
183
  <path class="area" d="${this.area(this.plotData)}" />
137
- <path class="elevation" d="${this.line(this.plotData)}" fill="none" />`
184
+ ${this.renderLineSegments('elevation')}`
138
185
  )}
139
186
 
140
187
  <g style="visibility: ${this.pointer.x > 0 ? 'visible' : 'hidden'}">
141
188
  <g clip-path="polygon(0 0, ${this.pointer.x - ml} 0, ${this.pointer.x - ml} 100%, 0 100%)">
142
- ${guard([this.lines, width, height, ml], () => svg`<path class="elevation highlight" d="${this.line(this.plotData)}" fill="none" />`)}
189
+ ${guard([this.lines, width, height, ml], () => this.renderLineSegments('elevation highlight'))}
143
190
  </g>
144
191
  <line
145
192
  class="pointer-line x"
@@ -176,10 +223,29 @@ export default class ElevationProfile extends LitElement {
176
223
  style="visibility: ${this.lines.length ? 'visible' : 'hidden'}">
177
224
  <line x2="${width - ml - this.margin.right}"></line>
178
225
  </g>
226
+ <g transform="translate(0,${height - this.margin.bottom + offset})">
227
+ ${this.renderTrailBands()}
228
+ </g>
179
229
  </svg>
180
230
  `;
181
231
  }
182
232
 
233
+ private renderLineSegments(className: string) {
234
+ return this.lineSegmentsData.map(([start, end, value]) => {
235
+ const segmentData = this.plotData.slice(start, end);
236
+ return svg`<path class="${className}" data-value="${ifDefined(value)}" d="${this.line(segmentData)}" fill="none" />`;
237
+ });
238
+ }
239
+
240
+ private renderTrailBands() {
241
+ return this.xAxisSegmentsData.map(([start, end, value]) => {
242
+ const x1 = this.scaleX(this.plotData[start].x);
243
+ const x2 = this.scaleX(this.plotData[end - 1].x);
244
+ const bandWidth = x2 - x1;
245
+ return svg`<rect class="trail-band" data-value="${ifDefined(value)}" x="${x1}" width="${bandWidth}" />`;
246
+ });
247
+ }
248
+
183
249
  public tickFormat(value: number, axis: 'x' | 'y') {
184
250
  if (axis === 'y' || value < 1000) {
185
251
  return this.meterFormat!.format(value);
@@ -242,11 +308,17 @@ export default class ElevationProfile extends LitElement {
242
308
  y: this.scaleY(data.y),
243
309
  };
244
310
 
311
+ const segments: OverDetails['segments'] = {
312
+ line: getSegmentValueAtIndex(this.lineSegmentsData, index),
313
+ xAxis: getSegmentValueAtIndex(this.xAxisSegmentsData, index),
314
+ };
315
+
245
316
  this.dispatchEvent(
246
317
  new CustomEvent<OverDetails>('over', {
247
318
  detail: {
248
319
  coordinate: this.plotData[index].coordinate,
249
- position: this.pointer
320
+ position: this.pointer,
321
+ ...(Object.keys(segments).length > 0 && { segments })
250
322
  }
251
323
  }),
252
324
  );
@@ -265,6 +337,62 @@ export default class ElevationProfile extends LitElement {
265
337
  }
266
338
  }
267
339
 
340
+ function fillUnspecified(segment: SegmentData, length: number): SegmentData {
341
+ const filledSegments: SegmentData = [];
342
+ let currentIndex = 0;
343
+
344
+ for (const [start, end, value] of segment) {
345
+ if (start > currentIndex) {
346
+ filledSegments.push([currentIndex, start, null]);
347
+ }
348
+ filledSegments.push([start, end, value]);
349
+ currentIndex = end;
350
+ }
351
+
352
+ if (currentIndex < length) {
353
+ filledSegments.push([currentIndex, length, null]);
354
+ }
355
+
356
+ return filledSegments;
357
+ }
358
+
359
+ function getSegmentValueAtIndex(segments: SegmentData, index: number): string | null {
360
+ for (const [start, end, value] of segments) {
361
+ if (index >= start && index < end) {
362
+ return value;
363
+ }
364
+ }
365
+ // we must never be here because of fillUnspecified
366
+ return null;
367
+ }
368
+
369
+ function remapSegments(segments: SegmentData, indexMap: Map<number, number>): SegmentData {
370
+ const remappedSegments: SegmentData = [];
371
+
372
+ for (const [start, end, value] of segments) {
373
+ // Find the first simplified index that corresponds to an original index >= start
374
+ let newStart = -1;
375
+ let newEnd = -1;
376
+
377
+ for (let i = start; i <= end; i++) {
378
+ const simplifiedIdx = indexMap.get(i);
379
+ if (simplifiedIdx !== undefined && simplifiedIdx >= 0) {
380
+ if (newStart === -1) {
381
+ newStart = simplifiedIdx;
382
+ }
383
+ newEnd = simplifiedIdx;
384
+ }
385
+ }
386
+
387
+ // Only add the segment if we found at least one point that survived simplification
388
+ if (newStart >= 0 && newEnd >= 0) {
389
+ remappedSegments.push([newStart, newEnd + 1, value]);
390
+ }
391
+ }
392
+
393
+ return remappedSegments;
394
+ }
395
+
268
396
 
269
397
  declare global {
270
398
  interface HTMLElementTagNameMap {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoblocks/elevation-profile",
3
- "version": "0.0.24",
3
+ "version": "0.0.25-beta.2",
4
4
  "license": "BSD-3-Clause",
5
5
  "repository": "github:geoblocks/elevation-profile",
6
6
  "type": "module",
@@ -29,20 +29,20 @@
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.4",
32
+ "@lit-labs/observers": "2.0.6",
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.2.1"
38
+ "lit": "3.3.1"
39
39
  },
40
40
  "devDependencies": {
41
- "@parcel/packager-ts": "2.13.3",
42
- "@parcel/transformer-typescript-tsc": "2.13.3",
43
- "@parcel/transformer-typescript-types": "2.13.3",
41
+ "@parcel/packager-ts": "2.16.3",
42
+ "@parcel/transformer-typescript-tsc": "2.16.3",
43
+ "@parcel/transformer-typescript-types": "2.16.3",
44
44
  "gh-pages": "6.3.0",
45
- "parcel": "2.13.3",
46
- "typescript": "5.7.3"
45
+ "parcel": "2.16.3",
46
+ "typescript": "5.9.3"
47
47
  }
48
48
  }