@geoblocks/elevation-profile 0.0.23 → 0.0.25-beta.1

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;
@@ -35,6 +42,7 @@ export default class ElevationProfile extends LitElement {
35
42
  tickValues(values: number[], axis: 'x' | 'y'): void;
36
43
  pointSvg(x: number, y: number, index: number): TemplateResult;
37
44
  firstUpdated(): void;
45
+ disconnectedCallback(): void;
38
46
  createRenderRoot(): this;
39
47
  }
40
48
  declare global {
@@ -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;IAEvC,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;IA+CZ,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\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], () => 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 // FIXME: because the ref element are used before render is done, we need to force an update\n this.requestUpdate();\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;IA8BvB,OAAO,CAAC,iBAAiB,EAAE,cAAc;IAczC,UAAU,CAAC,iBAAiB,EAAE,cAAc;IA6B5C,MAAM;IAsGR,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;AAgCD,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 = 0;\n @property({type: String}) locale = navigator.language;\n @property({type: Array}) lines: number[][][] = [];\n @property({type: Array}) points: number[][] = [];\n @property() updateScale = (x: scaleLinear, y: scaleLinear, width: number, height: number): void => {};\n @property({type: Object}) margin = {top: 20, right: 20, bottom: 20, left: 20};\n @property({type: Object}) tickSize = {x: 100, y: 40};\n @property({type: Boolean}) pointerEvents = true;\n @property({type: Array}) lineSegments?: SegmentData;\n @property({type: Array}) xAxisSegments?: SegmentData;\n private yAxisObserver: ResizeObserver | null = null;\n\n @state() pointer = {x: 0, y: 0};\n private resizeController = new ResizeController(this, {\n callback: () => [this.offsetWidth, this.offsetHeight],\n });\n\n private plotData: PlotPoint[] = [];\n private pointsData: PlotPoint[] = [];\n private lineSegmentsData: SegmentData = [];\n private xAxisSegmentsData: SegmentData = [];\n private scaleX = scaleLinear();\n private scaleY = scaleLinear();\n\n private bisectDistance = bisector((point: PlotPoint) => point.x);\n\n private line = line()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y((point: PlotPoint) => this.scaleY(point.y));\n private area = area()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y1((point: PlotPoint) => this.scaleY(point.y));\n private xAxis = axisBottom(this.scaleX).tickFormat((value: number) => this.tickFormat(value, 'x'));\n private yAxis = axisLeft(this.scaleY).tickFormat((value: number) => this.tickFormat(value, 'y'));\n private xGrid = axisBottom(this.scaleX).tickFormat(() => '');\n private yGrid = axisLeft(this.scaleY).tickFormat(() => '');\n\n private meterFormat: Intl.NumberFormat | null = null;\n private kilometerFormat: Intl.NumberFormat | null = null;\n\n override updated(changedProperties: PropertyValues) {\n if (changedProperties.has('locale')) {\n this.meterFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'meter',\n });\n\n this.kilometerFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'kilometer',\n });\n }\n }\n\n override willUpdate(changedProperties: PropertyValues) {\n if (changedProperties.has('lines')) {\n this.plotData.length = 0;\n 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 // FIXME: what if this.lines is not set yet?\n if (changedProperties.has('lineSegments')) {\n this.lineSegmentsData = fillUnspecified(this.lineSegments || [], this.lines.length);\n }\n if (changedProperties.has('xAxisSegments')) {\n this.xAxisSegmentsData = fillUnspecified(this.xAxisSegments || [], this.lines.length);\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 // FIXME: take line simplification into account: we have less points in plotData than in lines\n const segmentData = this.plotData.slice(start, end + 1);\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 // FIXME: take line simplification into account: we have less points in plotData than in lines\n const x1 = this.scaleX(this.plotData[start]?.x ?? 0);\n const x2 = this.scaleX(this.plotData[end]?.x ?? 0);\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\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
@@ -104,7 +106,7 @@ var $916babf1e6dc2c08$var$__decorate = undefined && undefined.__decorate || func
104
106
  let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0, $agntW$LitElement) {
105
107
  constructor(){
106
108
  super(...arguments);
107
- this.tolerance = 1;
109
+ this.tolerance = 0;
108
110
  this.locale = navigator.language;
109
111
  this.lines = [];
110
112
  this.points = [];
@@ -120,6 +122,7 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
120
122
  y: 40
121
123
  };
122
124
  this.pointerEvents = true;
125
+ this.yAxisObserver = null;
123
126
  this.pointer = {
124
127
  x: 0,
125
128
  y: 0
@@ -132,6 +135,8 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
132
135
  });
