react-cheminfo 0.14.0 → 0.16.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.
- package/README.md +175 -21
- package/lib/about/core/about.d.ts +13 -7
- package/lib/about/core/about.d.ts.map +1 -1
- package/lib/about/core/about.js +4 -2
- package/lib/about/core/about.js.map +1 -1
- package/lib/about/ui/AboutBuild.d.ts +20 -0
- package/lib/about/ui/AboutBuild.d.ts.map +1 -0
- package/lib/about/ui/AboutBuild.js +38 -0
- package/lib/about/ui/AboutBuild.js.map +1 -0
- package/lib/about/ui/AboutPage.d.ts +7 -0
- package/lib/about/ui/AboutPage.d.ts.map +1 -1
- package/lib/about/ui/AboutPage.js +5 -2
- package/lib/about/ui/AboutPage.js.map +1 -1
- package/lib/about/ui/index.d.ts +2 -0
- package/lib/about/ui/index.d.ts.map +1 -1
- package/lib/about/ui/index.js +1 -0
- package/lib/about/ui/index.js.map +1 -1
- package/lib/build/core/buildInfo.d.ts +39 -0
- package/lib/build/core/buildInfo.d.ts.map +1 -0
- package/lib/build/core/buildInfo.js +20 -0
- package/lib/build/core/buildInfo.js.map +1 -0
- package/lib/build/core/current.d.ts +12 -0
- package/lib/build/core/current.d.ts.map +1 -0
- package/lib/build/core/current.js +11 -0
- package/lib/build/core/current.js.map +1 -0
- package/lib/build/core/index.d.ts +3 -0
- package/lib/build/core/index.d.ts.map +1 -0
- package/lib/build/core/index.js +2 -0
- package/lib/build/core/index.js.map +1 -0
- package/lib/build/vite/buildInfo.d.ts +57 -0
- package/lib/build/vite/buildInfo.d.ts.map +1 -0
- package/lib/build/vite/buildInfo.js +151 -0
- package/lib/build/vite/buildInfo.js.map +1 -0
- package/lib/build/vite/gitHead.d.ts +29 -0
- package/lib/build/vite/gitHead.d.ts.map +1 -0
- package/lib/build/vite/gitHead.js +102 -0
- package/lib/build/vite/gitHead.js.map +1 -0
- package/lib/build/vite/index.d.ts +4 -0
- package/lib/build/vite/index.d.ts.map +1 -0
- package/lib/build/vite/index.js +3 -0
- package/lib/build/vite/index.js.map +1 -0
- package/lib/build-info.d.ts +3 -0
- package/lib/build-info.d.ts.map +1 -0
- package/lib/build-info.js +2 -0
- package/lib/build-info.js.map +1 -0
- package/lib/core.d.ts +2 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +2 -0
- package/lib/core.js.map +1 -1
- package/lib/credits/core/credits.d.ts +35 -0
- package/lib/credits/core/credits.d.ts.map +1 -1
- package/lib/credits/core/credits.js +41 -0
- package/lib/credits/core/credits.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +40 -0
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/core/types.d.ts +1 -1
- package/lib/ecosystem/core/types.d.ts.map +1 -1
- package/lib/ecosystem/ui/Wordmark.d.ts +14 -3
- package/lib/ecosystem/ui/Wordmark.d.ts.map +1 -1
- package/lib/ecosystem/ui/Wordmark.js +8 -4
- package/lib/ecosystem/ui/Wordmark.js.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +11 -0
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/parallel/core/index.d.ts +27 -0
- package/lib/parallel/core/index.d.ts.map +1 -0
- package/lib/parallel/core/index.js +20 -0
- package/lib/parallel/core/index.js.map +1 -0
- package/lib/parallel/core/parallelAxes.d.ts +103 -0
- package/lib/parallel/core/parallelAxes.d.ts.map +1 -0
- package/lib/parallel/core/parallelAxes.js +146 -0
- package/lib/parallel/core/parallelAxes.js.map +1 -0
- package/lib/parallel/core/parallelBrush.d.ts +79 -0
- package/lib/parallel/core/parallelBrush.d.ts.map +1 -0
- package/lib/parallel/core/parallelBrush.js +99 -0
- package/lib/parallel/core/parallelBrush.js.map +1 -0
- package/lib/parallel/core/parallelExtent.d.ts +28 -0
- package/lib/parallel/core/parallelExtent.d.ts.map +1 -0
- package/lib/parallel/core/parallelExtent.js +47 -0
- package/lib/parallel/core/parallelExtent.js.map +1 -0
- package/lib/parallel/core/parallelFilter.d.ts +36 -0
- package/lib/parallel/core/parallelFilter.d.ts.map +1 -0
- package/lib/parallel/core/parallelFilter.js +74 -0
- package/lib/parallel/core/parallelFilter.js.map +1 -0
- package/lib/parallel/core/parallelHit.d.ts +47 -0
- package/lib/parallel/core/parallelHit.d.ts.map +1 -0
- package/lib/parallel/core/parallelHit.js +81 -0
- package/lib/parallel/core/parallelHit.js.map +1 -0
- package/lib/parallel/core/parallelPaint.d.ts +104 -0
- package/lib/parallel/core/parallelPaint.d.ts.map +1 -0
- package/lib/parallel/core/parallelPaint.js +160 -0
- package/lib/parallel/core/parallelPaint.js.map +1 -0
- package/lib/parallel/core/parallelPalette.d.ts +31 -0
- package/lib/parallel/core/parallelPalette.d.ts.map +1 -0
- package/lib/parallel/core/parallelPalette.js +68 -0
- package/lib/parallel/core/parallelPalette.js.map +1 -0
- package/lib/parallel/core/parallelTicks.d.ts +34 -0
- package/lib/parallel/core/parallelTicks.d.ts.map +1 -0
- package/lib/parallel/core/parallelTicks.js +116 -0
- package/lib/parallel/core/parallelTicks.js.map +1 -0
- package/lib/parallel/core/parallelTypes.d.ts +129 -0
- package/lib/parallel/core/parallelTypes.d.ts.map +1 -0
- package/lib/parallel/core/parallelTypes.js +29 -0
- package/lib/parallel/core/parallelTypes.js.map +1 -0
- package/lib/parallel/ui/ParallelAxesLayer.d.ts +28 -0
- package/lib/parallel/ui/ParallelAxesLayer.d.ts.map +1 -0
- package/lib/parallel/ui/ParallelAxesLayer.js +29 -0
- package/lib/parallel/ui/ParallelAxesLayer.js.map +1 -0
- package/lib/parallel/ui/ParallelCoordinates.d.ts +21 -0
- package/lib/parallel/ui/ParallelCoordinates.d.ts.map +1 -0
- package/lib/parallel/ui/ParallelCoordinates.js +109 -0
- package/lib/parallel/ui/ParallelCoordinates.js.map +1 -0
- package/lib/parallel/ui/ParallelLabels.d.ts +26 -0
- package/lib/parallel/ui/ParallelLabels.d.ts.map +1 -0
- package/lib/parallel/ui/ParallelLabels.js +25 -0
- package/lib/parallel/ui/ParallelLabels.js.map +1 -0
- package/lib/parallel/ui/ParallelTooltip.d.ts +26 -0
- package/lib/parallel/ui/ParallelTooltip.d.ts.map +1 -0
- package/lib/parallel/ui/ParallelTooltip.js +30 -0
- package/lib/parallel/ui/ParallelTooltip.js.map +1 -0
- package/lib/parallel/ui/index.d.ts +25 -0
- package/lib/parallel/ui/index.d.ts.map +1 -0
- package/lib/parallel/ui/index.js +18 -0
- package/lib/parallel/ui/index.js.map +1 -0
- package/lib/parallel/ui/parallelCoordinatesProps.d.ts +185 -0
- package/lib/parallel/ui/parallelCoordinatesProps.d.ts.map +1 -0
- package/lib/parallel/ui/parallelCoordinatesProps.js +11 -0
- package/lib/parallel/ui/parallelCoordinatesProps.js.map +1 -0
- package/lib/parallel/ui/parallelGestureModel.d.ts +52 -0
- package/lib/parallel/ui/parallelGestureModel.d.ts.map +1 -0
- package/lib/parallel/ui/parallelGestureModel.js +80 -0
- package/lib/parallel/ui/parallelGestureModel.js.map +1 -0
- package/lib/parallel/ui/parallelInk.d.ts +76 -0
- package/lib/parallel/ui/parallelInk.d.ts.map +1 -0
- package/lib/parallel/ui/parallelInk.js +113 -0
- package/lib/parallel/ui/parallelInk.js.map +1 -0
- package/lib/parallel/ui/parallelPlotModel.d.ts +55 -0
- package/lib/parallel/ui/parallelPlotModel.d.ts.map +1 -0
- package/lib/parallel/ui/parallelPlotModel.js +113 -0
- package/lib/parallel/ui/parallelPlotModel.js.map +1 -0
- package/lib/parallel/ui/parallelStyles.d.ts +83 -0
- package/lib/parallel/ui/parallelStyles.d.ts.map +1 -0
- package/lib/parallel/ui/parallelStyles.js +131 -0
- package/lib/parallel/ui/parallelStyles.js.map +1 -0
- package/lib/parallel/ui/useParallelCanvas.d.ts +49 -0
- package/lib/parallel/ui/useParallelCanvas.d.ts.map +1 -0
- package/lib/parallel/ui/useParallelCanvas.js +58 -0
- package/lib/parallel/ui/useParallelCanvas.js.map +1 -0
- package/lib/parallel/ui/useParallelGesture.d.ts +67 -0
- package/lib/parallel/ui/useParallelGesture.d.ts.map +1 -0
- package/lib/parallel/ui/useParallelGesture.js +139 -0
- package/lib/parallel/ui/useParallelGesture.js.map +1 -0
- package/lib/structure/ui/useToolbarFloor.d.ts.map +1 -1
- package/lib/structure/ui/useToolbarFloor.js +29 -22
- package/lib/structure/ui/useToolbarFloor.js.map +1 -1
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/lib/vite.d.ts +1 -0
- package/lib/vite.d.ts.map +1 -1
- package/lib/vite.js +1 -0
- package/lib/vite.js.map +1 -1
- package/package.json +2 -1
- package/src/about/core/about.ts +18 -9
- package/src/about/ui/AboutBuild.tsx +83 -0
- package/src/about/ui/AboutPage.tsx +15 -7
- package/src/about/ui/index.ts +2 -0
- package/src/build/core/buildInfo.ts +48 -0
- package/src/build/core/current.ts +12 -0
- package/src/build/core/index.ts +2 -0
- package/src/build/vite/buildInfo.ts +178 -0
- package/src/build/vite/gitHead.ts +114 -0
- package/src/build/vite/index.ts +8 -0
- package/src/build-info.ts +2 -0
- package/src/core.ts +2 -0
- package/src/credits/core/credits.ts +42 -0
- package/src/ecosystem/core/sites.ts +40 -0
- package/src/ecosystem/core/types.ts +4 -1
- package/src/ecosystem/ui/Wordmark.tsx +22 -7
- package/src/ecosystem/ui/glyphs.tsx +46 -0
- package/src/parallel/core/index.ts +80 -0
- package/src/parallel/core/parallelAxes.ts +207 -0
- package/src/parallel/core/parallelBrush.ts +145 -0
- package/src/parallel/core/parallelExtent.ts +56 -0
- package/src/parallel/core/parallelFilter.ts +94 -0
- package/src/parallel/core/parallelHit.ts +106 -0
- package/src/parallel/core/parallelPaint.ts +231 -0
- package/src/parallel/core/parallelPalette.ts +80 -0
- package/src/parallel/core/parallelTicks.ts +164 -0
- package/src/parallel/core/parallelTypes.ts +147 -0
- package/src/parallel/ui/ParallelAxesLayer.tsx +123 -0
- package/src/parallel/ui/ParallelCoordinates.tsx +233 -0
- package/src/parallel/ui/ParallelLabels.tsx +56 -0
- package/src/parallel/ui/ParallelTooltip.tsx +55 -0
- package/src/parallel/ui/index.ts +50 -0
- package/src/parallel/ui/parallelCoordinatesProps.ts +199 -0
- package/src/parallel/ui/parallelGestureModel.ts +109 -0
- package/src/parallel/ui/parallelInk.ts +182 -0
- package/src/parallel/ui/parallelPlotModel.ts +130 -0
- package/src/parallel/ui/parallelStyles.ts +146 -0
- package/src/parallel/ui/useParallelCanvas.ts +104 -0
- package/src/parallel/ui/useParallelGesture.ts +227 -0
- package/src/structure/ui/useToolbarFloor.ts +27 -22
- package/src/ui.ts +1 -0
- package/src/vite.ts +1 -0
|
@@ -79,6 +79,19 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
|
|
|
79
79
|
brandAlt: '#d63384',
|
|
80
80
|
mark: { plate: '#5b52e0', accent: '#ff8ac2' },
|
|
81
81
|
},
|
|
82
|
+
{
|
|
83
|
+
id: 'dbe',
|
|
84
|
+
name: { lead: 'dbe', alt: 'cheminfo', dot: true },
|
|
85
|
+
host: 'dbe.cheminfo.org',
|
|
86
|
+
repository: 'https://github.com/cheminfo/dbe.cheminfo.org',
|
|
87
|
+
tagline: 'Count rings and double bonds from a formula or a structure.',
|
|
88
|
+
group: 'basics',
|
|
89
|
+
brand: '#63123f',
|
|
90
|
+
// The emerald the mark's double bond is drawn in sits at 2.5:1 on white,
|
|
91
|
+
// so the second half of the wordmark takes this darkened form instead.
|
|
92
|
+
brandAlt: '#047857',
|
|
93
|
+
mark: { plate: '#63123f', accent: '#10b981' },
|
|
94
|
+
},
|
|
82
95
|
{
|
|
83
96
|
id: 'nmrium',
|
|
84
97
|
name: { lead: 'NMR', alt: 'ium' },
|
|
@@ -236,6 +249,33 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
|
|
|
236
249
|
brandAlt: '#0b6e85',
|
|
237
250
|
mark: { plate: '#7c2d12', accent: '#06b6d4' },
|
|
238
251
|
},
|
|
252
|
+
{
|
|
253
|
+
id: 'symmetry',
|
|
254
|
+
name: { lead: 'Symme', alt: 'Try' },
|
|
255
|
+
host: 'symmetry.cheminfo.org',
|
|
256
|
+
repository: 'https://github.com/cheminfo/symmetry.cheminfo.org',
|
|
257
|
+
tagline: 'Find the symmetry of a molecule, a crystal or a pattern.',
|
|
258
|
+
group: 'basics',
|
|
259
|
+
brand: '#9f1239',
|
|
260
|
+
// The gold that carries the apex of the mark sits at 2.2:1 on white, so
|
|
261
|
+
// the second half of the wordmark takes this darkened amber instead.
|
|
262
|
+
brandAlt: '#b45309',
|
|
263
|
+
mark: { plate: '#9f1239', accent: '#f59e0b' },
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: 'osiris',
|
|
267
|
+
name: { lead: 'osiris', alt: 'cheminfo', dot: true },
|
|
268
|
+
host: 'osiris.cheminfo.org',
|
|
269
|
+
repository: 'https://github.com/cheminfo/osiris.cheminfo.org',
|
|
270
|
+
tagline: 'Predicted toxicity risks and properties, one molecule or many.',
|
|
271
|
+
group: 'research',
|
|
272
|
+
brand: '#854d0e',
|
|
273
|
+
// The lapis and the gold are equiluminant — 1.02:1 against each other — so
|
|
274
|
+
// the mark draws the same blue lightened, and this darkened form is what
|
|
275
|
+
// the second half of the wordmark is set in.
|
|
276
|
+
brandAlt: '#1d4ed8',
|
|
277
|
+
mark: { plate: '#854d0e', accent: '#60a5fa' },
|
|
278
|
+
},
|
|
239
279
|
];
|
|
240
280
|
|
|
241
281
|
/**
|
|
@@ -8,6 +8,7 @@ export type SiteId =
|
|
|
8
8
|
| 'smiles'
|
|
9
9
|
| 'openbabel'
|
|
10
10
|
| 'chemcalc'
|
|
11
|
+
| 'dbe'
|
|
11
12
|
| 'nmrium'
|
|
12
13
|
| 'metabo'
|
|
13
14
|
| 'derepflow'
|
|
@@ -21,7 +22,9 @@ export type SiteId =
|
|
|
21
22
|
| 'polycarp'
|
|
22
23
|
| '3d'
|
|
23
24
|
| 'periodic-table'
|
|
24
|
-
| 'database'
|
|
25
|
+
| 'database'
|
|
26
|
+
| 'symmetry'
|
|
27
|
+
| 'osiris';
|
|
25
28
|
|
|
26
29
|
/** A site's name, split the way its own wordmark splits it. */
|
|
27
30
|
export interface SiteName {
|
|
@@ -3,12 +3,22 @@ import type { CSSProperties, ReactElement } from 'react';
|
|
|
3
3
|
import { joinClassNames } from '../../shared/ui/joinClassNames.ts';
|
|
4
4
|
import { siteById } from '../core/lookup.ts';
|
|
5
5
|
import { siteNameColors } from '../core/nameColors.ts';
|
|
6
|
-
import type { SiteId } from '../core/sites.ts';
|
|
6
|
+
import type { EcosystemSite, SiteId } from '../core/sites.ts';
|
|
7
7
|
|
|
8
8
|
/** What a site's written name needs. */
|
|
9
9
|
export interface WordmarkProps {
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The site whose name is written, passed rather than named — for a site that
|
|
12
|
+
* is deliberately not one of `ECOSYSTEM_SITES`. One of `site` and `siteId` is
|
|
13
|
+
* required.
|
|
14
|
+
* @default undefined
|
|
15
|
+
*/
|
|
16
|
+
site?: EcosystemSite;
|
|
17
|
+
/**
|
|
18
|
+
* The same site, named rather than passed, which is what a header knows.
|
|
19
|
+
* @default undefined
|
|
20
|
+
*/
|
|
21
|
+
siteId?: SiteId;
|
|
12
22
|
/**
|
|
13
23
|
* Size of the name, in pixels. The weight comes from the surrounding
|
|
14
24
|
* context, so the same wordmark suits a header bar and a heading.
|
|
@@ -32,12 +42,17 @@ export interface WordmarkProps {
|
|
|
32
42
|
* its address.
|
|
33
43
|
* @param props - The site, the size of the name, and extra class names.
|
|
34
44
|
* @returns The name, as one inline element that never wraps mid-address.
|
|
45
|
+
* @throws {Error} When neither `site` nor `siteId` is given.
|
|
35
46
|
*/
|
|
36
47
|
export function Wordmark(props: WordmarkProps): ReactElement {
|
|
37
|
-
const { siteId, size = 17, className } = props;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
const { site, siteId, size = 17, className } = props;
|
|
49
|
+
const written = site ?? (siteId === undefined ? undefined : siteById(siteId));
|
|
50
|
+
if (written === undefined) {
|
|
51
|
+
throw new Error('Wordmark needs one of its `site` and `siteId` props');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { lead, alt, dot } = written.name;
|
|
55
|
+
const colors = siteNameColors(written);
|
|
41
56
|
|
|
42
57
|
return (
|
|
43
58
|
<span
|
|
@@ -129,6 +129,18 @@ export const GLYPHS: Record<SiteId, (alt: string) => ReactNode> = {
|
|
|
129
129
|
<path d="M23 24V18" stroke="#ffffff" />
|
|
130
130
|
</g>
|
|
131
131
|
),
|
|
132
|
+
// A ring with a double bond drawn inside it: the two things a DBE adds up,
|
|
133
|
+
// and the equals sign the E of the name already is.
|
|
134
|
+
dbe: (alt) => (
|
|
135
|
+
<g fill="none" strokeLinecap="round" strokeLinejoin="round">
|
|
136
|
+
<path
|
|
137
|
+
d="M16 5 25.53 10.5 25.53 21.5 16 27 6.47 21.5 6.47 10.5Z"
|
|
138
|
+
stroke="#ffffff"
|
|
139
|
+
strokeWidth="2.4"
|
|
140
|
+
/>
|
|
141
|
+
<path d="M10.8 12.9h10.4M10.8 19.1h10.4" stroke={alt} strokeWidth="2.9" />
|
|
142
|
+
</g>
|
|
143
|
+
),
|
|
132
144
|
// The ring and the substituent dots of the DerepFlow logo do not survive
|
|
133
145
|
// 16 px, so the mark keeps the two elements that carry its meaning: the
|
|
134
146
|
// compound, and the tick that says it has been identified.
|
|
@@ -363,4 +375,38 @@ export const GLYPHS: Record<SiteId, (alt: string) => ReactNode> = {
|
|
|
363
375
|
<ellipse cx="16" cy="9.4" rx="10.2" ry="3.9" fill={alt} />
|
|
364
376
|
</>
|
|
365
377
|
),
|
|
378
|
+
// A tetrahedron seen from slightly above, four balls at its corners: the
|
|
379
|
+
// first shape a chemist meets symmetry in. The apex carries the answering
|
|
380
|
+
// colour, which is also the C3 axis every base triangle turns about.
|
|
381
|
+
symmetry: (alt) => (
|
|
382
|
+
<>
|
|
383
|
+
<g stroke="#ffffff" strokeWidth="1.8" strokeLinecap="round" fill="none">
|
|
384
|
+
<path d="M16 6.4 6.6 24.4M16 6.4 25.4 24.4M16 6.4 16 17.6" />
|
|
385
|
+
<path d="M6.6 24.4 25.4 24.4" />
|
|
386
|
+
<path d="M6.6 24.4 16 17.6M25.4 24.4 16 17.6" opacity="0.5" />
|
|
387
|
+
</g>
|
|
388
|
+
<circle cx="16" cy="17.6" r="2.5" fill="#ffffff" opacity="0.55" />
|
|
389
|
+
<circle cx="6.6" cy="24.4" r="3.2" fill="#ffffff" />
|
|
390
|
+
<circle cx="25.4" cy="24.4" r="3.2" fill="#ffffff" />
|
|
391
|
+
<circle cx="16" cy="6.4" r="3.6" fill={alt} />
|
|
392
|
+
</>
|
|
393
|
+
),
|
|
394
|
+
// Three axes with one line crossing them: a parallel-coordinates plot, which
|
|
395
|
+
// is how this site reads a set of molecules against one another. The line
|
|
396
|
+
// carries the answering colour because the line is the molecule.
|
|
397
|
+
osiris: (alt) => (
|
|
398
|
+
<>
|
|
399
|
+
<g stroke="#ffffff" strokeWidth="2.2" strokeLinecap="round" fill="none">
|
|
400
|
+
<path d="M7.4 6.8V25.2M16 6.8V25.2M24.6 6.8V25.2" />
|
|
401
|
+
</g>
|
|
402
|
+
<path
|
|
403
|
+
d="M7.4 21.6 16 10.4 24.6 17.2"
|
|
404
|
+
fill="none"
|
|
405
|
+
stroke={alt}
|
|
406
|
+
strokeWidth="3.2"
|
|
407
|
+
strokeLinecap="round"
|
|
408
|
+
strokeLinejoin="round"
|
|
409
|
+
/>
|
|
410
|
+
</>
|
|
411
|
+
),
|
|
366
412
|
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a parallel-coordinates figure has to work out before it can draw: where
|
|
3
|
+
* each axis stands, how a value on it becomes a pixel, which row the pointer
|
|
4
|
+
* is over, which rows the brushes keep, and the canvas pass that paints them.
|
|
5
|
+
*
|
|
6
|
+
* The brush is the part worth reading twice. It is a pure function of a press
|
|
7
|
+
* and a pointer rather than a behaviour attached to an element, so every one
|
|
8
|
+
* of its four gestures — create, move, resize from either edge, clear — is
|
|
9
|
+
* covered by tests in a runtime with no DOM, and the figure stays declarative.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type { ParallelAxisLayout, ParallelAxisPixels } from './parallelAxes.ts';
|
|
13
|
+
export {
|
|
14
|
+
PARALLEL_MARGIN,
|
|
15
|
+
parallelAxisLayouts,
|
|
16
|
+
parallelAxisPixels,
|
|
17
|
+
parallelPixelAt,
|
|
18
|
+
parallelValueToY,
|
|
19
|
+
parallelYToValue,
|
|
20
|
+
} from './parallelAxes.ts';
|
|
21
|
+
export type {
|
|
22
|
+
ParallelBand,
|
|
23
|
+
ParallelBrushDrag,
|
|
24
|
+
ParallelBrushGrip,
|
|
25
|
+
} from './parallelBrush.ts';
|
|
26
|
+
export {
|
|
27
|
+
PARALLEL_BRUSH_HALF_WIDTH,
|
|
28
|
+
PARALLEL_BRUSH_HANDLE_REACH,
|
|
29
|
+
PARALLEL_BRUSH_SMALLEST,
|
|
30
|
+
parallelBandAt,
|
|
31
|
+
parallelBandOf,
|
|
32
|
+
parallelBrushGrip,
|
|
33
|
+
parallelIsBand,
|
|
34
|
+
parallelRangeOf,
|
|
35
|
+
} from './parallelBrush.ts';
|
|
36
|
+
export type { ParallelExtent } from './parallelExtent.ts';
|
|
37
|
+
export {
|
|
38
|
+
PARALLEL_DEGENERATE_PADDING,
|
|
39
|
+
parallelExtent,
|
|
40
|
+
} from './parallelExtent.ts';
|
|
41
|
+
export {
|
|
42
|
+
parallelIncludedMask,
|
|
43
|
+
parallelKeptCount,
|
|
44
|
+
parallelRangeKeeps,
|
|
45
|
+
} from './parallelFilter.ts';
|
|
46
|
+
export type { ParallelPoint, ParallelSegment } from './parallelHit.ts';
|
|
47
|
+
export {
|
|
48
|
+
PARALLEL_HOVER_TOLERANCE,
|
|
49
|
+
parallelNearestRow,
|
|
50
|
+
parallelSegmentAt,
|
|
51
|
+
} from './parallelHit.ts';
|
|
52
|
+
export type {
|
|
53
|
+
ParallelHighlight,
|
|
54
|
+
ParallelHighlightPaint,
|
|
55
|
+
ParallelPaint,
|
|
56
|
+
} from './parallelPaint.ts';
|
|
57
|
+
export {
|
|
58
|
+
PARALLEL_INCLUDED_ALPHA,
|
|
59
|
+
paintParallelHighlights,
|
|
60
|
+
paintParallelLines,
|
|
61
|
+
preparePlotCanvas,
|
|
62
|
+
traceParallelRow,
|
|
63
|
+
} from './parallelPaint.ts';
|
|
64
|
+
export {
|
|
65
|
+
PARALLEL_PALETTE_STEPS,
|
|
66
|
+
parallelColorSteps,
|
|
67
|
+
parallelPalette,
|
|
68
|
+
} from './parallelPalette.ts';
|
|
69
|
+
export type { ParallelGraduation } from './parallelTicks.ts';
|
|
70
|
+
export { PARALLEL_TICK_COUNT, parallelTicks } from './parallelTicks.ts';
|
|
71
|
+
export type {
|
|
72
|
+
ParallelAxis,
|
|
73
|
+
ParallelAxisScale,
|
|
74
|
+
ParallelAxisSource,
|
|
75
|
+
ParallelColorBy,
|
|
76
|
+
ParallelRange,
|
|
77
|
+
ParallelRanges,
|
|
78
|
+
ParallelTick,
|
|
79
|
+
} from './parallelTypes.ts';
|
|
80
|
+
export { parallelAxisOf } from './parallelTypes.ts';
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where each axis stands, how far it reaches, and how a value on it becomes a
|
|
3
|
+
* pixel.
|
|
4
|
+
*
|
|
5
|
+
* The first axis sits flush with the left of the drawing area and the last one
|
|
6
|
+
* flush with the right, rather than inset the way a two-axis chart insets its
|
|
7
|
+
* plot: the axes *are* the figure here, and a reader comparing the leftmost
|
|
8
|
+
* column against the rightmost must see both of them, not a margin.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { chartScale } from '../../chart/core/chartScale.ts';
|
|
12
|
+
|
|
13
|
+
import { parallelExtent } from './parallelExtent.ts';
|
|
14
|
+
import { parallelTicks } from './parallelTicks.ts';
|
|
15
|
+
import type {
|
|
16
|
+
ParallelAxis,
|
|
17
|
+
ParallelAxisScale,
|
|
18
|
+
ParallelTick,
|
|
19
|
+
} from './parallelTypes.ts';
|
|
20
|
+
|
|
21
|
+
/** Room kept around the drawing area for the axis names and the tick labels. */
|
|
22
|
+
export const PARALLEL_MARGIN = {
|
|
23
|
+
/** Above the drawing area, where the axis names are written. */
|
|
24
|
+
top: 26,
|
|
25
|
+
/** To the right of it, so the last axis's labels are not clipped. */
|
|
26
|
+
right: 54,
|
|
27
|
+
/** Below it. */
|
|
28
|
+
bottom: 22,
|
|
29
|
+
/** To its left, where the first axis's tick labels are written. */
|
|
30
|
+
left: 54,
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
/** How a value on one axis becomes a pixel, as the two numbers a multiply-add needs. */
|
|
34
|
+
export interface ParallelAxisPixels {
|
|
35
|
+
/** The pixel the value zero lands on — or the value one, on a log axis. */
|
|
36
|
+
readonly offset: number;
|
|
37
|
+
/** Pixels per unit, negative because an axis grows upward. Zero when the axis is flat. */
|
|
38
|
+
readonly factor: number;
|
|
39
|
+
/** Whether a value is read through its base-10 logarithm first. */
|
|
40
|
+
readonly logarithmic: boolean;
|
|
41
|
+
/** The smallest value a log axis can place; anything at or below it is pinned there. */
|
|
42
|
+
readonly floor: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One axis, placed and measured, ready to draw. */
|
|
46
|
+
export interface ParallelAxisLayout {
|
|
47
|
+
/** The axis this lays out. */
|
|
48
|
+
id: string;
|
|
49
|
+
/** Its name. */
|
|
50
|
+
label: string;
|
|
51
|
+
/** The unit written after the name, or an empty string. */
|
|
52
|
+
unit: string;
|
|
53
|
+
/** Horizontal centre, in pixels from the left of the drawing area. */
|
|
54
|
+
x: number;
|
|
55
|
+
/** The value at the bottom of the axis. */
|
|
56
|
+
min: number;
|
|
57
|
+
/** The value at its top. */
|
|
58
|
+
max: number;
|
|
59
|
+
/** How values are placed between them. */
|
|
60
|
+
scale: ParallelAxisScale;
|
|
61
|
+
/** Its graduations, already written out, from the bottom up. */
|
|
62
|
+
ticks: readonly ParallelTick[];
|
|
63
|
+
/** How a value on it becomes a pixel, worked out once here. */
|
|
64
|
+
pixels: ParallelAxisPixels;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A flat axis: every value lands on the top of the drawing area. */
|
|
68
|
+
const FLAT: ParallelAxisPixels = {
|
|
69
|
+
offset: 0,
|
|
70
|
+
factor: 0,
|
|
71
|
+
logarithmic: false,
|
|
72
|
+
floor: 1,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** How far below the largest value a log axis may reach when its low end is not positive. */
|
|
76
|
+
const LOG_FLOOR_SHARE = 1e-12;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Place every axis across the drawing area and work out what it reaches.
|
|
80
|
+
* @param axes - The axes, from left to right.
|
|
81
|
+
* @param count - How many rows the columns are read over.
|
|
82
|
+
* @param innerWidth - Width between the first and the last axis, in pixels.
|
|
83
|
+
* @param innerHeight - Height of the drawing area, in pixels.
|
|
84
|
+
* @returns One layout per axis, in the order they were given. A single axis is
|
|
85
|
+
* centred; two or more are spread evenly with the outer ones flush with the
|
|
86
|
+
* edges.
|
|
87
|
+
*/
|
|
88
|
+
export function parallelAxisLayouts(
|
|
89
|
+
axes: readonly ParallelAxis[],
|
|
90
|
+
count: number,
|
|
91
|
+
innerWidth: number,
|
|
92
|
+
innerHeight: number,
|
|
93
|
+
): ParallelAxisLayout[] {
|
|
94
|
+
const layouts: ParallelAxisLayout[] = [];
|
|
95
|
+
const total = axes.length;
|
|
96
|
+
for (let index = 0; index < total; index++) {
|
|
97
|
+
const axis = axes[index];
|
|
98
|
+
if (axis === undefined) continue;
|
|
99
|
+
const extent = axisExtent(axis, count);
|
|
100
|
+
const graduation = parallelTicks(axis, extent.min, extent.max);
|
|
101
|
+
const [min, max] = graduation.domain;
|
|
102
|
+
const scale = axis.scale ?? 'linear';
|
|
103
|
+
layouts.push({
|
|
104
|
+
id: axis.id,
|
|
105
|
+
label: axis.label,
|
|
106
|
+
unit: axis.unit ?? '',
|
|
107
|
+
x: total === 1 ? innerWidth / 2 : (innerWidth * index) / (total - 1),
|
|
108
|
+
min,
|
|
109
|
+
max,
|
|
110
|
+
scale,
|
|
111
|
+
ticks: graduation.ticks,
|
|
112
|
+
pixels: parallelAxisPixels(min, max, scale, innerHeight),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return layouts;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The mapping that puts `max` at the top of the drawing area and `min` at its
|
|
120
|
+
* bottom.
|
|
121
|
+
* @param min - The value at the bottom.
|
|
122
|
+
* @param max - The value at the top.
|
|
123
|
+
* @param scale - How values are placed between them.
|
|
124
|
+
* @param innerHeight - Height of the drawing area, in pixels.
|
|
125
|
+
* @returns The mapping. An axis with no span, or one drawn in no height at
|
|
126
|
+
* all, comes back flat, which pins every value to the top rather than dividing
|
|
127
|
+
* by zero.
|
|
128
|
+
*/
|
|
129
|
+
export function parallelAxisPixels(
|
|
130
|
+
min: number,
|
|
131
|
+
max: number,
|
|
132
|
+
scale: ParallelAxisScale,
|
|
133
|
+
innerHeight: number,
|
|
134
|
+
): ParallelAxisPixels {
|
|
135
|
+
if (max - min <= 0 || innerHeight <= 0) return FLAT;
|
|
136
|
+
if (scale !== 'log') {
|
|
137
|
+
const linear = chartScale(min, max, innerHeight, 0);
|
|
138
|
+
return { ...linear, logarithmic: false, floor: 1 };
|
|
139
|
+
}
|
|
140
|
+
const floor = min > 0 ? min : max > 0 ? max * LOG_FLOOR_SHARE : 1;
|
|
141
|
+
const low = Math.log10(floor);
|
|
142
|
+
const high = Math.log10(Math.max(max, floor));
|
|
143
|
+
if (high - low <= 0) return FLAT;
|
|
144
|
+
const mapping = chartScale(low, high, innerHeight, 0);
|
|
145
|
+
return { ...mapping, logarithmic: true, floor };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Where one value falls on an axis, in pixels.
|
|
150
|
+
*
|
|
151
|
+
* For a single conversion — a tick, a brushed edge, a highlighted crossing.
|
|
152
|
+
* Inside a loop over rows, read `axis.pixels` and write the multiply-add out.
|
|
153
|
+
* @param pixels - The axis's mapping.
|
|
154
|
+
* @param value - The value to place.
|
|
155
|
+
* @returns The pixel, measured from the top of the drawing area. A value
|
|
156
|
+
* outside the axis lands outside the drawing area and is not clipped.
|
|
157
|
+
*/
|
|
158
|
+
export function parallelPixelAt(
|
|
159
|
+
pixels: ParallelAxisPixels,
|
|
160
|
+
value: number,
|
|
161
|
+
): number {
|
|
162
|
+
if (pixels.factor === 0) return pixels.offset;
|
|
163
|
+
const placed = pixels.logarithmic
|
|
164
|
+
? Math.log10(Math.max(value, pixels.floor))
|
|
165
|
+
: value;
|
|
166
|
+
return pixels.offset + placed * pixels.factor;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Where one value falls on an axis, in pixels.
|
|
171
|
+
* @param value - The value to place.
|
|
172
|
+
* @param axis - The axis it belongs to.
|
|
173
|
+
* @returns The pixel, measured from the top of the drawing area.
|
|
174
|
+
*/
|
|
175
|
+
export function parallelValueToY(
|
|
176
|
+
value: number,
|
|
177
|
+
axis: ParallelAxisLayout,
|
|
178
|
+
): number {
|
|
179
|
+
return parallelPixelAt(axis.pixels, value);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* What value a pixel on an axis stands for — the brush's direction.
|
|
184
|
+
* @param y - The pixel, measured from the top of the drawing area.
|
|
185
|
+
* @param axis - The axis it belongs to.
|
|
186
|
+
* @returns The value there, or the top of a flat axis. Zero comes back
|
|
187
|
+
* unsigned, so an interval that reaches the end of an axis never reads `-0`.
|
|
188
|
+
*/
|
|
189
|
+
export function parallelYToValue(y: number, axis: ParallelAxisLayout): number {
|
|
190
|
+
const { pixels, max } = axis;
|
|
191
|
+
if (pixels.factor === 0) return max;
|
|
192
|
+
const placed = (y - pixels.offset) / pixels.factor;
|
|
193
|
+
if (pixels.logarithmic) return 10 ** placed;
|
|
194
|
+
return placed === 0 ? 0 : placed;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function axisExtent(
|
|
198
|
+
axis: ParallelAxis,
|
|
199
|
+
count: number,
|
|
200
|
+
): { min: number; max: number } {
|
|
201
|
+
const domain = axis.domain;
|
|
202
|
+
if (domain === undefined) return parallelExtent(axis.values, count);
|
|
203
|
+
return {
|
|
204
|
+
min: Math.min(domain[0], domain[1]),
|
|
205
|
+
max: Math.max(domain[0], domain[1]),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The brush, as arithmetic rather than as a library.
|
|
3
|
+
*
|
|
4
|
+
* Everything a vertical brush does — create by dragging on a bare axis, move
|
|
5
|
+
* the band, drag either edge, clear by pressing outside it — is four answers
|
|
6
|
+
* to one question: which part of the axis the press landed on. Written this
|
|
7
|
+
* way it is a pure function of a press and a pointer, so the whole gesture is
|
|
8
|
+
* covered by tests in a runtime with no DOM at all, which is the one thing a
|
|
9
|
+
* DOM-mutating brush could never be.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ParallelAxisLayout } from './parallelAxes.ts';
|
|
13
|
+
import { parallelValueToY, parallelYToValue } from './parallelAxes.ts';
|
|
14
|
+
import type { ParallelRange } from './parallelTypes.ts';
|
|
15
|
+
|
|
16
|
+
/** A band kept on one axis, in pixels from the top of the drawing area. */
|
|
17
|
+
export interface ParallelBand {
|
|
18
|
+
/** The upper edge, which is the higher value. */
|
|
19
|
+
top: number;
|
|
20
|
+
/** The lower edge, which is the lower value. */
|
|
21
|
+
bottom: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Which part of an axis a press landed on. */
|
|
25
|
+
export type ParallelBrushGrip = 'create' | 'move' | 'top' | 'bottom';
|
|
26
|
+
|
|
27
|
+
/** A drag in progress on one axis. */
|
|
28
|
+
export interface ParallelBrushDrag {
|
|
29
|
+
/** The axis being brushed, by its id. */
|
|
30
|
+
axis: string;
|
|
31
|
+
/** What the press took hold of. */
|
|
32
|
+
grip: ParallelBrushGrip;
|
|
33
|
+
/** Where the press landed, in pixels from the top of the drawing area. */
|
|
34
|
+
originY: number;
|
|
35
|
+
/** The band as it stood when the press landed. */
|
|
36
|
+
origin: ParallelBand;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** How close to an edge a press has to land to take hold of it, in pixels. */
|
|
40
|
+
export const PARALLEL_BRUSH_HANDLE_REACH = 4;
|
|
41
|
+
|
|
42
|
+
/** How tall a released band has to be to count as a band rather than a click. */
|
|
43
|
+
export const PARALLEL_BRUSH_SMALLEST = 1;
|
|
44
|
+
|
|
45
|
+
/** How far either side of an axis a press still reaches it, in pixels. */
|
|
46
|
+
export const PARALLEL_BRUSH_HALF_WIDTH = 9;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* What a press at that height takes hold of.
|
|
50
|
+
* @param y - Where the press landed, in pixels from the top of the drawing area.
|
|
51
|
+
* @param band - The band already on that axis, or `null` when it carries none.
|
|
52
|
+
* @returns The grip. A press within {@link PARALLEL_BRUSH_HANDLE_REACH} of an
|
|
53
|
+
* edge takes that edge, a press inside the band moves it, and anything else —
|
|
54
|
+
* including a press on a bare axis — starts a new one.
|
|
55
|
+
*/
|
|
56
|
+
export function parallelBrushGrip(
|
|
57
|
+
y: number,
|
|
58
|
+
band: ParallelBand | null,
|
|
59
|
+
): ParallelBrushGrip {
|
|
60
|
+
if (band === null) return 'create';
|
|
61
|
+
if (Math.abs(y - band.top) <= PARALLEL_BRUSH_HANDLE_REACH) return 'top';
|
|
62
|
+
if (Math.abs(y - band.bottom) <= PARALLEL_BRUSH_HANDLE_REACH) return 'bottom';
|
|
63
|
+
if (y > band.top && y < band.bottom) return 'move';
|
|
64
|
+
return 'create';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The band a drag stands at, now that the pointer is there.
|
|
69
|
+
* @param drag - The drag, as it began.
|
|
70
|
+
* @param y - Where the pointer is, in pixels from the top of the drawing area.
|
|
71
|
+
* @param height - Height of the drawing area, in pixels.
|
|
72
|
+
* @returns The band, held inside the drawing area at both ends. Dragging one
|
|
73
|
+
* edge past the other swaps them rather than inverting the band, and a band
|
|
74
|
+
* being moved keeps its height rather than being squashed against an end.
|
|
75
|
+
*/
|
|
76
|
+
export function parallelBandAt(
|
|
77
|
+
drag: ParallelBrushDrag,
|
|
78
|
+
y: number,
|
|
79
|
+
height: number,
|
|
80
|
+
): ParallelBand {
|
|
81
|
+
const to = clamp(y, 0, height);
|
|
82
|
+
if (drag.grip === 'create') return order(drag.originY, to);
|
|
83
|
+
if (drag.grip === 'top') return order(to, drag.origin.bottom);
|
|
84
|
+
if (drag.grip === 'bottom') return order(drag.origin.top, to);
|
|
85
|
+
const shift = clamp(
|
|
86
|
+
to - drag.originY,
|
|
87
|
+
-drag.origin.top,
|
|
88
|
+
height - drag.origin.bottom,
|
|
89
|
+
);
|
|
90
|
+
return {
|
|
91
|
+
top: drag.origin.top + shift,
|
|
92
|
+
bottom: drag.origin.bottom + shift,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Whether a released band keeps anything, or was a click that clears the axis.
|
|
98
|
+
* @param band - The band, as the pointer left it.
|
|
99
|
+
* @returns Whether it is tall enough to be an interval.
|
|
100
|
+
*/
|
|
101
|
+
export function parallelIsBand(band: ParallelBand): boolean {
|
|
102
|
+
return band.bottom - band.top >= PARALLEL_BRUSH_SMALLEST;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Where an interval sits on its axis, in pixels.
|
|
107
|
+
* @param range - The interval, low value first.
|
|
108
|
+
* @param axis - The axis it belongs to.
|
|
109
|
+
* @returns The band.
|
|
110
|
+
*/
|
|
111
|
+
export function parallelBandOf(
|
|
112
|
+
range: ParallelRange,
|
|
113
|
+
axis: ParallelAxisLayout,
|
|
114
|
+
): ParallelBand {
|
|
115
|
+
return order(
|
|
116
|
+
parallelValueToY(range[1], axis),
|
|
117
|
+
parallelValueToY(range[0], axis),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* What interval a band keeps, in the axis's own units.
|
|
123
|
+
* @param band - The band, in pixels from the top of the drawing area.
|
|
124
|
+
* @param axis - The axis it belongs to.
|
|
125
|
+
* @returns The interval, low value first — which is the order every filter in
|
|
126
|
+
* the family reads, and getting it backwards keeps nothing at all.
|
|
127
|
+
*/
|
|
128
|
+
export function parallelRangeOf(
|
|
129
|
+
band: ParallelBand,
|
|
130
|
+
axis: ParallelAxisLayout,
|
|
131
|
+
): ParallelRange {
|
|
132
|
+
const low = parallelYToValue(band.bottom, axis);
|
|
133
|
+
const high = parallelYToValue(band.top, axis);
|
|
134
|
+
return low <= high ? [low, high] : [high, low];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function order(a: number, b: number): ParallelBand {
|
|
138
|
+
return a <= b ? { top: a, bottom: b } : { top: b, bottom: a };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function clamp(value: number, low: number, high: number): number {
|
|
142
|
+
if (value < low) return low;
|
|
143
|
+
if (value > high) return high;
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two ends of one axis, read off its own column.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately not `chartPadExtent`: a parallel-coordinates axis ends exactly
|
|
5
|
+
* on the data, because the reader compares rows against each other rather than
|
|
6
|
+
* against a round number, and the best row has to touch the top of its axis.
|
|
7
|
+
* The only opening is for a column with no spread at all, which would
|
|
8
|
+
* otherwise draw no axis and place every row on one pixel.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** How far a column with no spread at all is opened, on each side. */
|
|
12
|
+
export const PARALLEL_DEGENERATE_PADDING = 0.5;
|
|
13
|
+
|
|
14
|
+
/** The two ends of an axis, low first. */
|
|
15
|
+
export interface ParallelExtent {
|
|
16
|
+
/** The value at the bottom of the axis. */
|
|
17
|
+
min: number;
|
|
18
|
+
/** The value at its top. */
|
|
19
|
+
max: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The smallest and the largest finite value of one column.
|
|
24
|
+
* @param values - The column, read in place.
|
|
25
|
+
* @param count - How many rows to read, from the start.
|
|
26
|
+
* @returns The extent. A column holding no finite value at all comes back as
|
|
27
|
+
* half a unit either side of zero, and a constant one as half a unit either
|
|
28
|
+
* side of its value, so that both still draw an axis.
|
|
29
|
+
*/
|
|
30
|
+
export function parallelExtent(
|
|
31
|
+
values: ArrayLike<number>,
|
|
32
|
+
count: number,
|
|
33
|
+
): ParallelExtent {
|
|
34
|
+
const rows = Math.min(count, values.length);
|
|
35
|
+
let min = Number.POSITIVE_INFINITY;
|
|
36
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
37
|
+
for (let row = 0; row < rows; row++) {
|
|
38
|
+
const value = values[row] as number;
|
|
39
|
+
if (!Number.isFinite(value)) continue;
|
|
40
|
+
if (value < min) min = value;
|
|
41
|
+
if (value > max) max = value;
|
|
42
|
+
}
|
|
43
|
+
if (min > max) {
|
|
44
|
+
return {
|
|
45
|
+
min: -PARALLEL_DEGENERATE_PADDING,
|
|
46
|
+
max: PARALLEL_DEGENERATE_PADDING,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (min === max) {
|
|
50
|
+
return {
|
|
51
|
+
min: min - PARALLEL_DEGENERATE_PADDING,
|
|
52
|
+
max: max + PARALLEL_DEGENERATE_PADDING,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { min, max };
|
|
56
|
+
}
|