Skip to content

Commit 8d3a1dc

Browse files
authored
Merge pull request #348 from UW-Macrostrat/measurements
Measurements tile
2 parents 755e822 + 24968d0 commit 8d3a1dc

File tree

19 files changed

+675
-50
lines changed

19 files changed

+675
-50
lines changed

packages/lithology-hierarchy/src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { apiV2Prefix } from "@macrostrat-web/settings";
44
import { Spinner } from "@blueprintjs/core";
55
import { useAPIResult, ErrorCallout } from "@macrostrat/ui-components";
66
import { useState } from "react";
7-
import { nestLiths, Lith } from "./nest-data";
7+
import { nestLiths, nestItems, Lith } from "./nest-data";
88
import Hierarchy from "./simple-hierarchy";
9+
import LexHierarchyInner from "./lex-hierarchy";
910

1011
const h = hyper.styled(styles);
1112

@@ -35,3 +36,13 @@ export default function MacrostratLithologyHierarchy({ width, height }) {
3536
]),
3637
]);
3738
}
39+
40+
export function LexHierarchy({ width, height, data, href = null, onClick = () => {} }: { width: string | number; height: string | number; data: Lith[]; href?: string | null; onClick?: () => void }) {
41+
const nestedData = nestItems(data);
42+
43+
return h("div.flex.row", [
44+
h("div.example-container", [
45+
h(LexHierarchyInner, { width, height, data: nestedData, href, onClick }),
46+
]),
47+
]);
48+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import hyper from "@macrostrat/hyper";
2+
import styles from "./main.module.sass";
3+
import { TreeNodeData } from "./nest-data";
4+
import { LithologyTag } from "@macrostrat/data-components";
5+
import React, {useMemo} from "react"
6+
7+
const h = hyper.styled(styles);
8+
9+
export default function LexHierarchyInner({ data, href, onClick }: { data: TreeNodeData; href: string; onClick: () => void }) {
10+
return h(Tree, { data, level: 0, href, onClick });
11+
}
12+
13+
const Tree = React.memo(function Tree({
14+
data,
15+
level = 0,
16+
href,
17+
onClick,
18+
}: {
19+
data: TreeNodeData;
20+
level: number;
21+
href: string;
22+
onClick: () => void;
23+
}) {
24+
const headerEl = "h" + (level + 2);
25+
26+
const [subTrees, nodes] = useMemo(() => divideChildren(data), [data]);
27+
28+
return h("div.tree", { className: `tree-level-${level}` }, [
29+
h("div.main-tree", [
30+
h.if(data.children != null)(headerEl, capitalize(data.name)),
31+
h.if(nodes.length > 0)(
32+
"div.nodes",
33+
nodes.map((d) =>
34+
h("div.node", { key: d.name }, [
35+
h(LithologyTag, {
36+
data: d.lith ?? d,
37+
href,
38+
onClick,
39+
}),
40+
])
41+
)
42+
),
43+
]),
44+
subTrees.map((d) =>
45+
h(Tree, {
46+
key: d.name,
47+
data: d,
48+
level: level + 1,
49+
href,
50+
onClick,
51+
})
52+
),
53+
]);
54+
});
55+
56+
57+
function capitalize(s: string) {
58+
return s.charAt(0).toUpperCase() + s.slice(1);
59+
}
60+
61+
function divideChildren(data: TreeNodeData) {
62+
/** Divide children into terminal and non-terminal nodes */
63+
const terminal = [];
64+
const nonTerminal = [];
65+
const { children = [] } = data;
66+
for (const child of children) {
67+
const len = child.children?.length ?? 0;
68+
if (len == 0) {
69+
terminal.push(child);
70+
} else {
71+
nonTerminal.push(child);
72+
}
73+
}
74+
return [nonTerminal, terminal];
75+
}

packages/lithology-hierarchy/src/main.module.sass

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,9 @@
3737
li
3838
display: inline-block
3939
margin-right: 0.5em
40-
line-height: 2em
40+
line-height: 2em
41+
42+
.nodes
43+
display: flex
44+
gap: .5em
45+
flex-wrap: wrap

packages/lithology-hierarchy/src/nest-data.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,88 @@ function convert(data: TreeNodeMap): TreeNodeData {
115115
children: Array.from(data.children.values()).map(convert),
116116
};
117117
}
118+
119+
120+
export function nestItems(liths: Lith[]): TreeNodeData {
121+
const root: TreeNodeMap = { name: "Rocks", children: new Map() };
122+
// Ensure that empty strings are treated as null
123+
for (let lith of liths) {
124+
for (const key of ["type", "group", "class"]) {
125+
if (lith[key] === "") lith[key] = null;
126+
}
127+
}
128+
129+
for (let lith of liths) {
130+
if (lith.class == null || lith.type == null)
131+
console.error(lith, "Class and type should never be null");
132+
if (lith.class == null) console.log(lith.name, "Class is null");
133+
if (lith.type == null) console.log(lith.name, "Type is null");
134+
135+
// Create a class if it doesn't exist
136+
if (lith.class != null) {
137+
if (!root.children.has(lith.class)) {
138+
root.children.set(lith.class, {
139+
name: lith.class,
140+
lith,
141+
children: new Map<string, TreeNodeMap>(),
142+
});
143+
}
144+
} else {
145+
if (!root.children.has(lith.name)) {
146+
root.children.set(lith.name, { name: lith.name, lith });
147+
}
148+
}
149+
150+
// Add the type to the class
151+
if (lith.class != null && lith.type != null) {
152+
const parent = root.children.get(lith.class);
153+
if (!parent.children.has(lith.type)) {
154+
parent.children.set(lith.type, {
155+
name: lith.type,
156+
children: new Map<string, TreeNodeMap>(),
157+
lith,
158+
});
159+
}
160+
}
161+
162+
// Add the group to the type
163+
if (lith.class != null && lith.type != null) {
164+
if (lith.group != null) {
165+
const parent = root.children.get(lith.class);
166+
const grandparent = parent.children.get(lith.type);
167+
if (!grandparent.children.has(lith.group)) {
168+
grandparent.children.set(lith.group, {
169+
name: lith.group,
170+
children: new Map<string, TreeNodeMap>(),
171+
});
172+
}
173+
} else {
174+
const parent = root.children.get(lith.class);
175+
const grandparent = parent.children.get(lith.type);
176+
if (!grandparent.children.has(lith.name)) {
177+
grandparent.children.set(lith.name, { name: lith.name, lith });
178+
}
179+
}
180+
}
181+
182+
// Add the lithology to the group
183+
if (
184+
lith.class != null &&
185+
lith.type != null &&
186+
lith.group != null &&
187+
lith.name != null
188+
) {
189+
const parent = root.children.get(lith.class);
190+
const grandparent = parent.children.get(lith.type);
191+
const greatgrandparent = grandparent.children.get(lith.group);
192+
greatgrandparent.children.set(lith.name, {
193+
name: lith.name,
194+
lith,
195+
children: new Map<string, TreeNodeMap>(),
196+
});
197+
}
198+
}
199+
200+
// Export to TreeNode format
201+
return convert(root);
202+
}