133
136
  this.plotData = [];
134
137
  this.pointsData = [];
138
+ this.lineSegmentsData = [];
139
+ this.xAxisSegmentsData = [];
135
140
  this.scaleX = (0, $agntW$scaleLinear)();
136
141
  this.scaleY = (0, $agntW$scaleLinear)();
137
142
  this.bisectDistance = (0, $agntW$bisector)((point)=>point.x);
@@ -184,6 +189,9 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
184
189
  coordinate: point
185
190
  });
186
191
  }
192
+ // FIXME: what if this.lines is not set yet?
193
+ if (changedProperties.has('lineSegments')) this.lineSegmentsData = $916babf1e6dc2c08$var$fillUnspecified(this.lineSegments || [], this.lines.length);
194
+ if (changedProperties.has('xAxisSegments')) this.xAxisSegmentsData = $916babf1e6dc2c08$var$fillUnspecified(this.xAxisSegments || [], this.lines.length);
187
195
  }
188
196
  render() {
189
197
  const [width, height] = this.resizeController.value ?? [
@@ -227,7 +235,7 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
227
235
  ml
228
236
  ], ()=>(0, $agntW$svg)`
229
237
  <path class="area" d="${this.area(this.plotData)}" />
230
- <path class="elevation" d="${this.line(this.plotData)}" fill="none" />`)}
238
+ ${this.renderLineSegments('elevation')}`)}
231
239
 
232
240
  <g style="visibility: ${this.pointer.x > 0 ? 'visible' : 'hidden'}">
233
241
  <g clip-path="polygon(0 0, ${this.pointer.x - ml} 0, ${this.pointer.x - ml} 100%, 0 100%)">
@@ -236,7 +244,7 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
236
244
  width,
237
245
  height,
238
246
  ml
239
- ], ()=>(0, $agntW$svg)`<path class="elevation highlight" d="${this.line(this.plotData)}" fill="none" />`)}
247
+ ], ()=>this.renderLineSegments('elevation highlight'))}
240
248
  </g>
241
249
  <line
242
250
  class="pointer-line x"
@@ -273,9 +281,28 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
273
281
  style="visibility: ${this.lines.length ? 'visible' : 'hidden'}">
274
282
  <line x2="${width - ml - this.margin.right}"></line>
275
283
  </g>
284
+ <g transform="translate(0,${height - this.margin.bottom + offset})">
285
+ ${this.renderTrailBands()}
286
+ </g>
276
287
  </svg>
277
288
  `;
278
289
  }
290
+ renderLineSegments(className) {
291
+ return this.lineSegmentsData.map(([start, end, value])=>{
292
+ // FIXME: take line simplification into account: we have less points in plotData than in lines
293
+ const segmentData = this.plotData.slice(start, end + 1);
294
+ return (0, $agntW$svg)`<path class="${className}" data-value="${(0, $agntW$ifDefined)(value)}" d="${this.line(segmentData)}" fill="none" />`;
295
+ });
296
+ }
297
+ renderTrailBands() {
298
+ return this.xAxisSegmentsData.map(([start, end, value])=>{
299
+ // FIXME: take line simplification into account: we have less points in plotData than in lines
300
+ const x1 = this.scaleX(this.plotData[start]?.x ?? 0);
301
+ const x2 = this.scaleX(this.plotData[end]?.x ?? 0);
302
+ const bandWidth = x2 - x1;
303
+ return (0, $agntW$svg)`<rect class="trail-band" data-value="${(0, $agntW$ifDefined)(value)}" x="${x1}" width="${bandWidth}" />`;
304
+ });
305
+ }
279
306
  tickFormat(value, axis) {
280
307
  if (axis === 'y' || value < 1000) return this.meterFormat.format(value);
281
308
  else return this.kilometerFormat.format(value / 1000);
@@ -288,9 +315,20 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
288
315
  return (0, $agntW$svg)`<circle class="point" cx="${x}" cy="${y}" r="10"/>`;
289
316
  }
