@macrostrat/column-views 1.0.0

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.
Files changed (109) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +3 -0
  3. package/dist/age-axis.7ce58d66.js +41 -0
  4. package/dist/age-axis.7ce58d66.js.map +1 -0
  5. package/dist/boxes.6d3192e6.js +164 -0
  6. package/dist/boxes.6d3192e6.js.map +1 -0
  7. package/dist/boxes.module.998ff312.js +13 -0
  8. package/dist/boxes.module.998ff312.js.map +1 -0
  9. package/dist/boxes.module.dd631fe3.css +4 -0
  10. package/dist/boxes.module.dd631fe3.css.map +1 -0
  11. package/dist/carbon-isotopes.6d9bddcf.js +11 -0
  12. package/dist/carbon-isotopes.6d9bddcf.js.map +1 -0
  13. package/dist/column.e35a471f.js +149 -0
  14. package/dist/column.e35a471f.js.map +1 -0
  15. package/dist/column.e901b5d4.js +117 -0
  16. package/dist/column.e901b5d4.js.map +1 -0
  17. package/dist/column.module.2e29259c.js +22 -0
  18. package/dist/column.module.2e29259c.js.map +1 -0
  19. package/dist/column.module.35184ea7.css +157 -0
  20. package/dist/column.module.35184ea7.css.map +1 -0
  21. package/dist/column.module.35ba04e7.js +34 -0
  22. package/dist/column.module.35ba04e7.js.map +1 -0
  23. package/dist/column.module.c9df6edf.css +138 -0
  24. package/dist/column.module.c9df6edf.css.map +1 -0
  25. package/dist/composite.494beffc.js +153 -0
  26. package/dist/composite.494beffc.js.map +1 -0
  27. package/dist/composite.module.5cc0e747.css +4 -0
  28. package/dist/composite.module.5cc0e747.css.map +1 -0
  29. package/dist/composite.module.6359deba.js +13 -0
  30. package/dist/composite.module.6359deba.js.map +1 -0
  31. package/dist/core.f3824dd8.js +89 -0
  32. package/dist/core.f3824dd8.js.map +1 -0
  33. package/dist/data-area.f9bb9ad9.js +86 -0
  34. package/dist/data-area.f9bb9ad9.js.map +1 -0
  35. package/dist/data-provider.61ef7f9a.js +25 -0
  36. package/dist/data-provider.61ef7f9a.js.map +1 -0
  37. package/dist/helpers.2af05ee4.js +94 -0
  38. package/dist/helpers.2af05ee4.js.map +1 -0
  39. package/dist/index.d.ts +363 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +19 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/index.module.8fad0020.js +37 -0
  44. package/dist/index.module.8fad0020.js.map +1 -0
  45. package/dist/index.module.de76feb2.css +65 -0
  46. package/dist/index.module.de76feb2.css.map +1 -0
  47. package/dist/isotopes-column.b5442b4d.js +163 -0
  48. package/dist/isotopes-column.b5442b4d.js.map +1 -0
  49. package/dist/layers.946aaa37.js +231 -0
  50. package/dist/layers.946aaa37.js.map +1 -0
  51. package/dist/map.e782010f.js +82 -0
  52. package/dist/map.e782010f.js.map +1 -0
  53. package/dist/names.ab14789e.js +65 -0
  54. package/dist/names.ab14789e.js.map +1 -0
  55. package/dist/providers.abcb2cbe.js +25 -0
  56. package/dist/providers.abcb2cbe.js.map +1 -0
  57. package/dist/resolvers.2949c4c0.js +83 -0
  58. package/dist/resolvers.2949c4c0.js.map +1 -0
  59. package/dist/section.6f6fc929.js +118 -0
  60. package/dist/section.6f6fc929.js.map +1 -0
  61. package/dist/selection-popover.7e008544.js +60 -0
  62. package/dist/selection-popover.7e008544.js.map +1 -0
  63. package/dist/selection-popover.module.2732ebc0.css +34 -0
  64. package/dist/selection-popover.module.2732ebc0.css.map +1 -0
  65. package/dist/selection-popover.module.fdbfb40e.js +22 -0
  66. package/dist/selection-popover.module.fdbfb40e.js.map +1 -0
  67. package/dist/selection.add9f8b0.js +117 -0
  68. package/dist/selection.add9f8b0.js.map +1 -0
  69. package/dist/store.c5271371.js +18 -0
  70. package/dist/store.c5271371.js.map +1 -0
  71. package/dist/types.ff938b22.js +18 -0
  72. package/dist/types.ff938b22.js.map +1 -0
  73. package/dist/unit-details.0c1edd00.js +209 -0
  74. package/dist/unit-details.0c1edd00.js.map +1 -0
  75. package/dist/units.5ec6a44c.js +42 -0
  76. package/dist/units.5ec6a44c.js.map +1 -0
  77. package/package.json +66 -0
  78. package/src/age-axis.ts +42 -0
  79. package/src/carbon-isotopes/column.module.sass +136 -0
  80. package/src/carbon-isotopes/column.ts +131 -0
  81. package/src/carbon-isotopes/core.ts +78 -0
  82. package/src/carbon-isotopes/data-area.ts +96 -0
  83. package/src/carbon-isotopes/data-provider.ts +19 -0
  84. package/src/carbon-isotopes/index.ts +3 -0
  85. package/src/carbon-isotopes/isotopes-column.ts +214 -0
  86. package/src/column.module.sass +114 -0
  87. package/src/column.ts +185 -0
  88. package/src/helpers.ts +133 -0
  89. package/src/index.ts +7 -0
  90. package/src/map/animated.ts +78 -0
  91. package/src/map/index.ts +86 -0
  92. package/src/map/layers.ts +316 -0
  93. package/src/providers.ts +34 -0
  94. package/src/section.ts +129 -0
  95. package/src/selection-popover.module.sass +27 -0
  96. package/src/selection-popover.ts +71 -0
  97. package/src/store.ts +12 -0
  98. package/src/unit-details/index.module.sass +51 -0
  99. package/src/unit-details/index.ts +215 -0
  100. package/src/units/boxes.module.sass +3 -0
  101. package/src/units/boxes.ts +221 -0
  102. package/src/units/composite.module.sass +2 -0
  103. package/src/units/composite.ts +266 -0
  104. package/src/units/index.ts +36 -0
  105. package/src/units/names.ts +118 -0
  106. package/src/units/resolvers.ts +111 -0
  107. package/src/units/selection.ts +167 -0
  108. package/src/units/types.ts +26 -0
  109. package/src/units/units.scss +16 -0