pages/lex/+Page.client.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ export function Page() {
123123
{ href: "/lex/fossils", title: "Fossils" },
124124
"Fossil taxonomic occurrences from the Paleobiology Database linked to Macrostrat units"
125125
),
126+
h(
127+
LinkCard,
128+
{ href: "/lex/measurements", title: "Measurements" },
129+
"Measurement names and descriptions"
130+
),
126131
h("p", [
127132
h("strong", h("a", { href: "/sift" }, "Sift")),
128133
", Macrostrat's legacy lexicon app, is still available for use as it is gradually brought into this new framework.",
Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { useState } from "react";
44
import { ContentPage } from "~/layouts";
55
import { useData } from "vike-react/useData";
66
import { SearchBar } from "~/components/general";
7-
import { LithologyTag } from "~/components/lex/tag";
7+
import { LexHierarchy } from "@macrostrat-web/lithology-hierarchy";
8+
import { navigate } from "vike/client/router";
89

910
export function Page() {
1011
const { res } = useData();
@@ -27,8 +28,6 @@ export function LexListPage({ res, title, route, id }) {
2728
);
2829
});
2930

30-
const grouped = groupByClassThenType(filtered);
31-
3231
return h(ContentPage, { className: "econ-list-page" }, [
3332
h(StickyHeader, [
3433
h(PageBreadcrumbs, { title }),
@@ -37,46 +36,6 @@ export function LexListPage({ res, title, route, id }) {
3736
onChange: handleChange,
3837
}),
3938
]),
40-
h(
41-
"div.econ-list",
42-
Object.entries(grouped).map(([className, types]) =>
43-
h("div.econ-class-group", [
44-
h("h2", UpperCase(className)),
45-
...Object.entries(types).map(([type, group]) =>
46-
h("div.econ-group", [
47-
h("h3", UpperCase(type)),
48-
h(
49-
"div.econ-items",
50-
group.map((d) => h(LithologyTag, { data: d, href: `/lex/${route}/${d[id]}` }))
51-
),
52-
])
53-
),
54-
])
55-
)
56-
),
39+
h(LexHierarchy, { data: filtered, onClick: (e, item) => navigate(`/lex/${route}/${item[id]}`) }),
5740
]);
58-
}
59-
60-
function groupByClassThenType(items) {
61-
return items.reduce((acc, item) => {
62-
const { class: className, type } = item;
63-
64-
if (!type || type.trim() === "") {
65-
return acc;
66-
}
67-
68-
if (!acc[className]) {
69-
acc[className] = {};
70-
}
71-
if (!acc[className][type]) {
72-
acc[className][type] = [];
73-
}
74-
75-
acc[className][type].push(item);
76-
return acc;
77-
}, {});
78-
}
79-
80-
function UpperCase(str) {
81-
return str.charAt(0).toUpperCase() + str.slice(1);
82-
}
41+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import h from "@macrostrat/hyper";
22
import { useData } from "vike-react/useData";
3-
import { LexListPage } from "../economics/+Page";
3+
import { LexListPage } from "../economics/+Page.client";
44

55
export function Page() {
66
const { res } = useData();

0 commit comments

Comments
 (0)