290
317
  firstUpdated() {
318
+ const axisY = this.querySelector('.axis.y');
319
+ if (axisY) {
320
+ this.yAxisObserver = new ResizeObserver(()=>{
321
+ this.requestUpdate();
322
+ });
323
+ this.yAxisObserver.observe(axisY);
324
+ }
291
325
  // FIXME: because the ref element are used before render is done, we need to force an update
292
326
  this.requestUpdate();
293
327
  }
328
+ disconnectedCallback() {
329
+ if (this.yAxisObserver) this.yAxisObserver.disconnect();
330
+ super.disconnectedCallback();
331
+ }
294
332
  pointerMove(event) {
295
333
  const pointerDistance = this.scaleX.invert((0, $agntW$pointer)(event)[0]);
296
334
  const index = Math.min(this.bisectDistance.left(this.plotData, pointerDistance), this.plotData.length - 1);
@@ -306,10 +344,17 @@ let $916babf1e6dc2c08$var$ElevationProfile = class ElevationProfile extends (0,
306
344
  x: this.scaleX(data.x),
307
345
  y: this.scaleY(data.y)
308
346
  };
347
+ const segments = {
348
+ line: $916babf1e6dc2c08$var$getSegmentValueAtIndex(this.lineSegmentsData, index),
349
+ xAxis: $916babf1e6dc2c08$var$getSegmentValueAtIndex(this.xAxisSegmentsData, index)
350
+ };
309
351
  this.dispatchEvent(new CustomEvent('over', {
310
352
  detail: {
311
353
  coordinate: this.plotData[index].coordinate,
312
- position: this.pointer
354
+ position: this.pointer,
355
+ ...Object.keys(segments).length > 0 && {
356
+ segments: segments
357
+ }
313
358
  }
314
359
  }));
315
360
  }
@@ -362,6 +407,16 @@ $916babf1e6dc2c08$var$__decorate([
362
407
  type: Boolean
363
408
  })
364
409
  ], $916babf1e6dc2c08$var$ElevationProfile.prototype, "pointerEvents", void 0);
410
+ $916babf1e6dc2c08$var$__decorate([
411
+ (0, $agntW$property)({
412
+ type: Array
413
+ })
414
+ ], $916babf1e6dc2c08$var$ElevationProfile.prototype, "lineSegments", void 0);
415
+ $916babf1e6dc2c08$var$__decorate([
416
+ (0, $agntW$property)({
417
+ type: Array
418
+ })
419
+ ], $916babf1e6dc2c08$var$ElevationProfile.prototype, "xAxisSegments", void 0);
365
420
  $916babf1e6dc2c08$var$__decorate([
366
421
  (0, $agntW$state)()
367
422
  ], $916babf1e6dc2c08$var$ElevationProfile.prototype, "pointer", void 0);
@@ -369,6 +424,36 @@ $916babf1e6dc2c08$var$ElevationProfile = $916babf1e6dc2c08$var$__decorate([
369
424
  (0, $agntW$customElement)('elevation-profile')
370
425
  ], $916babf1e6dc2c08$var$ElevationProfile);
371
426
  var $916babf1e6dc2c08$export$2e2bcd8739ae039 = $916babf1e6dc2c08$var$ElevationProfile;
