chrono-phylo-tree 1.0.16 → 1.1.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 (66) hide show
  1. package/eslint.config.js +28 -0
  2. package/global.d.ts +26 -0
  3. package/index.html +13 -0
  4. package/package.json +3 -7
  5. package/src/App.css +131 -0
  6. package/src/App.tsx +149 -0
  7. package/src/assets/react.svg +1 -0
  8. package/src/classes/Species.tsx +267 -0
  9. package/src/components/HoverDescription.tsx +27 -0
  10. package/src/components/LanguageSelector.tsx +29 -0
  11. package/src/components/Menu.tsx +348 -0
  12. package/src/components/NavBar.tsx +234 -0
  13. package/src/components/PhTree.tsx +362 -0
  14. package/src/index.css +68 -0
  15. package/src/index.ts +4 -0
  16. package/src/main.tsx +10 -0
  17. package/src/types.d.ts +9 -0
  18. package/src/utils/between.tsx +3 -0
  19. package/src/utils/example.tsx +73 -0
  20. package/src/utils/hexToRGBA.tsx +12 -0
  21. package/src/utils/scientificNotation.tsx +20 -0
  22. package/src/utils/setFromJson.tsx +40 -0
  23. package/src/utils/translate.tsx +68 -0
  24. package/src/utils/updateSpecies.tsx +129 -0
  25. package/src/vite-env.d.ts +1 -0
  26. package/tsconfig.app.json +26 -0
  27. package/tsconfig.json +17 -0
  28. package/tsconfig.node.json +24 -0
  29. package/vite.config.ts +34 -0
  30. package/dist/App.d.ts +0 -3
  31. package/dist/App.d.ts.map +0 -1
  32. package/dist/chrono-phylo-tree.js +0 -1111
  33. package/dist/chrono-phylo-tree.umd.cjs +0 -15
  34. package/dist/classes/Species.d.ts +0 -35
  35. package/dist/classes/Species.d.ts.map +0 -1
  36. package/dist/components/HoverDescription.d.ts +0 -15
  37. package/dist/components/HoverDescription.d.ts.map +0 -1
  38. package/dist/components/LanguageSelector.d.ts +0 -9
  39. package/dist/components/LanguageSelector.d.ts.map +0 -1
  40. package/dist/components/Menu.d.ts +0 -15
  41. package/dist/components/Menu.d.ts.map +0 -1
  42. package/dist/components/NavBar.d.ts +0 -33
  43. package/dist/components/NavBar.d.ts.map +0 -1
  44. package/dist/components/PhTree.d.ts +0 -31
  45. package/dist/components/PhTree.d.ts.map +0 -1
  46. package/dist/index.d.ts +0 -5
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/main.d.ts +0 -1
  49. package/dist/main.d.ts.map +0 -1
  50. package/dist/utils/between.d.ts +0 -2
  51. package/dist/utils/between.d.ts.map +0 -1
  52. package/dist/utils/example.d.ts +0 -3
  53. package/dist/utils/example.d.ts.map +0 -1
  54. package/dist/utils/hexToRGBA.d.ts +0 -2
  55. package/dist/utils/hexToRGBA.d.ts.map +0 -1
  56. package/dist/utils/scientificNotation.d.ts +0 -2
  57. package/dist/utils/scientificNotation.d.ts.map +0 -1
  58. package/dist/utils/setFromJson.d.ts +0 -3
  59. package/dist/utils/setFromJson.d.ts.map +0 -1
  60. package/dist/utils/translate.d.ts +0 -4
  61. package/dist/utils/translate.d.ts.map +0 -1
  62. package/dist/utils/updateSpecies.d.ts +0 -7
  63. package/dist/utils/updateSpecies.d.ts.map +0 -1
  64. /package/{dist → public}/logo.png +0 -0
  65. /package/{dist → public}/translate.csv +0 -0
  66. /package/{dist → public}/vite.svg +0 -0