@@ -0,0 +1,316 @@
1
+ import h from "@macrostrat/hyper";
2
+ import {
3
+ Feature,
4
+ FeatureLayer,
5
+ MapContext,
6
+ } from "@macrostrat/svg-map-components";
7
+ import { useAPIResult, useKeyHandler } from "@macrostrat/ui-components";
8
+ import chroma from "chroma-js";
9
+ import { ExtendedFeature, geoCentroid } from "d3-geo";
10
+ import { geoVoronoi } from "d3-geo-voronoi";
11
+ import { FeatureCollection, GeometryCollection, Polygon } from "geojson";
12
+ import { useContext, useEffect, useMemo } from "react";
13
+ import { feature } from "topojson-client";
14
+
15
+ const defaultStyle = {
16
+ fill: "rgb(239, 180, 249)",
17
+ stroke: "magenta",
18
+ };
19
+
20
+ export function MeasurementsLayer(props) {
21
+ const { style = defaultStyle, ...params } = props;
22
+ const res: any = useAPIResult("/measurements", {
23
+ ...params,
24
+ format: "geojson",
25
+ response: "light",
26
+ });
27
+ if (res == null) return null;
28
+
29
+ return h(FeatureLayer, {
30
+ useCanvas: false,
31
+ style,
32
+ features: res.features.filter((d) => d.properties.unit_id != null),
33
+ });
34
+ }
35
+
36
+ type ColumnProps = { col_id: number };
37
+
38
+ type ColumnFeature = ExtendedFeature<Polygon, ColumnProps>;
39
+
40
+ function processTopoJSON(res) {
41
+ try {
42
+ const { data } = res.success;
43
+ const { features: f } = feature(data, data.objects.output) as any;
44
+ return f;
45
+ } catch (err) {
46
+ console.error(err);
47
+ return [];
48
+ }
49
+ }
50
+ function ColumnFeatures(props) {
51
+ const {
52
+ features,
53
+ onClick,
54
+ color = "rgba(150,150,150, 1)",
55
+ singleFeature = true,
56
+ } = props;
57
+
58
+ const c = chroma(color);
59
+
60
+ return h(
61
+ FeatureLayer,
62
+ {
63
+ className: "columns",
64
+ useCanvas: onClick == null,
65
+ style: {
66
+ fill: c.alpha(0.2).css(),
67
+ stroke: c.alpha(0.4).css(),
68
+ },
69
+ },
70
+ features.map((f) => {
71
+ return h(Feature, {
72
+ id: f.id ?? f.properties.col_id,
73
+ onClick,
74
+ feature: f,
75
+ });
76
+ })
77
+ );
78
+ }
79
+
80
+ enum MacrostratStatusCode {
81
+ InProcess = "in process",
82
+ }
83
+
84
+ interface ColumnNavProps {
85
+ col_id: number;
86
+ status_code?: MacrostratStatusCode;
87
+ project_id?: number;
88
+ onChange(col_id: number): void;
89
+ }
90
+
91
+ interface KeyboardNavProps extends ColumnNavProps {
92
+ features: ColumnFeature[];
93
+ showLayers: boolean;
94
+ }
95
+
96
+ function normalize(angle) {
97
+ if (angle > Math.PI) {
98
+ angle -= 2 * Math.PI;
99
+ } else if (angle <= -Math.PI) {
100
+ angle += 2 * Math.PI;
101
+ }
102
+ return angle;
103
+ }
104
+
105
+ function buildTriangulation(features) {
106
+ console.log("Computing triangulation");
107
+ const centroids = features.map(geoCentroid);
108
+ const tri = geoVoronoi(centroids);
109
+ return { centroids, tri };
110
+ }
111
+
112
+ function buildKeyMapping(neighbors, centroids, currentIndex, projection) {
113
+ if (neighbors == null) return;
114
+
115
+ const currentCentroid = projection(centroids[currentIndex]);
116
+
117
+ let edgeAngles = neighbors.map((index) => {
118
+ const centroid = projection(centroids[index]);
119
+ const dx = centroid[0] - currentCentroid[0];
120
+ const dy = centroid[1] - currentCentroid[1];
121
+ return { col_index: index, angle: Math.atan2(dy, dx) };
122
+ });
123
+
124
+ edgeAngles.sort((d) => d.angle);
125
+
126
+ function closestAngle(num) {
127
+ // Find closest angle in array of neighbors
128
+ let curr = edgeAngles[0];
129
+ for (let next of edgeAngles) {
130
+ if (
131
+ Math.abs(normalize(num - next.angle)) <
132
+ Math.abs(normalize(num - curr.angle))
133
+ ) {
134
+ curr = next;
135
+ }
136
+ }
137
+ return curr.col_index;
138
+ }
139
+
140
+ return {
141
+ 37: closestAngle(Math.PI), // left
142
+ 38: closestAngle((3 * Math.PI) / 2), // up
143
+ 39: closestAngle(0), // right
144
+ 40: closestAngle(Math.PI / 2), // down
145
+ };
146
+ }
147
+
148
+ function ColumnKeyboardNavigation(props: KeyboardNavProps) {
149
+ /** Keyboard navigation of columns using a Delaunay triangulation */
150
+ const {
151
+ features = [],
152
+ col_id = null,
153
+ onChange,
154
+ showLayers = false,
155
+ ...projectArgs
156
+ } = props;
157
+ const { projection } = useContext(MapContext);
158
+ const { centroids, tri } = useMemo(
159
+ () => buildTriangulation(features),
160
+ [features]
161
+ );
162
+
163
+ const currentIndex = useMemo(() => {
164
+ return features.findIndex((d) => d.properties.col_id == col_id);
165
+ }, [features, col_id]);
166
+
167
+ const neighbors = tri.delaunay.neighbors[currentIndex];
168
+
169
+ const keyMapping = useMemo(() => {
170
+ return buildKeyMapping(neighbors, centroids, currentIndex, projection);
171
+ }, [neighbors, currentIndex]);
172
+
173
+ useKeyHandler(
174
+ (event) => {
175
+ const nextColumnIx = keyMapping[event.keyCode];
176
+ if (nextColumnIx == null) return;
177
+ const { col_id } = features[nextColumnIx].properties;
178
+
179
+ // @ts-ignore
180
+ onChange({ col_id, ...projectArgs });
181
+ },
182
+ [keyMapping]
183
+ );
184
+
185
+ if (neighbors == null) return null;
186
+ const neighborFeatures = neighbors.map((d) => features[d]);
187
+
188
+ return h.if(showLayers)([
189
+ h(FeatureLayer, {
190
+ features: tri.links().features,
191
+ useCanvas: false,
192
+ style: {
193
+ stroke: "purple",
194
+ fill: "transparent",
195
+ },
196
+ }),
197
+ h(FeatureLayer, {
198
+ features: neighborFeatures,
199
+ useCanvas: false,
200
+ style: {
201
+ stroke: "rgb(93, 101, 212)",
202
+ strokeWidth: 3,
203
+ fill: "rgba(93, 101, 212, 0.5)",
204
+ },
205
+ }),
206
+
207
+ //h(FeatureLayer, {features: tri.centers, useCanvas: false})
208
+ ]);
209
+ }
210
+
211
+ function processGeoJSON(res) {
212
+ return processGeoJSONBare(res?.success?.data);
213
+ }
214
+
215
+ function processGeoJSONBare(res) {
216
+ return res?.features;
217
+ }
218
+
219
+ const processors = {
220
+ topojson: processTopoJSON,
221
+ geojson: processGeoJSON,
222
+ geojson_bare: processGeoJSONBare,
223
+ };
224
+
225
+ function useColumnData({
226
+ apiRoute = "/columns",
227
+ status_code,
228
+ project_id,
229
+ format = "topojson",
230
+ }) {
231
+ let all: boolean = undefined;
232
+ if (status_code == null && project_id == null) {
233
+ all = true;
234
+ }
235
+
236
+ const processor = processors[format];
237
+
238
+ return useAPIResult(
239
+ apiRoute,
240
+ { format, all, status_code, project_id },
241
+ processor
242
+ );
243
+ }
244
+
245
+ interface ColumnExtraInfo {
246
+ apiRoute?: string;
247
+ color?: string;
248
+ filterColumns?(d: ColumnFeature): boolean;
249
+ showDebugLayers?: boolean;
250
+ format?: "topojson" | "geojson";
251
+ }
252
+
253
+ const Columns = (props: ColumnNavProps & ColumnExtraInfo) => {
254
+ const {
255
+ apiRoute = "/columns",
256
+ onChange,
257
+ col_id = null,
258
+ status_code,
259
+ project_id,
260
+ color,
261
+ filterColumns,
262
+ showDebugLayers = false,
263
+ format = "topojson",
264
+ } = props;
265
+
266
+ let features: any[] = useColumnData({
267
+ apiRoute,
268
+ status_code,
269
+ project_id,
270
+ format,
271
+ }) as any;
272
+
273
+ if (features == null) return null;
274
+
275
+ if (filterColumns != null) {
276
+ features = features.filter(filterColumns);
277
+ }
278
+
279
+ return h([
280
+ h(ColumnKeyboardNavigation, {
281
+ features,
282
+ col_id,
283
+ onChange,
284
+ status_code,
285
+ project_id,
286
+ showLayers: showDebugLayers,
287
+ }),
288
+ h(ColumnFeatures, { features, onClick: onChange, color }),
289
+ ]);
290
+ };
291
+
292
+ const ColumnCenters = (props: ColumnNavProps) => {
293
+ return h(Columns, { apiRoute: "/defs/columns", ...props });
294
+ };
295
+
296
+ const CurrentColumn = (props) => {
297
+ const { feature } = props;
298
+ return h(FeatureLayer, {
299
+ features: [feature],
300
+ style: {
301
+ fill: "rgba(255,0,0,0.4)",
302
+ stroke: "rgba(255,0,0,0.6)",
303
+ strokeWidth: 2,
304
+ },
305
+ });
306
+ };
307
+
308
+ export {
309
+ Columns,
310
+ CurrentColumn,
311
+ processTopoJSON,
312
+ ColumnCenters,
313
+ ColumnFeatures,
314
+ ColumnKeyboardNavigation,
315
+ useColumnData,
316
+ };
@@ -0,0 +1,34 @@
1
+ import h from "@macrostrat/hyper";
2
+ import { ColumnProvider, ColumnAxisType } from "@macrostrat/column-components";
3
+ import { APIProvider } from "@macrostrat/ui-components";
4
+ import { ReactNode } from "react";
5
+
6
+ export function MacrostratColumnProvider(props) {
7
+ // A column provider specialized the Macrostrat API
8
+ return h(ColumnProvider, { axisType: ColumnAxisType.AGE, ...props });
9
+ }
10
+
11
+ type APIProviderProps = {
12
+ children: ReactNode;
13
+ useDev?: boolean;
14
+ baseURL?: string;
15
+ };
16
+
17
+ export function MacrostratAPIProvider({
18
+ children,
19
+ useDev = false,
20
+ baseURL,
21
+ }: APIProviderProps) {
22
+ baseURL ??= useDev
23
+ ? "https://dev2.macrostrat.org/api/v2"
24
+ : "https://macrostrat.org/api/v2";
25
+
26
+ return h(
27
+ APIProvider,
28
+ {
29
+ baseURL,
30
+ unwrapResponse: (res) => res.success.data,
31
+ },
32
+ children
33
+ );
34
+ }
package/src/section.ts ADDED
@@ -0,0 +1,129 @@
1
+ import { CompositeUnitsColumn, IUnit } from "./units";
2
+ import { ReactNode, useMemo } from "react";
3
+ import { AgeAxis } from "./age-axis";
4
+ import { Timescale, TimescaleOrientation } from "@macrostrat/timescale";
5
+ import { ColumnSVG } from "@macrostrat/column-components";
6
+ import { MacrostratColumnProvider } from "./index";
7
+ import hyper from "@macrostrat/hyper";
8
+ import styles from "./column.module.sass";
9
+
10
+ const h = hyper.styled(styles);
11
+
12
+ export interface SectionInfo {
13
+ section_id: number | number[];
14
+ t_age: number;
15
+ b_age: number;
16
+ units: IUnit[];
17
+ }
18
+
19
+ export interface IColumnProps {
20
+ data: IUnit[];
21
+ pixelScale?: number;
22
+ range?: [number, number];
23
+ unitComponent?: React.FunctionComponent<any>;
24
+ unitComponentProps?: any;
25
+ showLabels?: boolean;
26
+ width?: number;
27
+ columnWidth?: number;
28
+ targetUnitHeight?: number;
29
+ children?: ReactNode;
30
+ showLabelColumn?: boolean;
31
+ }
32
+
33
+ export function Section(props: IColumnProps) {
34
+ // Section with "squishy" time scale
35
+ const {
36
+ data,
37
+ range: _range,
38
+ pixelScale: _pixelScale,
39
+ unitComponent,
40
+ showLabels = true,
41
+ targetUnitHeight = 20,
42
+ width = 300,
43
+ columnWidth = 150,
44
+ unitComponentProps,
45
+ showLabelColumn = true,
46
+ } = props;
47
+
48
+ const b_age = Math.max(...data.map((d) => d.b_age));
49
+ const t_age = Math.min(...data.map((d) => d.t_age));
50
+
51
+ const range = useMemo(() => {
52
+ if (_range == null) {
53
+ return [b_age, t_age];
54
+ }
55
+ return _range;
56
+ }, [_range, b_age, t_age]);
57
+
58
+ const dAge = useMemo(() => range[0] - range[1], [range]);
59
+
60
+ const pixelScale = useMemo(() => {
61
+ if (_pixelScale != null) return _pixelScale;
62
+ const targetHeight = targetUnitHeight * data.length;
63
+ return Math.ceil(targetHeight / dAge);
64
+ }, [_pixelScale, targetUnitHeight, data.length, dAge]);
65
+
66
+ const height = useMemo(() => dAge * pixelScale, [dAge, pixelScale]);
67
+
68
+ /** Ensure that we can arrange units into the maximum number
69
+ * of columns defined by unitComponentProps, but that we don't
70
+ * use more than necessary.
71
+ */
72
+ const _unitComponentProps = useMemo(() => {
73
+ return {
74
+ ...unitComponentProps,
75
+ nColumns: Math.min(
76
+ Math.max(...data.map((d) => d.column)) + 1,
77
+ unitComponentProps?.nColumns ?? 2
78
+ ),
79
+ };
80
+ }, [data, unitComponentProps]);
81
+
82
+ return h(
83
+ MacrostratColumnProvider,
84
+ {
85
+ divisions: data,
86
+ range,
87
+ pixelsPerMeter: pixelScale, // Actually pixels per myr
88
+ },
89
+ [
90
+ h(AgeAxis, {
91
+ width: 20,
92
+ padding: 0,
93
+ paddingV: 10,
94
+ showLabel: false,
95
+ }),
96
+ h("div.timescale-container", { style: { marginTop: `10px` } }, [
97
+ h(Timescale, {
98
+ orientation: TimescaleOrientation.VERTICAL,
99
+ length: height,
100
+ levels: timescaleLevels as [number, number],
101
+ absoluteAgeScale: true,
102
+ showAgeAxis: false,
103
+ ageRange: range as [number, number],
104
+ }),
105
+ ]),
106
+ h(
107
+ ColumnSVG,
108
+ {
109
+ innerWidth: showLabels ? width : columnWidth,
110
+ paddingRight: 1,
111
+ paddingLeft: 1,
112
+ paddingV: 10,
113
+ innerHeight: height,
114
+ },
115
+ h(CompositeUnitsColumn, {
116
+ showLabelColumn: showLabelColumn,
117
+ width: showLabels ? width : columnWidth,
118
+ columnWidth,
119
+ gutterWidth: 5,
120
+ showLabels,
121
+ unitComponent,
122
+ unitComponentProps: _unitComponentProps,
123
+ })
124
+ ),
125
+ ]
126
+ );
127
+ }
128
+
129
+ const timescaleLevels = [2, 5];
@@ -0,0 +1,27 @@
1
+ .popover-main
2
+ position: absolute
3
+ pointer-events: none
4
+
5
+ :global(.bp5-overlay)
6
+ pointer-events: all
7
+
8
+ :global(.bp5-popover-target)
9
+ height: 100%
10
+ width: 100%
11
+ display: block
12
+ line-height: 0
13
+ pointer-events: none
14
+ outline: 2px solid #f22
15
+
16
+ .legend-panel-outer
17
+ width: 25em
18
+ max-height: 60vh
19
+ overflow-y: scroll
20
+ background-color: var(--panel-background-color)
21
+
22
+ .legend-info-panel
23
+ padding: 1em
24
+
25
+ .legend-json-view
26
+ padding-left: 0
27
+ margin-left: -0.5em
@@ -0,0 +1,71 @@
1
+ import hyper from "@macrostrat/hyper";
2
+ import { Button, Popover } from "@blueprintjs/core";
3
+ import { DOMElement } from "react";
4
+ import styles from "./selection-popover.module.sass";
5
+ import { useSelectedUnit } from "./units";
6
+ import { UnitDetailsPanel } from "./unit-details";
7
+
8
+ const h = hyper.styled(styles);
9
+
10
+ export function UnitDetailsPopover({
11
+ style,
12
+ children,
13
+ boundary,
14
+ }: {
15
+ style: object;
16
+ children: React.ReactNode;
17
+ boundary?: DOMElement<any, any>;
18
+ }) {
19
+ const content = h(LegendPopoverContainer, children);
20
+
21
+ return h(
22
+ "div.popover-main",
23
+ {
24
+ style,
25
+ },
26
+ h(
27
+ Popover,
28
+ // @ts-ignore
29
+ { content, isOpen: true, usePortal: false, boundary, position: "right" },
30
+ h("span.popover-target")
31
+ )
32
+ );
33
+ }
34
+
35
+ export function LegendPopoverContainer({ children }) {
36
+ return h(
37
+ "div.legend-panel-outer",
38
+ {
39
+ onClick(e) {
40
+ // Stop events from leaking to the parent
41
+ e.stopPropagation();
42
+ },
43
+ },
44
+ [h("div.legend-info-panel", children)]
45
+ );
46
+ }
47
+
48
+ export function UnitSelectionPopover(props) {
49
+ const { position } = props;
50
+ const unit = useSelectedUnit();
51
+ if (unit == null) {
52
+ return null;
53
+ }
54
+
55
+ return h(
56
+ "div.unit-popover-container",
57
+ h(
58
+ UnitDetailsPopover,
59
+ {
60
+ style: {
61
+ position: "absolute",
62
+ top: position?.y,
63
+ width: position?.width,
64
+ left: position?.x,
65
+ height: position?.height,
66
+ },
67
+ },
68
+ h(UnitDetailsPanel, { unit })
69
+ )
70
+ );
71
+ }
package/src/store.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { createContext, useContext } from "react";
2
+ import h from "@macrostrat/hyper";
3
+
4
+ const MacrostratUnitsContext = createContext(null);
5
+
6
+ export function MacrostratUnitsProvider({ children, units }) {
7
+ return h(MacrostratUnitsContext.Provider, { value: units }, children);
8
+ }
9
+
10
+ export function useMacrostratUnits() {
11
+ return useContext(MacrostratUnitsContext);
12
+ }
@@ -0,0 +1,51 @@
1
+ .legend-panel-header
2
+ display: flex
3
+ justify-content: space-between
4
+ align-items: baseline
5
+ gap: 1em
6
+ border-bottom: 1px solid var(--panel-rule-color)
7
+ position: sticky
8
+ top: 0
9
+ background-color: var(--panel-secondary-background-color)
10
+ margin: -1em -1em 0
11
+ padding: 0.5em 1em
12
+ z-index: 50
13
+ code
14
+ font-size: 0.9em
15
+ color: var(--secondary-color)
16
+ .spacer
17
+ flex: 1
18
+ button
19
+ line-height: 1.5
20
+ transform: translateY(2px)
21
+
22
+ h3
23
+ margin: 0 0 0.5em
24
+ font-size: 1.1em
25
+
26
+ .unit-details-content
27
+ position: relative
28
+ margin-top: 1em
29
+ margin-bottom: -0.5em
30
+
31
+ .proportion, .sep, .no-units
32
+ color: var(--secondary-color)
33
+ font-style: italic
34
+ font-size: 0.9em
35
+
36
+ .strat-name-id, .unit-id
37
+ font-family: var(--monospace-font)
38
+ color: var(--secondary-color)
39
+ font-size: 0.9em
40
+
41
+ &::before
42
+ content: '#'
43
+ color: var(--tertiary-color)
44
+
45
+ .color-swatch
46
+ width: 1em
47
+ height: 1em
48
+ border-radius: 0.2em
49
+ margin-right: 0.2em
50
+ border: 1px solid var(--panel-rule-color)
51
+ display: inline-block