427
+ function $916babf1e6dc2c08$var$fillUnspecified(segment, length) {
428
+ const filledSegments = [];
429
+ let currentIndex = 0;
430
+ for (const [start, end, value] of segment){
431
+ if (start > currentIndex) filledSegments.push([
432
+ currentIndex,
433
+ start,
434
+ null
435
+ ]);
436
+ filledSegments.push([
437
+ start,
438
+ end,
439
+ value
440
+ ]);
441
+ currentIndex = end;
442
+ }
443
+ if (currentIndex < length) filledSegments.push([
444
+ currentIndex,
445
+ length,
446
+ null
447
+ ]);
448
+ return filledSegments;
449
+ }
450
+ function $916babf1e6dc2c08$var$getSegmentValueAtIndex(segments, index) {
451
+ for (const [start, end, value] of segments){
452
+ if (index >= start && index < end) return value;
453
+ }
454
+ // we must never be here because of fillUnspecified
455
+ return null;
456
+ }
372
457
 
373
458
 
374
459
  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\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 // FIXME: because the ref element are used before render is done, we need to force an update\n this.requestUpdate();\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":"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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 {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 = 0;\n @property({type: String}) locale = navigator.language;\n @property({type: Array}) lines: number[][][] = [];\n @property({type: Array}) points: number[][] = [];\n @property() updateScale = (x: scaleLinear, y: scaleLinear, width: number, height: number): void => {};\n @property({type: Object}) margin = {top: 20, right: 20, bottom: 20, left: 20};\n @property({type: Object}) tickSize = {x: 100, y: 40};\n @property({type: Boolean}) pointerEvents = true;\n @property({type: Array}) lineSegments?: SegmentData;\n @property({type: Array}) xAxisSegments?: SegmentData;\n private yAxisObserver: ResizeObserver | null = null;\n\n @state() pointer = {x: 0, y: 0};\n private resizeController = new ResizeController(this, {\n callback: () => [this.offsetWidth, this.offsetHeight],\n });\n\n private plotData: PlotPoint[] = [];\n private pointsData: PlotPoint[] = [];\n private lineSegmentsData: SegmentData = [];\n private xAxisSegmentsData: SegmentData = [];\n private scaleX = scaleLinear();\n private scaleY = scaleLinear();\n\n private bisectDistance = bisector((point: PlotPoint) => point.x);\n\n private line = line()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y((point: PlotPoint) => this.scaleY(point.y));\n private area = area()\n .defined((point: PlotPoint) => !isNaN(point.y))\n .x((point: PlotPoint) => this.scaleX(point.x))\n .y1((point: PlotPoint) => this.scaleY(point.y));\n private xAxis = axisBottom(this.scaleX).tickFormat((value: number) => this.tickFormat(value, 'x'));\n private yAxis = axisLeft(this.scaleY).tickFormat((value: number) => this.tickFormat(value, 'y'));\n private xGrid = axisBottom(this.scaleX).tickFormat(() => '');\n private yGrid = axisLeft(this.scaleY).tickFormat(() => '');\n\n private meterFormat: Intl.NumberFormat | null = null;\n private kilometerFormat: Intl.NumberFormat | null = null;\n\n override updated(changedProperties: PropertyValues) {\n if (changedProperties.has('locale')) {\n this.meterFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'meter',\n });\n\n this.kilometerFormat = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: 'kilometer',\n });\n }\n }\n\n override willUpdate(changedProperties: PropertyValues) {\n if (changedProperties.has('lines')) {\n this.plotData.length = 0;\n 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 // FIXME: what if this.lines is not set yet?\n if (changedProperties.has('lineSegments')) {\n this.lineSegmentsData = fillUnspecified(this.lineSegments || [], this.lines.length);\n }\n if (changedProperties.has('xAxisSegments')) {\n this.xAxisSegmentsData = fillUnspecified(this.xAxisSegments || [], this.lines.length);\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 // FIXME: take line simplification into account: we have less points in plotData than in lines\n const segmentData = this.plotData.slice(start, end + 1);\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 // FIXME: take line simplification into account: we have less points in plotData than in lines\n const x1 = this.scaleX(this.plotData[start]?.x ?? 0);\n const x2 = this.scaleX(this.plotData[end]?.x ?? 0);\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\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,14 +19,20 @@ 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')