@@ -0,0 +1,362 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Species } from "../classes/Species";
3
+
4
+ interface MultiplePhTreesProps {
5
+ speciesList: Species[];
6
+ width?: number;
7
+ height?: number;
8
+ padding?: number;
9
+ stroke?: string;
10
+ format?: (n: number) => string;
11
+ chronoScale?: boolean;
12
+ showImages?: boolean;
13
+ presentTime?: number;
14
+ handleMouseMove?: (x: number, y: number) => void;
15
+ children?: (
16
+ species: Species | undefined,
17
+ showMenu: boolean,
18
+ toggleShowMenu: (species: Species) => void,
19
+ hoverSpecies: Species | undefined
20
+ ) => any;
21
+ }
22
+
23
+ export const MultiplePhTrees = (
24
+ {
25
+ speciesList,
26
+ width = 1000,
27
+ height = 50,
28
+ padding = 0,
29
+ stroke = "grey",
30
+ format = (n) => n.toString(),
31
+ chronoScale = true,
32
+ showImages = true,
33
+ presentTime,
34
+ handleMouseMove,
35
+ children
36
+ }: MultiplePhTreesProps
37
+ ) => {
38
+ const copies = speciesList.map(sp => sp.copy());
39
+ const lifeApparition = Math.min(...copies.map(sp => sp.apparition));
40
+ const lifeApparitionDuration = Math.max(...copies.map(sp => sp.apparition - lifeApparition));
41
+ const life = new Species(
42
+ "",
43
+ lifeApparition,
44
+ lifeApparitionDuration,
45
+ undefined,
46
+ []
47
+ );
48
+ life.display = false;
49
+ life.linkDescendants(copies);
50
+ return (
51
+ <PhTree
52
+ commonAncestor={life}
53
+ width={width}
54
+ height={height}
55
+ padding={padding}
56
+ stroke={stroke}
57
+ format={format}
58
+ chronoScale={chronoScale}
59
+ showImages={showImages}
60
+ presentTime={presentTime}
61
+ handleMouseMove={handleMouseMove}
62
+ >
63
+ {(species, showMenu, toggleShowMenu, hoverSpecies) => children?.(species, showMenu, toggleShowMenu, hoverSpecies)}
64
+ </PhTree>
65
+ );
66
+ };
67
+
68
+ interface PhTreeProps {
69
+ commonAncestor: Species;
70
+ width?: number;
71
+ height?: number;
72
+ padding?: number;
73
+ stroke?: string;
74
+ format?: (n: number) => string;
75
+ chronoScale?: boolean;
76
+ showImages?: boolean;
77
+ presentTime?: number;
78
+ handleMouseMove?: (x: number, y: number) => void;
79
+ children?: (
80
+ species: Species | undefined,
81
+ showMenu: boolean,
82
+ toggleShowMenu: (species: Species) => void,
83
+ hoverSpecies: Species | undefined
84
+ ) => any;
85
+ }
86
+
87
+ export const PhTree = (
88
+ {
89
+ commonAncestor,
90
+ width = 1000,
91
+ height = 50,
92
+ padding = 0,
93
+ stroke = "grey",
94
+ format = (n) => n.toString(),
95
+ chronoScale = true,
96
+ showImages = true,
97
+ presentTime,
98
+ handleMouseMove,
99
+ children
100
+ }: PhTreeProps
101
+ ) => {
102
+ const [showMenu, setShowMenu] = useState(false);
103
+ const [species, setSpecies] = useState<Species | undefined>(undefined);
104
+ const [hoverSpecies, setHoverSpecies] = useState<Species | undefined>(undefined);
105
+ const [showDesc, setShowDesc] = useState<Map<Species, boolean>>(commonAncestor.allDescendants().reduce((acc, desc) => acc.set(desc, true), new Map()));
106
+
107
+ useEffect(() => {
108
+ setShowDesc(commonAncestor.allDescendants().reduce((acc, desc) => acc.set(desc, true), new Map()));
109
+ }, [commonAncestor]);
110
+
111
+ const toggleShowMenu = (sp: Species) => {
112
+ setSpecies(showMenu ? undefined : sp);
113
+ setShowMenu(!showMenu);
114
+ };
115
+
116
+ const toggleShowDesc = (sp: Species) => {
117
+ const newShowDesc = new Map(showDesc);
118
+ newShowDesc.set(sp, !showDesc.get(sp));
119
+ setShowDesc(newShowDesc);
120
+ };
121
+
122
+ const hoverShowMenu = (sp: Species | undefined) => {
123
+ setHoverSpecies(sp);
124
+ };
125
+
126
+ const isPresentTimeDefined = presentTime !== undefined && chronoScale;
127
+ const heightFactor = (ad: Species[]) => showImages ? ad.map<number>(sp => sp.image ? 2 : 1).reduce((a, b) => a + b) : ad.length;
128
+
129
+ return (
130
+ <>
131
+ <svg
132
+ width={width * (isPresentTimeDefined ? (Math.min(presentTime, commonAncestor.absoluteExtinction()) - commonAncestor.apparition) / commonAncestor.absoluteDuration() : 1)}
133
+ height={height * heightFactor(isPresentTimeDefined ? commonAncestor.allDescendants().filter(desc => desc.apparition < presentTime) : commonAncestor.allDescendants())}
134
+ onMouseMove={(event) => {handleMouseMove?.(event.clientX, event.clientY)}}
135
+ >
136
+ <DrawTree
137
+ commonAncestor={commonAncestor}
138
+ species={commonAncestor}
139
+ y={-1}
140
+ scaleX={width / (chronoScale ? commonAncestor.absoluteDuration() : (Math.max(0, commonAncestor.stepsUntilLastDescendant()) + 1))}
141
+ scaleY={height}
142
+ padding={padding}
143
+ stroke={stroke}
144
+ format={format}
145
+ chronoScale={chronoScale}
146
+ showImages={showImages}
147
+ presentTime={presentTime}
148
+ toggleShowMenu={toggleShowMenu}
149
+ hoverShowMenu={hoverShowMenu}
150
+ showDesc={showDesc}
151
+ changeShowDesc={toggleShowDesc}
152
+ />
153
+ </svg>
154
+ {children?.(species, showMenu, toggleShowMenu, hoverSpecies)}
155
+ </>
156
+ );
157
+ };
158
+
159
+ interface DrawTreeProps {
160
+ commonAncestor: Species;
161
+ species: Species;
162
+ y: number;
163
+ scaleX: number;
164
+ scaleY: number;
165
+ padding?: number;
166
+ stroke?: string;
167
+ format?: (n: number) => string;
168
+ chronoScale?: boolean;
169
+ showImages?: boolean;
170
+ presentTime?: number;
171
+ toggleShowMenu: (s: Species) => void;
172
+ hoverShowMenu: (s: Species | undefined) => void;
173
+ showDesc: Map<Species, boolean>;
174
+ changeShowDesc: (s: Species) => void;
175
+ }
176
+
177
+ const DrawTree = ({
178
+ commonAncestor,
179
+ species,
180
+ y,
181
+ scaleX,
182
+ scaleY,
183
+ padding = 0,
184
+ stroke = "grey",
185
+ format = (n: number) => n.toString(),
186
+ chronoScale = true,
187
+ showImages = true,
188
+ presentTime = undefined,
189
+ toggleShowMenu,
190
+ hoverShowMenu,
191
+ showDesc,
192
+ changeShowDesc
193
+ }: DrawTreeProps) => {
194
+ const isPresentTimeDefined = presentTime !== undefined && chronoScale;
195
+ const all = commonAncestor.allDescendants().filter(desc => isPresentTimeDefined ? desc.apparition < presentTime : true);
196
+ const spIndex = all.indexOf(species);
197
+ const startX = (chronoScale ? species.apparition - commonAncestor.apparition : (commonAncestor.stepsUntil(species) ?? 0)) * scaleX;
198
+ const endX = startX + (chronoScale ? Math.min(showDesc.get(species) ? species.duration : species.absoluteDuration(), isPresentTimeDefined ? presentTime - species.apparition : species.absoluteDuration()) : 1) * scaleX;
199
+ const endY = (showImages && spIndex > 0) ? [...Array(spIndex).keys()].map(i => all[i]).map(sp => (sp.image ? 2 : 1) * scaleY).reduce((a, b) => a + b) : (spIndex * scaleY);
200
+ const descendants = species.descendants.filter(desc => isPresentTimeDefined ? desc.apparition < presentTime : true);
201
+ const branchX = descendants.length > 0 ? startX + (Math.min(...descendants.map(desc => desc.apparition)) - species.apparition) * scaleX : endX;
202
+
203
+ return (
204
+ <g key={spIndex}>
205
+ {y >= 0 && (
206
+ <line
207
+ x1={startX}
208
+ y1={y}
209
+ x2={startX}
210
+ y2={endY}
211
+ stroke={stroke}
212
+ />
213
+ )}
214
+ {species.display && <HorizontalLine
215
+ commonAncestor={commonAncestor}
216
+ species={species}
217
+ height={scaleY}
218
+ x1={startX}
219
+ x2={endX}
220
+ x0={branchX}
221
+ y={endY}
222
+ stroke={stroke}
223
+ changeShowDesc={() => changeShowDesc(species)}
224
+ showDesc={showDesc.get(species)}
225
+ padding={padding}
226
+ format={format}
227
+ chronoScale={chronoScale}
228
+ showImages={showImages}
229
+ presentTime={presentTime}
230
+ toggleShowMenu={toggleShowMenu}
231
+ hoverShowMenu={hoverShowMenu}
232
+ />}
233
+ {species.descendants.map((desc, index) => (
234
+ <g className={(showDesc.get(species) && descendants.includes(desc)) ? "block" : "hidden"} key={all.length + index}>
235
+ <DrawTree
236
+ commonAncestor={commonAncestor}
237
+ species={desc}
238
+ y={desc.display ? endY : -1}
239
+ scaleX={scaleX}
240
+ scaleY={scaleY}
241
+ padding={padding}
242
+ stroke={stroke}
243
+ format={format}
244
+ chronoScale={chronoScale}
245
+ showImages={showImages}
246
+ presentTime={presentTime}
247
+ toggleShowMenu={toggleShowMenu}
248
+ showDesc={showDesc}
249
+ changeShowDesc={changeShowDesc}
250
+ hoverShowMenu={hoverShowMenu}
251
+ />
252
+ </g>
253
+ ))}
254
+ </g>
255
+ );
256
+ };
257
+
258
+ interface HorizontalLineProps {
259
+ commonAncestor: Species;
260
+ species: Species;
261
+ height: number;
262
+ x1: number;
263
+ x2: number;
264
+ x0?: number;
265
+ y: number;
266
+ stroke: string;
267
+ showDesc?: boolean;
268
+ changeShowDesc?: () => void;
269
+ padding?: number;
270
+ format?: (n: number) => string;
271
+ chronoScale?: boolean;
272
+ showImages?: boolean;
273
+ presentTime?: number;
274
+ toggleShowMenu: (s: Species) => void;
275
+ hoverShowMenu: (s: Species | undefined) => void;
276
+ className?: string;
277
+ buttonClassName?: string;
278
+ }
279
+
280
+ const HorizontalLine = ({
281
+ commonAncestor,
282
+ species,
283
+ height,
284
+ x1, x2, x0, y,
285
+ stroke,
286
+ showDesc = true,
287
+ changeShowDesc = () => {},
288
+ padding = 0,
289
+ format = (n) => n.toString(),
290
+ chronoScale = true,
291
+ showImages = true,
292
+ presentTime,
293
+ toggleShowMenu,
294
+ hoverShowMenu,
295
+ className,
296
+ buttonClassName
297
+ }: HorizontalLineProps) => {
298
+ const isPresentTimeDefined = presentTime !== undefined;
299
+ const all = commonAncestor.allDescendants().filter(desc => isPresentTimeDefined ? desc.apparition < presentTime : true);
300
+ const index = (s: Species) => all.indexOf(s);
301
+ const orientation = species.ancestor ? (index(species) > index(species.ancestor) ? -3 : 1) : 1;
302
+ const descendants = species.descendants.filter(desc => isPresentTimeDefined ? desc.apparition < presentTime : true);
303
+ const lastOne = descendants.filter(desc => desc.apparition === species.extinction()).length === 0;
304
+ const extinction = format(Math.min(showDesc ? species.extinction() : species.absoluteExtinction(), isPresentTimeDefined ? presentTime : species.absoluteExtinction()));
305
+ return (
306
+ <g>
307
+ <line
308
+ x1={x1}
309
+ y1={y}
310
+ x2={x2}
311
+ y2={y}
312
+ stroke={stroke}
313
+ />
314
+ <foreignObject
315
+ x={x1 + padding}
316
+ y={y + (padding) * orientation}
317
+ width={(chronoScale ? x0 ?? x2 : x2) - x1 - 2 * padding}
318
+ height={height + (showImages && species.image ? height : 0)}
319
+ >
320
+ <div className={`flex flex-row justify-between w-full ${className ?? ""}`}>
321
+ <div>
322
+ {chronoScale ? format(species.apparition) : ""}
323
+ </div>
324
+ <button
325
+ className={`p-0.625 justify-center flex flex-col items-center ${buttonClassName ?? ""}`}
326
+ onClick={() => toggleShowMenu(species)}
327
+ onMouseEnter={() => hoverShowMenu(species)}
328
+ onMouseLeave={() => hoverShowMenu(undefined)}
329
+ >
330
+ {species.name}
331
+ {species.image && showImages && (
332
+ <img
333
+ src={species.image}
334
+ alt={species.name}
335
+ style={{ height: height }}
336
+ />
337
+ )}
338
+ </button>
339
+ {descendants.length > 0 ?
340
+ <button onClick={changeShowDesc} className="h-1" style={{maxWidth: 4}}>
341
+ {((lastOne || !showDesc) && (!x0 || x0 === x2)) ? extinction : ""}
342
+ </button> :
343
+ <div>
344
+ {chronoScale ? extinction : ""}
345
+ </div>
346
+ }
347
+ </div>
348
+ </foreignObject>
349
+ {chronoScale && x0 && x0 < x2 && (
350
+ <foreignObject
351
+ x={x0 + padding}
352
+ y={y + padding * orientation}
353
+ width={x2 - x0 - 2 * padding}
354
+ height={height}
355
+ >
356
+ <div className="flex flex-row justify-end w-full">
357
+ {(lastOne || !showDesc) && chronoScale ? extinction : ""}
358
+ </div>
359
+ </foreignObject>)}
360
+ </g>
361
+ );
362
+ }
package/src/index.css ADDED
@@ -0,0 +1,68 @@
1
+ :root {
2
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: rgba(255, 255, 255, 0.87);
8
+ background-color: #242424;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ }
15
+
16
+ a {
17
+ font-weight: 500;
18
+ color: #646cff;
19
+ text-decoration: inherit;
20
+ }
21
+ a:hover {
22
+ color: #535bf2;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ display: flex;
28
+ place-items: center;
29
+ min-width: 320px;
30
+ min-height: 100vh;
31
+ }
32
+
33
+ h1 {
34
+ font-size: 3.2em;
35
+ line-height: 1.1;
36
+ }
37
+
38
+ button {
39
+ border-radius: 8px;
40
+ border: 1px solid transparent;
41
+ padding: 0.6em 1.2em;
42
+ font-size: 1em;
43
+ font-weight: 500;
44
+ font-family: inherit;
45
+ background-color: #1a1a1a;
46
+ cursor: pointer;
47
+ transition: border-color 0.25s;
48
+ }
49
+ button:hover {
50
+ border-color: #646cff;
51
+ }
52
+ button:focus,
53
+ button:focus-visible {
54
+ outline: 4px auto -webkit-focus-ring-color;
55
+ }
56
+
57
+ @media (prefers-color-scheme: light) {
58
+ :root {
59
+ color: #213547;
60
+ background-color: #ffffff;
61
+ }
62
+ a:hover {
63
+ color: #747bff;
64
+ }
65
+ button {
66
+ background-color: #f9f9f9;
67
+ }
68
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { Species } from "./classes/Species";
2
+ export { PhTree, MultiplePhTrees } from "./components/PhTree";
3
+ export { Menu } from "./components/Menu";
4
+ export { codeText, codeTextAlt, getLanguageOptions } from "./utils/translate";
package/src/main.tsx ADDED
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
package/src/types.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export interface SpeciesJSON {
2
+ name: string;
3
+ apparition?: number;
4
+ duration?: number;
5
+ description?: string;
6
+ descendants?: SpeciesJSON[];
7
+ afterApparition?: number;
8
+ image?: string;
9
+ }
@@ -0,0 +1,3 @@
1
+ export const between = (n: number, min: number, max: number) => {
2
+ return n < min ? min : n > max ? max : n;
3
+ };
@@ -0,0 +1,73 @@
1
+ import { Species } from "../classes/Species";
2
+
3
+ export const example = Species.fromJSON({
4
+ name: "Hominoidea",
5
+ apparition: -25e6,
6
+ duration: 6e6,
7
+ descendants: [
8
+ {
9
+ name: "Hilobates",
10
+ afterApparition: 6e6,
11
+ duration: 19e6,
12
+ image: "https://upload.wikimedia.org/wikipedia/commons/4/40/Hylobaes_lar_Canarias.jpg"
13
+ },
14
+ {
15
+ name: "Hominidae",
16
+ afterApparition: 6e6,
17
+ duration: 6e6,
18
+ descendants: [
19
+ {
20
+ name: "Pongo",
21
+ afterApparition: 6e6,
22
+ duration: 13e6,
23
+ image: "https://upload.wikimedia.org/wikipedia/commons/6/65/Pongo_tapanuliensis.jpg"
24
+ },
25
+ {
26
+ name: "Homininae",
27
+ afterApparition: 6e6,
28
+ duration: 5e6,
29
+ descendants: [
30
+ {
31
+ name: "Gorilla",
32
+ afterApparition: 5e6,
33
+ duration: 8e6,
34
+ image: "https://gorillas-world.com/wp-content/uploads/anatomia.jpg"
35
+ },
36
+ {
37
+ name: "Hominini",
38
+ afterApparition: 5e6,
39
+ duration: 2e6,
40
+ descendants: [
41
+ {
42
+ name: "Pan",
43
+ afterApparition: 2e6,
44
+ duration: 3e6,
45
+ descendants: [
46
+ {
47
+ name: "Pan Troglodytes",
48
+ afterApparition: 3e6,
49
+ duration: 3e6,
50
+ image:"https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcR-v4-d4R9AUgsHdG42VPYuYj_d4OMRHKasUQ&s"
51
+ },
52
+ {
53
+ name:"Pan Paniscus",
54
+ afterApparition : 3e6,
55
+ duration : 3e6,
56
+ image : "https://upload.wikimedia.org/wikipedia/commons/e/e2/Apeldoorn_Apenheul_zoo_Bonobo.jpg"
57
+ }
58
+ ],
59
+ },
60
+ {
61
+ name: "Homo",
62
+ afterApparition: 2e6,
63
+ duration: 6e6,
64
+ image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR7XK_e3HG0jhOticytH1Dn3tzBEZyRyWc5Mg&s"
65
+ }
66
+ ],
67
+ }
68
+ ],
69
+ }
70
+ ]
71
+ }
72
+ ]
73
+ });
@@ -0,0 +1,12 @@
1
+ const hexToRGB = (hex: string) => {
2
+ hex = hex.replace(/^#/, '');
3
+ const r = parseInt(hex.substring(0, 2), 16);
4
+ const g = parseInt(hex.substring(2, 4), 16);
5
+ const b = parseInt(hex.substring(4, 6), 16);
6
+ return { r, g, b };
7
+ };
8
+
9
+ export const hexToRGBA = (hex: string, a: number) => {
10
+ const {r, g, b} = hexToRGB(hex);
11
+ return `rgba(${r}, ${g}, ${b}, ${a})`
12
+ };
@@ -0,0 +1,20 @@
1
+ export const scientificNotation = (n: number, decimals: number = 2) => {
2
+ if(n === 0) {
3
+ return "0";
4
+ }
5
+ const abs = Math.abs(n);
6
+ const exp = Math.floor(Math.log10(abs));
7
+ const mant = n / Math.pow(10, exp);
8
+ let mantText = mant.toFixed(decimals);
9
+ for(let i = mantText.length - 1; i >= 0; i--) {
10
+ if(mantText[i] === "0") {
11
+ mantText = mantText.slice(0, i);
12
+ } else {
13
+ if(mantText[i] === ".") {
14
+ mantText = mantText.slice(0, i);
15
+ }
16
+ break;
17
+ }
18
+ }
19
+ return mantText + ((abs >= 1 && abs < 10) ? "" : ("e" + exp));
20
+ };
@@ -0,0 +1,40 @@
1
+ import { Species } from "../classes/Species";
2
+ import { SpeciesJSON } from "../types";
3
+
4
+ const handleFileChange = (file: File | undefined): Promise<SpeciesJSON> => {
5
+ return new Promise((resolve, reject) => {
6
+ if (file && file.type === 'application/json') {
7
+ const reader = new FileReader();
8
+ reader.onload = (event) => {
9
+ try {
10
+ const jsonContent = JSON.parse(event.target?.result as string);
11
+ resolve(jsonContent);
12
+ } catch (error) {
13
+ reject('Error parsing JSON');
14
+ }
15
+ };
16
+ reader.onerror = () => {
17
+ reject('Error reading file');
18
+ };
19
+ reader.readAsText(file);
20
+ } else {
21
+ reject('Please select a valid JSON file.');
22
+ }
23
+ });
24
+ };
25
+
26
+ export const setFromJson = (
27
+ setSpecies: (species: Species | undefined) => void,
28
+ setScale: (scale: number) => void,
29
+ setPresentTime: (time: number) => void,
30
+ presentTimeBoolean: boolean
31
+ ) => async (file: File | undefined) => {
32
+ setSpecies(undefined);
33
+ const json = await handleFileChange(file);
34
+ const newSpecies = Species.fromJSON(json);
35
+ setSpecies(newSpecies);
36
+ if(presentTimeBoolean){
37
+ setPresentTime(newSpecies.absoluteExtinction());
38
+ }
39
+ setScale(newSpecies.absoluteDuration());
40
+ };
@@ -0,0 +1,68 @@
1
+ import Papa from 'papaparse';
2
+ import { useEffect, useState } from 'react';
3
+
4
+ let data0: any[] = [];
5
+
6
+ const fetchCSVData = async (filePath: string): Promise<any[]> => {
7
+ const response = await fetch(filePath);
8
+ const reader = response.body!.getReader();
9
+ const result = await reader.read();
10
+ const decoder = new TextDecoder('utf-8');
11
+ const CSVString = decoder.decode(result.value!);
12
+ const { data } = Papa.parse(CSVString, {
13
+ header: true,
14
+ dynamicTyping: true,
15
+ delimiter: ";",
16
+ });
17
+ return data;
18
+ };
19
+
20
+ const provisionalData = (filePath: string = "/translate.csv") => (async (filePath: string = "/translate.csv") => {
21
+ data0 = await fetchCSVData(filePath);
22
+ })(filePath);
23
+
24
+ export const codeText = (code: string, language: string, arg: string[] = [], filePath: string = "/translate.csv") => {
25
+ const [data, setData] = useState<any[]>([]);
26
+ useEffect(() => {
27
+ fetchCSVData(filePath).then(setData);
28
+ provisionalData(filePath);
29
+ }, [language]);
30
+ const row = data.find((row: any) => row.code === code) ?? data0.find((row: any) => row.code === code);
31
+ try{
32
+ const str = row[language];
33
+ const val = arg.reduce((acc, arg, i) => acc.replace(`{${i}}`, arg), str);
34
+ return val as string;
35
+ } catch {
36
+ return;
37
+ }
38
+ };
39
+
40
+ export const codeTextAlt = async (code: string, language: string, arg: string[] = [], filePath: string = "/translate.csv"): Promise<string> => {
41
+ const data = await fetchCSVData(filePath);
42
+ const row = data.find((row: any) => row.code === code);
43
+ try {
44
+ const str = row[language];
45
+ return arg.reduce((acc, arg, i) => acc.replace(`{${i}}`, arg), str);
46
+ } catch {
47
+ return "";
48
+ }
49
+ };
50
+
51
+ export const getLanguageOptions = (filePath: string = "/translate.csv") => {
52
+ const [languageOptions, setLanguageOptions] = useState<Map<string, string>>(new Map());
53
+
54
+ useEffect(() => {
55
+ const loadLanguage = async () => {
56
+ const data = await fetchCSVData(filePath);
57
+ const lan = data.find((row: any) => row.code === "lan");
58
+ if (lan) {
59
+ delete lan.code;
60
+ const lanMap = new Map<string, string>(Object.entries(lan));
61
+ setLanguageOptions(lanMap);
62
+ }
63
+ };
64
+
65
+ loadLanguage();
66
+ }, []);
67
+ return languageOptions;
68
+ };