Skip to content

Commit 3cd32d3

Browse files
committed
Merge branch 'main' into build-fix
2 parents d51eb8d + db327a3 commit 3cd32d3

File tree

25 files changed

+1299
-240
lines changed

25 files changed

+1299
-240
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@
6464
"vite": "^5.4.11"
6565
},
6666
"resolutions": {
67-
"@types/react": "18.3.12"
67+
"@types/react": "18.3.12",
68+
"react": "^18.3.1",
69+
"react-dom": "^18.3.1"
6870
},
6971
"workspaces": [
7072
"packages/*",

packages/column-components/src/notes/connector.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const NotePositioner = forwardRef(function (
3131
x: paddingLeft - outerPad,
3232
y,
3333
height: noteHeight + 2 * outerPad,
34+
overflow: "visible",
3435
style: { overflowY: "visible" },
3536
},
3637
[

packages/column-components/src/notes/note.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ export function NotesList(props: NoteListProps) {
6868
return h(
6969
"g",
7070
notesInfo.map(({ note, pixelOffset, pixelHeight, spacing }) => {
71+
// If the note has a bad pixelOffset, skip it
72+
73+
if (pixelOffset == null || pixelHeight == null) {
74+
return null;
75+
}
76+
7177
return h(Note, {
7278
key: note.id,
7379
note,
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"name": "@macrostrat/column-creator",
3+
"version": "0.0.1",
4+
"description": "Interface for defining stratigraphic columns in a standardized format",
5+
"type": "module",
6+
"source": "src/index.ts",
7+
"main": "dist/esm/index.js",
8+
"types": "dist/esm/index.d.ts",
9+
"node": "dist/node/index.js",
10+
"sideEffects": [
11+
"**/*.css"
12+
],
13+
"exports": {
14+
".": {
15+
"source": "./src/index.ts",
16+
"import": "./dist/esm/index.js",
17+
"types": "./dist/esm/index.d.ts",
18+
"node": "./dist/node/index.js"
19+
}
20+
},
21+
"targets": {
22+
"node": {
23+
"engines": {
24+
"node": ">=14"
25+
}
26+
}
27+
},
28+
"scripts": {
29+
"build": "rm -rf dist && parcel build"
30+
},
31+
"dependencies": {
32+
"@macrostrat/column-components": "workspace:^",
33+
"@macrostrat/column-views": "workspace:^",
34+
"@macrostrat/hyper": "^3.0.6",
35+
"zustand": "^5.0.3",
36+
"zustand-computed": "^2.0.2"
37+
},
38+
"peerDependencies": {
39+
"react": "^16.8.6||^17.0.0||^18.0.0"
40+
},
41+
"repository": {
42+
"type": "git",
43+
"url": "https://github.com/UW-Macrostrat/web-components.git",
44+
"directory": "packages/column-creator"
45+
},
46+
"devDependencies": {
47+
"parcel": "^2.14.4"
48+
},
49+
"files": [
50+
"dist",
51+
"src"
52+
]
53+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
div.data-editor-content
2+
width: 800px
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
import {
2+
ColumnCreatorData,
3+
ColumnCreatorProvider,
4+
useColumnCreatorStore,
5+
useSelector,
6+
} from "./store";
7+
import { ColorCell, ColorPicker, DataSheet } from "@macrostrat/data-sheet";
8+
9+
export * from "./store";
10+
import { FlexRow, ToasterContext, useToaster } from "@macrostrat/ui-components";
11+
import {
12+
BasicUnitComponent,
13+
Column,
14+
ColumnNotes,
15+
} from "@macrostrat/column-views";
16+
import hyper from "@macrostrat/hyper";
17+
import { asChromaColor } from "@macrostrat/color-utils";
18+
import { Radio, RadioGroup, SegmentedControl } from "@blueprintjs/core";
19+
import { useState } from "react";
20+
21+
import styles from "./index.module.sass";
22+
import Box from "ui-box";
23+
24+
const h = hyper.styled(styles);
25+
26+
export function ColumnCreator({ data }: { data: ColumnCreatorData }) {
27+
return h(
28+
ToasterContext,
29+
h(
30+
ColumnCreatorProvider,
31+
{ initialData: data },
32+
h("div.column-creator-test", [
33+
h(FlexRow, { gap: "2em", alignItems: "baseline" }, [
34+
h(Box, { flex: 2 }, [
35+
h("h2", "Column creator"),
36+
h("p", [
37+
"This simple column creator shows the minimal data structure needed to graphically define a Macrostrat-like geologic column using ",
38+
h("em", "surfaces"),
39+
" and ",
40+
h("em", "units"),
41+
".",
42+
]),
43+
h("p", [
44+
"Splitting column datasets into two elements allows columns depicting complex lateral relationships (e.g., from stratigraphic charts) to be captured faithfully.",
45+
]),
46+
h("h3", "How to use"),
47+
h("ol", [
48+
h("li", "Start by adding surfaces at a range of heights."),
49+
h("li", [
50+
"Then, link units to surfaces to define elements. You can add patterns using ",
51+
h(
52+
"a",
53+
{ href: "https://davenquinn.com/projects/geologic-patterns" },
54+
"FGDC geologic patterns"
55+
),
56+
".",
57+
]),
58+
]),
59+
]),
60+
h(Box, { flex: 1 }, [
61+
h("h3", "Todo"),
62+
h("ul", [
63+
h("li", "Unit selection and highlighting"),
64+
h("li", "Data saving and loading (file and Macrostrat API)"),
65+
h("li", "Chronostratigraphy"),
66+
h(
67+
"li",
68+
"Auto-generation of units from surfaces for simple columns"
69+
),
70+
h("li", "Additional unit metadata (e.g., lithology, etc.)"),
71+
h("li", "Unit nesting/hierarchy"),
72+
]),
73+
]),
74+
]),
75+
h(FlexRow, { gap: "2em" }, [
76+
h(ColumnCreatorColumn),
77+
h(ColumnCreatorDataEditor),
78+
]),
79+
])
80+
)
81+
);
82+
}
83+
84+
function ColumnCreatorColumn() {
85+
const units = useSelector((state) => state.realizedUnits);
86+
const axisType = useSelector((state) => state.info.axisType);
87+
const surfaces = useSelector((state) => state.data.surfaces);
88+
89+
return h(
90+
Column,
91+
{
92+
units: units.filter((u) => u.errors.length == 0),
93+
axisType,
94+
pixelScale: 0.8,
95+
allowUnitSelection: false,
96+
unitComponent: BasicUnitComponent,
97+
showLabelColumn: false,
98+
},
99+
h(ColumnSurfacesLayer, { surfaces })
100+
);
101+
}
102+
103+
function ColumnSurfacesLayer({ surfaces }) {
104+
const notes = surfaces
105+
.filter((d) => d.height != null)
106+
.map((s) => {
107+
return {
108+
id: s.id,
109+
note: s.id,
110+
height: s.height,
111+
};
112+
});
113+
114+
return h(
115+
"div.column-surfaces-layer",
116+
h(ColumnNotes, {
117+
notes,
118+
paddingLeft: 30,
119+
})
120+
);
121+
}
122+
123+
enum EditingType {
124+
SURFACES = "surfaces",
125+
UNITS = "units",
126+
}
127+
128+
function EditingTypeSelector({ value, onChange }) {
129+
return h(SegmentedControl, {
130+
fill: false,
131+
options: [
132+
{ label: "Surfaces", value: EditingType.SURFACES },
133+
{ label: "Units", value: EditingType.UNITS },
134+
],
135+
onValueChange: (value) => onChange(value),
136+
value: value,
137+
className: "editing-type-selector",
138+
});
139+
}
140+
141+
function ColumnCreatorDataEditor() {
142+
const [editingType, setEditingType] = useState(EditingType.SURFACES);
143+
144+
return h(Box, { display: "flex", flexDirection: "column", gap: "1em" }, [
145+
h(FlexRow, { alignItems: "baseline", gap: "2em" }, [
146+
h("h3", "Column editor"),
147+
h(EditingTypeSelector, {
148+
value: editingType,
149+
onChange: (value) => setEditingType(value),
150+
}),
151+
h("div.spacer", { flex: 1 }),
152+
]),
153+
h("div.data-editor-content", [
154+
h(
155+
VisibilityToggle,
156+
{
157+
show: editingType === EditingType.UNITS,
158+
},
159+
h(ColumnCreatorUnitsEditor)
160+
),
161+
h(
162+
VisibilityToggle,
163+
{
164+
show: editingType === EditingType.SURFACES,
165+
},
166+
h(ColumnCreatorSurfacesEditor)
167+
),
168+
]),
169+
]);
170+
}
171+
172+
function VisibilityToggle({ children, show }) {
173+
// Toggle visibility of children based on the show prop
174+
return h(
175+
"div.visibility-toggle",
176+
{
177+
style: {
178+
display: show ? "block" : "none",
179+
},
180+
},
181+
children
182+
);
183+
}
184+
185+
function ColumnCreatorSurfacesEditor() {
186+
const surfaces = useSelector((state) => state.initialData.surfaces);
187+
const setSurfaces = useSelector((state) => state.setSurfaces);
188+
189+
return h(DataSheet, {
190+
data: surfaces,
191+
columnSpec: [
192+
{
193+
name: "ID",
194+
key: "id",
195+
required: true,
196+
},
197+
{
198+
name: "Height",
199+
key: "height",
200+
required: true,
201+
},
202+
],
203+
onUpdateData: (updatedData, data) => {
204+
setSurfaces(reconstructData(data, updatedData));
205+
},
206+
});
207+
}
208+
209+
function ColumnCreatorUnitsEditor() {
210+
const units = useSelector((state) => state.initialData.units);
211+
const setUnits = useSelector((state) => state.setUnits);
212+
213+
// Sort units by their bottom position
214+
215+
return h(DataSheet, {
216+
data: units,
217+
columnSpec: [
218+
{
219+
name: "Bottom",
220+
key: "b_surface",
221+
},
222+
{
223+
name: "Top",
224+
key: "t_surface",
225+
},
226+
{ name: "Name", key: "name" },
227+
228+
{
229+
name: "Color",
230+
key: "color",
231+
required: false,
232+
isValid: (d) => asChromaColor(d) != null,
233+
transform: (d) => d,
234+
dataEditor: ColorPicker,
235+
valueRenderer: (d) => {
236+
const color = asChromaColor(d);
237+
return color?.name() ?? "";
238+
},
239+
cellComponent: ColorCell,
240+
},
241+
{
242+
name: "Pattern",
243+
key: "pattern",
244+
},
245+
],
246+
onUpdateData: (updatedData, data) => {
247+
// Update the units in the store
248+
const newData = reconstructData(data, updatedData);
249+
setUnits(newData);
250+
},
251+
});
252+
}
253+
254+
function reconstructData<T>(data: T[], updates: Partial<T>[]): T[] {
255+
/** Reconstructs the data array by merging updates into the existing data. */
256+
const newData = Array(Math.max(data.length, updates.length));
257+
258+
for (let i = 0; i < newData.length; i++) {
259+
const d = updates[i];
260+
let newRow: object = (data[i] as object) ?? {};
261+
if (d != null) {
262+
newRow = { ...newRow, ...d };
263+
}
264+
newData[i] = newRow;
265+
}
266+
return newData;
267+
}
268+
269+
function ColumnBasicElementsEditor() {
270+
const info = useSelector((state) => state.info);
271+
const updateInfo = useSelector((state) => state.updateInfo);
272+
273+
return h("div.column-basic-elements-editor", [
274+
h(
275+
RadioGroup,
276+
{
277+
label: "Axis type",
278+
inline: true,
279+
selectedValue: info.axisType,
280+
onChange(evt) {
281+
updateInfo({ axisType: { $set: evt.target.value } });
282+
},
283+
},
284+
[
285+
h(Radio, {
286+
label: "Height",
287+
value: "height",
288+
}),
289+
h(Radio, {
290+
label: "Age",
291+
value: "age",
292+
}),
293+
]
294+
),
295+
]);
296+
}

0 commit comments

Comments
 (0)