27
34
  export default class ElevationProfile extends LitElement {
28
- @property({type: Number}) tolerance = 1;
35
+ @property({type: Number}) tolerance = 0;
29
36
  @property({type: String}) locale = navigator.language;
30
37
  @property({type: Array}) lines: number[][][] = [];
31
38
  @property({type: Array}) points: number[][] = [];
@@ -33,6 +40,9 @@ 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;
45
+ private yAxisObserver: ResizeObserver | null = null;
36
46
 
37
47
  @state() pointer = {x: 0, y: 0};
38
48
  private resizeController = new ResizeController(this, {
@@ -41,6 +51,8 @@ export default class ElevationProfile extends LitElement {
41
51
 
42
52
  private plotData: PlotPoint[] = [];
43
53
  private pointsData: PlotPoint[] = [];
54
+ private lineSegmentsData: SegmentData = [];
55
+ private xAxisSegmentsData: SegmentData = [];
44
56
  private scaleX = scaleLinear();
45
57
  private scaleY = scaleLinear();
46
58
 
@@ -96,6 +108,13 @@ export default class ElevationProfile extends LitElement {
96
108
  this.pointsData.push({x: point[3], y: point[2], coordinate: point});
97
109
  }
98
110
  }
111
+ // FIXME: what if this.lines is not set yet?
112
+ if (changedProperties.has('lineSegments')) {
113
+ this.lineSegmentsData = fillUnspecified(this.lineSegments || [], this.lines.length);
114
+ }
115
+ if (changedProperties.has('xAxisSegments')) {
116
+ this.xAxisSegmentsData = fillUnspecified(this.xAxisSegments || [], this.lines.length);
117
+ }
99
118
  }
100
119
 
101
120
  override render() {
@@ -133,12 +152,12 @@ export default class ElevationProfile extends LitElement {
133
152
 
134
153
  ${guard([this.lines, width, height, ml], () => svg`
135
154
  <path class="area" d="${this.area(this.plotData)}" />
136
- <path class="elevation" d="${this.line(this.plotData)}" fill="none" />`
155
+ ${this.renderLineSegments('elevation')}`
137
156
  )}
138
157
 
139
158
  <g style="visibility: ${this.pointer.x > 0 ? 'visible' : 'hidden'}">
140
159
  <g clip-path="polygon(0 0, ${this.pointer.x - ml} 0, ${this.pointer.x - ml} 100%, 0 100%)">
141
- ${guard([this.lines, width, height, ml], () => svg`<path class="elevation highlight" d="${this.line(this.plotData)}" fill="none" />`)}
160
+ ${guard([this.lines, width, height, ml], () => this.renderLineSegments('elevation highlight'))}
142
161
  </g>
143
162
  <line
144
163
  class="pointer-line x"
@@ -175,10 +194,31 @@ export default class ElevationProfile extends LitElement {
175
194
  style="visibility: ${this.lines.length ? 'visible' : 'hidden'}">
176
195
  <line x2="${width - ml - this.margin.right}"></line>
177
196
  </g>
197
+ <g transform="translate(0,${height - this.margin.bottom + offset})">
198
+ ${this.renderTrailBands()}
199
+ </g>
178
200
  </svg>
179
201
  `;
180
202
  }
181
203
 
204
+ private renderLineSegments(className: string) {
205
+ return this.lineSegmentsData.map(([start, end, value]) => {
206
+ // FIXME: take line simplification into account: we have less points in plotData than in lines
207
+ const segmentData = this.plotData.slice(start, end + 1);
208
+ return svg`<path class="${className}" data-value="${ifDefined(value)}" d="${this.line(segmentData)}" fill="none" />`;
209
+ });
210
+ }
211
+
212
+ private renderTrailBands() {
213
+ return this.xAxisSegmentsData.map(([start, end, value]) => {
214
+ // FIXME: take line simplification into account: we have less points in plotData than in lines
215
+ const x1 = this.scaleX(this.plotData[start]?.x ?? 0);
216
+ const x2 = this.scaleX(this.plotData[end]?.x ?? 0);
217
+ const bandWidth = x2 - x1;
218
+ return svg`<rect class="trail-band" data-value="${ifDefined(value)}" x="${x1}" width="${bandWidth}" />`;
219
+ });
220
+ }
221
+
182
222
  public tickFormat(value: number, axis: 'x' | 'y') {
183
223
  if (axis === 'y' || value < 1000) {
184
224
  return this.meterFormat!.format(value);
@@ -199,10 +239,24 @@ export default class ElevationProfile extends LitElement {
199
239
  }
200
240
 
201
241
  override firstUpdated() {
242
+ const axisY = this.querySelector('.axis.y');
243
+ if (axisY) {
244
+ this.yAxisObserver = new ResizeObserver(() => {
245
+ this.requestUpdate()
246
+ })
247
+ this.yAxisObserver.observe(axisY);
248
+ }
202
249
  // FIXME: because the ref element are used before render is done, we need to force an update
203
250
  this.requestUpdate();
204
251
  }
205
252
 
253
+ override disconnectedCallback() {
254
+ if (this.yAxisObserver) {
255
+ this.yAxisObserver.disconnect();
256
+ }
257
+ super.disconnectedCallback();
258
+ }
259
+
206
260
  private pointerMove(event: PointerEvent) {
207
261
  const pointerDistance = this.scaleX.invert(pointer(event)[0]);
208
262
  const index = Math.min(this.bisectDistance.left(this.plotData, pointerDistance), this.plotData.length - 1);
@@ -227,11 +281,17 @@ export default class ElevationProfile extends LitElement {
227
281
  y: this.scaleY(data.y),
228
282
  };
229
283
 
284
+ const segments: OverDetails['segments'] = {
285
+ line: getSegmentValueAtIndex(this.lineSegmentsData, index),
286
+ xAxis: getSegmentValueAtIndex(this.xAxisSegmentsData, index),
287
+ };
288
+
230
289
  this.dispatchEvent(
231
290
  new CustomEvent<OverDetails>('over', {
232
291
  detail: {
233
292
  coordinate: this.plotData[index].coordinate,
234
- position: this.pointer
293
+ position: this.pointer,
294
+ ...(Object.keys(segments).length > 0 && { segments })
235
295
  }
236
296
  }),
237
297
  );
@@ -250,6 +310,35 @@ export default class ElevationProfile extends LitElement {
250
310
  }
251
311
  }
252
312
 
313
+ function fillUnspecified(segment: SegmentData, length: number): SegmentData {
314
+ const filledSegments: SegmentData = [];
315
+ let currentIndex = 0;
316
+
317
+ for (const [start, end, value] of segment) {
318
+ if (start > currentIndex) {
319
+ filledSegments.push([currentIndex, start, null]);
320
+ }
321
+ filledSegments.push([start, end, value]);
322
+ currentIndex = end;
323
+ }
324
+
325
+ if (currentIndex < length) {
326
+ filledSegments.push([currentIndex, length, null]);
327
+ }
328
+
329
+ return filledSegments;
330
+ }
331
+
332
+ function getSegmentValueAtIndex(segments: SegmentData, index: number): string | null {
333
+ for (const [start, end, value] of segments) {
334
+ if (index >= start && index < end) {
335
+ return value;
336
+ }
337
+ }
338
+ // we must never be here because of fillUnspecified
339
+ return null;
340
+ }
341
+
253
342
 
254
343
  declare global {
255
344
  interface HTMLElementTagNameMap {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoblocks/elevation-profile",
3
- "version": "0.0.23",
3
+ "version": "0.0.25-beta.1",
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
  }