@jbrowse/core 5.0.0-beta.4 → 5.0.0-beta.7
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 +278 -250
- package/esm/PluginManager.js +1 -1
- package/esm/ReExports/publicUi.d.ts +1 -1
- package/esm/ReExports/publicUi.js +1 -1
- package/esm/ReExports/publicUtil.d.ts +1 -1
- package/esm/ReExports/publicUtil.js +1 -1
- package/esm/ReExports/workerNamespaceNames.js +3 -9
- package/esm/assemblyManager/assemblyConfigSchema.d.ts +2 -1
- package/esm/assemblyManager/assemblyConfigSchema.js +2 -1
- package/esm/assemblyManager/expandAssemblyConfigShorthand.d.ts +15 -15
- package/esm/assemblyManager/expandAssemblyConfigShorthand.js +34 -18
- package/esm/configuration/configurationSchema.d.ts +1 -2
- package/esm/configuration/configurationSchema.js +3 -8
- package/esm/configuration/configurationSlot.d.ts +0 -47
- package/esm/configuration/configurationSlot.js +4 -54
- package/esm/configuration/fillLocations.d.ts +14 -0
- package/esm/configuration/fillLocations.js +14 -0
- package/esm/configuration/fullConfSnapshot.d.ts +0 -15
- package/esm/configuration/fullConfSnapshot.js +0 -28
- package/esm/configuration/getConf.d.ts +4 -34
- package/esm/configuration/getConf.js +3 -38
- package/esm/configuration/index.d.ts +3 -4
- package/esm/configuration/index.js +3 -2
- package/esm/configuration/schemaRegistry.d.ts +1 -2
- package/esm/configuration/schemaRegistry.js +1 -2
- package/esm/configuration/slotFacade.d.ts +1 -16
- package/esm/configuration/slotFacade.js +1 -2
- package/esm/configuration/tabixShorthand.js +3 -3
- package/esm/configuration/types.d.ts +4 -16
- package/esm/data_adapters/CytobandAdapter/configSchema.js +3 -4
- package/esm/pluggableElementTypes/DisplayType.d.ts +5 -3
- package/esm/pluggableElementTypes/DisplayType.js +3 -1
- package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +30 -0
- package/esm/pluggableElementTypes/models/BaseTrackModel.js +45 -17
- package/esm/rpc/RpcRegistry.d.ts +7 -1
- package/esm/rpc/coreRpcMethods.d.ts +1 -0
- package/esm/rpc/coreRpcMethods.js +1 -0
- package/esm/rpc/methods/CoreEncodeFeatures.d.ts +15 -0
- package/esm/rpc/methods/CoreEncodeFeatures.js +62 -0
- package/esm/svg/svgId.d.ts +1 -1
- package/esm/svg/svgId.js +1 -1
- package/esm/ui/LegendSwatchGlyph.js +26 -21
- package/esm/ui/MenuTypes.d.ts +5 -52
- package/esm/ui/SizeSliderRow.d.ts +1 -3
- package/esm/ui/SizeSliderRow.js +4 -6
- package/esm/ui/SvgColorLegend.d.ts +9 -3
- package/esm/ui/SvgColorLegend.js +74 -33
- package/esm/ui/colorScale.d.ts +64 -0
- package/esm/ui/colorScale.js +41 -0
- package/esm/ui/colors.d.ts +11 -0
- package/esm/ui/colors.js +7 -0
- package/esm/ui/index.d.ts +5 -7
- package/esm/ui/index.js +3 -13
- package/esm/ui/legendMenuItem.d.ts +5 -16
- package/esm/ui/legendMenuItem.js +3 -14
- package/esm/ui/legendSpec.d.ts +11 -5
- package/esm/ui/legendSpec.js +8 -11
- package/esm/ui/makeSizeMenu.d.ts +0 -6
- package/esm/ui/makeSizeMenu.js +6 -30
- package/esm/ui/measureLegendText.js +1 -1
- package/esm/ui/menuItemAdornment.d.ts +9 -19
- package/esm/ui/menuItemAdornment.js +10 -31
- package/esm/ui/menuItems.d.ts +2 -6
- package/esm/ui/menuItems.js +0 -4
- package/esm/ui/palette.js +2 -1
- package/esm/ui/toggleMenuItems.d.ts +6 -13
- package/esm/ui/toggleMenuItems.js +13 -29
- package/esm/util/colorRamp.d.ts +3 -3
- package/esm/util/colorRamp.js +2 -2
- package/esm/util/featureTransforms.d.ts +15 -0
- package/esm/util/featureTransforms.js +342 -0
- package/esm/util/freezeDeep.d.ts +8 -10
- package/esm/util/freezeDeep.js +8 -10
- package/esm/util/glyphNames.d.ts +8 -0
- package/esm/util/glyphNames.js +12 -0
- package/esm/util/index.d.ts +0 -1
- package/esm/util/index.js +0 -1
- package/esm/util/markEncoding.d.ts +76 -0
- package/esm/util/markEncoding.js +436 -0
- package/esm/util/markEncodingTypes.d.ts +331 -0
- package/esm/util/markEncodingTypes.js +8 -0
- package/esm/util/renderToStaticMarkup.js +1 -1
- package/esm/util/starFusion.d.ts +26 -0
- package/esm/util/starFusion.js +37 -0
- package/esm/util/trackConfigDelta.d.ts +4 -8
- package/esm/util/trackConfigDelta.js +2 -4
- package/esm/util/tracks.d.ts +7 -1
- package/esm/util/tracks.js +72 -7
- package/esm/util/types/index.d.ts +12 -4
- package/esm/util/whenViewSettled.d.ts +9 -0
- package/esm/util/whenViewSettled.js +12 -4
- package/package.json +13 -3
- package/esm/configuration/promotableDefaults.d.ts +0 -234
- package/esm/configuration/promotableDefaults.js +0 -395
- package/esm/configuration/promotablePin.d.ts +0 -65
- package/esm/configuration/promotablePin.js +0 -1
- package/esm/configuration/promotableResolve.d.ts +0 -108
- package/esm/configuration/promotableResolve.js +0 -117
- package/esm/configuration/promotableSlots.d.ts +0 -12
- package/esm/configuration/promotableSlots.js +0 -45
- package/esm/configuration/slotShape.d.ts +0 -12
- package/esm/configuration/slotShape.js +0 -84
- package/esm/ui/PinAdornment.d.ts +0 -5
- package/esm/ui/PinAdornment.js +0 -67
- package/esm/ui/SvgGradientLegend.d.ts +0 -26
- package/esm/ui/SvgGradientLegend.js +0 -26
- package/esm/ui/promotablePinCoverage.d.ts +0 -58
- package/esm/ui/promotablePinCoverage.js +0 -124
- package/esm/util/openDisplays.d.ts +0 -23
- package/esm/util/openDisplays.js +0 -79
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
//
|
|
2
|
+
import { appendGlyph } from '@jbrowse/render-core/marks/glyphPaint';
|
|
3
|
+
import { DIAMOND_GLYPH_SCALE } from '@jbrowse/render-core/shaders/pointMarkConsts';
|
|
4
|
+
import { GLYPH_CODES } from '../util/glyphNames.js';
|
|
5
|
+
// The point shape's own painter, recorded as SVG path data, so the key draws
|
|
6
|
+
// the glyph the plot draws. The diamond paints wider than its diameter and
|
|
7
|
+
// is scaled down to fit the box.
|
|
8
|
+
function glyphPathData(glyph, size, x, y) {
|
|
9
|
+
const d = [];
|
|
10
|
+
const path = {
|
|
11
|
+
moveTo: (px, py) => d.push(`M${px} ${py}`),
|
|
12
|
+
lineTo: (px, py) => d.push(`L${px} ${py}`),
|
|
13
|
+
closePath: () => d.push('Z'),
|
|
14
|
+
rect: (px, py, w, h) => d.push(`M${px} ${py}h${w}v${h}h${-w}Z`),
|
|
15
|
+
arc: (cx, cy, r) => d.push(`A${r} ${r} 0 1 0 ${cx - r} ${cy}A${r} ${r} 0 1 0 ${cx + r} ${cy}Z`),
|
|
16
|
+
};
|
|
17
|
+
const diameter = glyph === 'diamond' ? size / DIAMOND_GLYPH_SCALE : size;
|
|
18
|
+
appendGlyph(path, GLYPH_CODES[glyph], x + size / 2, y + size / 2, diameter);
|
|
19
|
+
return d.join('');
|
|
20
|
+
}
|
|
21
|
+
// The one place that decides what a legend swatch looks like. Returns a bare
|
|
22
|
+
// shape rather than a rooted <svg>, so the floating legend (which wraps it in an
|
|
23
|
+
// inline <svg>) and the SVG export (which drops it into a row <g>) draw the
|
|
24
|
+
// identical box.
|
|
7
25
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
26
|
+
// The shape fills the `size` box from (x, y) so a caller can lay swatches out on
|
|
27
|
+
// a fixed pitch.
|
|
10
28
|
export function LegendSwatchGlyph({ swatch, size, x = 0, y = 0, }) {
|
|
11
|
-
|
|
12
|
-
// Thin enough to read as a stroke next to a solid block, thick enough to
|
|
13
|
-
// carry a color at 10-12px.
|
|
14
|
-
const strokeWidth = Math.max(2, Math.round(size / 5));
|
|
15
|
-
if (mark === 'line') {
|
|
16
|
-
return (_jsx("line", { x1: x, x2: x + size, y1: y + size / 2, y2: y + size / 2, stroke: color, strokeWidth: strokeWidth }));
|
|
17
|
-
}
|
|
18
|
-
if (mark === 'curve') {
|
|
19
|
-
// An arc bulging up out of the box's lower corners, which is the shape of
|
|
20
|
-
// the connector itself — a pair of endpoints with the curve between them.
|
|
21
|
-
const inset = strokeWidth / 2;
|
|
22
|
-
return (_jsx("path", { d: `M${x},${y + size - inset} Q${x + size / 2},${y - size / 3} ${x + size},${y + size - inset}`, fill: "none", stroke: color, strokeWidth: strokeWidth }));
|
|
23
|
-
}
|
|
24
|
-
return _jsx("rect", { x: x, y: y, width: size, height: size, fill: color });
|
|
29
|
+
return swatch.glyph ? (_jsx("path", { d: glyphPathData(swatch.glyph, size, x, y), fill: swatch.color })) : (_jsx("rect", { x: x, y: y, width: size, height: size, fill: swatch.color }));
|
|
25
30
|
}
|
package/esm/ui/MenuTypes.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Pin } from '../configuration/promotablePin.ts';
|
|
2
1
|
/** #menuItem divider | a horizontal rule; not clickable */
|
|
3
2
|
export interface MenuDivider {
|
|
4
3
|
priority?: number;
|
|
@@ -51,7 +50,7 @@ export interface BaseMenuItem {
|
|
|
51
50
|
*/
|
|
52
51
|
keepMenuOpen?: boolean;
|
|
53
52
|
/**
|
|
54
|
-
* #menuField arbitrary trailing content
|
|
53
|
+
* #menuField arbitrary trailing content
|
|
55
54
|
*
|
|
56
55
|
* Extra content rendered at the trailing (right) edge of the row, after the
|
|
57
56
|
* checkbox/radio decoration and help icon — e.g. a secondary toggle. The
|
|
@@ -59,56 +58,10 @@ export interface BaseMenuItem {
|
|
|
59
58
|
* onClick or dismiss the menu.
|
|
60
59
|
*
|
|
61
60
|
* An **element**, so a module that sets it drags React and whatever it renders
|
|
62
|
-
* into its own graph.
|
|
63
|
-
*
|
|
64
|
-
* a description instead. Prefer `pin`; reach for this only when the
|
|
65
|
-
* content is genuinely arbitrary.
|
|
61
|
+
* into its own graph. Reach for it only when the content is genuinely
|
|
62
|
+
* arbitrary — synteny's and wiggle's colour swatches are what it exists for.
|
|
66
63
|
*/
|
|
67
64
|
endAdornment?: React.ReactNode;
|
|
68
|
-
/**
|
|
69
|
-
* #menuField the "apply this to all open tracks of this type" pin; set it through a row builder's `pin` option
|
|
70
|
-
*
|
|
71
|
-
* The trailing "default for all tracks of this type" pin, as a **description**
|
|
72
|
-
* rather than an element — the renderer builds `PinAdornment` from
|
|
73
|
-
* it. Same rule as a `TrackControlProps` icon name (reference/DISPLAYCHROME.md):
|
|
74
|
-
* menu-item builders are called from state models and menu modules, which are
|
|
75
|
-
* eager, so an element here puts MUI's `ToggleButton`, `Tooltip` and two icons
|
|
76
|
-
* into every host's first paint. It did, until 2026-08-05; see
|
|
77
|
-
* reference/EAGER_BUNDLE.md.
|
|
78
|
-
*
|
|
79
|
-
* Set it through the `pin` option of `checkboxItem` / `toggleItem` /
|
|
80
|
-
* `radioItem` / `radioItems`, not by hand.
|
|
81
|
-
*
|
|
82
|
-
* A `type: 'custom'` row (`makePromotableSizeMenu`) draws its own pin inside
|
|
83
|
-
* its rendered content and still sets this — the declaration is what lets a
|
|
84
|
-
* built menu be asked which promotable slots it offers a pin for at all. Such
|
|
85
|
-
* a row is excluded from the trailing-column reservation; see
|
|
86
|
-
* `hasMenuItemAdornment`.
|
|
87
|
-
*/
|
|
88
|
-
pin?: MenuItemPin;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* A row's pin declaration: the {@link Pin} `makePin` built, plus the one thing
|
|
92
|
-
* the row knows and the pin doesn't — what to call the setting.
|
|
93
|
-
*
|
|
94
|
-
* **`control` holds the pin by reference; don't flatten this into
|
|
95
|
-
* `interface MenuItemPin extends Pin`.** That was tried, to save the hop every
|
|
96
|
-
* reader spells (`item.pin.control.active`), and it turns the declaration into a
|
|
97
|
-
* *copy* of the control — so a `Pin` whose `toggle` resolves anything through
|
|
98
|
-
* `this` silently mutates the copy and the real control never changes. Nothing in
|
|
99
|
-
* `Pin` promises copy-safety: `makePin` happens to close over what it needs, but
|
|
100
|
-
* the interface is a plain object a plugin can build by hand, and the failure is
|
|
101
|
-
* a pin that draws correctly and does nothing. Two hops in `pinnedSlots` and
|
|
102
|
-
* `menuItemAdornment` is the whole cost of not having that.
|
|
103
|
-
*/
|
|
104
|
-
export interface MenuItemPin {
|
|
105
|
-
control: Pin;
|
|
106
|
-
/**
|
|
107
|
-
* Names the setting in the pin's tooltip and aria-label. Carried here rather
|
|
108
|
-
* than read off the row's `label`, which is a `ReactNode` and may not be a
|
|
109
|
-
* string; a pin that can't name what it promotes reads as a bug.
|
|
110
|
-
*/
|
|
111
|
-
label: string;
|
|
112
65
|
}
|
|
113
66
|
/** #menuItem normal | an action row; the default when `type` is omitted */
|
|
114
67
|
export interface NormalMenuItem extends BaseMenuItem {
|
|
@@ -133,8 +86,8 @@ export interface RadioMenuItem extends BaseMenuItem {
|
|
|
133
86
|
* The function form is called when the submenu opens (or a walker asks for its
|
|
134
87
|
* rows), not when the parent menu is built — for a submenu whose rows cost a
|
|
135
88
|
* scan of the display's data to name. Read the field through `resolveSubMenu`
|
|
136
|
-
* rather than by hand, so the two forms stay one thing to the renderer
|
|
137
|
-
*
|
|
89
|
+
* rather than by hand, so the two forms stay one thing to the renderer and to
|
|
90
|
+
* every test helper that flattens a menu.
|
|
138
91
|
*/
|
|
139
92
|
export interface SubMenuItem extends BaseMenuItem {
|
|
140
93
|
type?: 'subMenu';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { Pin } from '../configuration/promotablePin.ts';
|
|
2
1
|
import type { SliderScale } from './sliderScale.ts';
|
|
3
|
-
export declare const SizeSliderRow: ({ title, getValue, min, max, step, scale, format, isDefault, commitOnRelease, onChange, onReset,
|
|
2
|
+
export declare const SizeSliderRow: ({ title, getValue, min, max, step, scale, format, isDefault, commitOnRelease, onChange, onReset, }: {
|
|
4
3
|
title: string;
|
|
5
4
|
getValue: () => number;
|
|
6
5
|
min: number;
|
|
@@ -12,5 +11,4 @@ export declare const SizeSliderRow: ({ title, getValue, min, max, step, scale, f
|
|
|
12
11
|
commitOnRelease?: boolean;
|
|
13
12
|
onChange: (n: number) => void;
|
|
14
13
|
onReset: () => void;
|
|
15
|
-
pin?: Pin;
|
|
16
14
|
}) => import("react").JSX.Element;
|
package/esm/ui/SizeSliderRow.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// The drawn half of a `makeSizeMenu` row, split out so the builder can reach
|
|
3
3
|
// it through `lazy()`. Everything Material UI in a size row is here: the
|
|
4
|
-
// slider, the reset button, the
|
|
4
|
+
// slider, the reset button, the caption. See makeSizeMenu.tsx.
|
|
5
5
|
import { useState } from 'react';
|
|
6
6
|
import { Typography } from '@mui/material';
|
|
7
7
|
import { observer } from 'mobx-react';
|
|
8
8
|
import { makeStyles } from '../util/tss-react/index.js';
|
|
9
9
|
import { ResetToDefaultButton } from './InlineMenuControls.js';
|
|
10
|
-
import { PinAdornment } from './PinAdornment.js';
|
|
11
10
|
import SingleSlider from './SingleSlider.js';
|
|
12
11
|
import { INLINE_MENU_ROW_WIDTH } from './inlineMenuRowWidth.js';
|
|
13
12
|
import { sliderScale } from './sliderScale.js';
|
|
@@ -24,16 +23,15 @@ const useStyles = makeStyles()(theme => ({
|
|
|
24
23
|
flex: 1,
|
|
25
24
|
},
|
|
26
25
|
}));
|
|
27
|
-
export const SizeSliderRow = observer(function SizeSliderRow({ title, getValue, min, max, step, scale, format, isDefault, commitOnRelease, onChange, onReset,
|
|
26
|
+
export const SizeSliderRow = observer(function SizeSliderRow({ title, getValue, min, max, step, scale, format, isDefault, commitOnRelease, onChange, onReset, }) {
|
|
28
27
|
const { classes } = useStyles();
|
|
29
|
-
const modelValue = getValue();
|
|
30
28
|
const [dragValue, setDragValue] = useState(undefined);
|
|
31
|
-
const value = dragValue ??
|
|
29
|
+
const value = dragValue ?? getValue();
|
|
32
30
|
const { toSlider, fromSlider, sliderStep } = sliderScale(scale);
|
|
33
31
|
const slug = title.toLowerCase().replaceAll(' ', '-');
|
|
34
32
|
return (_jsxs("div", { className: classes.root, children: [_jsxs("div", { className: classes.header, children: [_jsxs(Typography, { variant: "caption", color: "textSecondary", className: classes.label, children: [title, ": ", format(value)] }), _jsx(ResetToDefaultButton, { disabled: isDefault, onClick: () => {
|
|
35
33
|
onReset();
|
|
36
|
-
} })
|
|
34
|
+
} })] }), _jsx(SingleSlider, { value: toSlider(value), min: toSlider(min), max: toSlider(max), step: sliderStep ?? step, size: "small", "aria-label": title.toLowerCase(), "data-testid": `${slug}-slider`, valueLabelDisplay: "auto", valueLabelFormat: (v) => format(fromSlider(v)), sx: { py: 0.5, display: 'block' }, onChange: v => {
|
|
37
35
|
const n = fromSlider(v);
|
|
38
36
|
if (commitOnRelease) {
|
|
39
37
|
setDragValue(n);
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import type { ColorLegendEntry } from './legendSpec.ts';
|
|
2
|
-
import type { ReactNode } from 'react';
|
|
3
2
|
export type { ColorLegendEntry } from './legendSpec.ts';
|
|
4
3
|
export declare const LEGEND_ROW_HEIGHT = 14;
|
|
5
4
|
export declare const LEGEND_SWATCH = 10;
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Horizontal space the LGV export reserves beside the plot for a display whose
|
|
7
|
+
* legend asked to sit off the plot (`LegendMixin.svgLegendWidth`): the widest
|
|
8
|
+
* row this draws for a ramp, plus a margin either side.
|
|
9
|
+
*/
|
|
10
|
+
export declare const LEGEND_SVG_GUTTER_WIDTH: number;
|
|
11
|
+
export default function SvgColorLegend({ entries, canvasWidth, x, maxHeight, onDismiss, testid, idPrefix, }: {
|
|
7
12
|
entries: ColorLegendEntry[];
|
|
8
13
|
canvasWidth: number;
|
|
14
|
+
x?: number;
|
|
9
15
|
maxHeight?: number;
|
|
10
16
|
onDismiss?: () => void;
|
|
11
|
-
|
|
17
|
+
idPrefix?: string;
|
|
12
18
|
testid?: string;
|
|
13
19
|
}): import("react").JSX.Element | null;
|
package/esm/ui/SvgColorLegend.js
CHANGED
|
@@ -1,35 +1,68 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/* eslint-disable react-refresh/only-export-components -- geometry constants belong with this leaf SVG primitive; no component state to fast-refresh */
|
|
3
|
+
import { useId } from 'react';
|
|
4
|
+
import { svgSafeId } from '../svg/svgId.js';
|
|
3
5
|
import { LegendSwatchGlyph } from './LegendSwatchGlyph.js';
|
|
4
6
|
import { measureLegendText } from './measureLegendText.js';
|
|
5
7
|
const FONT_SIZE = 10;
|
|
6
8
|
const SWATCH_LEFT = 2;
|
|
7
9
|
const SWATCH_GAP = 2;
|
|
8
|
-
// Row geometry, exported so callers
|
|
9
|
-
//
|
|
10
|
+
// Row geometry, exported so callers sizing an outer container stay in lockstep
|
|
11
|
+
// with what this draws.
|
|
10
12
|
export const LEGEND_ROW_HEIGHT = 14;
|
|
11
13
|
export const LEGEND_SWATCH = 10;
|
|
14
|
+
const GRADIENT_BAR_WIDTH = 100;
|
|
15
|
+
const GRADIENT_BAR_HEIGHT = 10;
|
|
16
|
+
// the bar plus the row of domain labels under it
|
|
17
|
+
const GRADIENT_ROW_HEIGHT = GRADIENT_BAR_HEIGHT + 4 + LEGEND_ROW_HEIGHT;
|
|
18
|
+
/**
|
|
19
|
+
* Horizontal space the LGV export reserves beside the plot for a display whose
|
|
20
|
+
* legend asked to sit off the plot (`LegendMixin.svgLegendWidth`): the widest
|
|
21
|
+
* row this draws for a ramp, plus a margin either side.
|
|
22
|
+
*/
|
|
23
|
+
export const LEGEND_SVG_GUTTER_WIDTH = GRADIENT_BAR_WIDTH + 40;
|
|
12
24
|
// narrow right-side gutter reserved for the small dismiss "×"
|
|
13
25
|
const DISMISS_GUTTER = 11;
|
|
14
|
-
// A row's
|
|
26
|
+
// A row's swatches, with the flat `color` shorthand read as the single
|
|
15
27
|
// square it has always drawn.
|
|
16
28
|
function entrySwatches(entry) {
|
|
17
29
|
return (entry.swatches ??
|
|
18
30
|
(entry.color === undefined ? [] : [{ color: entry.color }]));
|
|
19
31
|
}
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
// A ramp row is the bar and its labels, captioned when the entry has a label.
|
|
33
|
+
function rowHeight(entry) {
|
|
34
|
+
return entry.gradient
|
|
35
|
+
? GRADIENT_ROW_HEIGHT + (entry.label ? LEGEND_ROW_HEIGHT : 0)
|
|
36
|
+
: LEGEND_ROW_HEIGHT;
|
|
37
|
+
}
|
|
38
|
+
// The rows that fit `maxHeight`, with the last fitting row given over to a
|
|
39
|
+
// "+N more" summary when they do not all fit.
|
|
40
|
+
function fitEntries(entries, maxHeight) {
|
|
41
|
+
let used = 0;
|
|
42
|
+
const shown = [];
|
|
43
|
+
for (const [idx, entry] of entries.entries()) {
|
|
44
|
+
const rest = entries.length - idx - 1;
|
|
45
|
+
const reserve = rest > 0 ? LEGEND_ROW_HEIGHT : 0;
|
|
46
|
+
if (used + rowHeight(entry) + reserve > maxHeight && shown.length > 0) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
used += rowHeight(entry);
|
|
50
|
+
shown.push(entry);
|
|
51
|
+
}
|
|
52
|
+
return shown;
|
|
53
|
+
}
|
|
54
|
+
function GradientRow({ entry, width, gradientId, }) {
|
|
55
|
+
const { stops, minLabel, maxLabel } = entry.gradient;
|
|
56
|
+
const barTop = (entry.label ? LEGEND_ROW_HEIGHT : 0) + 2;
|
|
57
|
+
const labelY = barTop + GRADIENT_BAR_HEIGHT + 2 + FONT_SIZE;
|
|
58
|
+
return (_jsxs(_Fragment, { children: [_jsx("rect", { x: 0, y: 0, width: width, height: rowHeight(entry), fill: "rgba(255,255,255,0.95)" }), entry.label ? (_jsx("text", { x: SWATCH_LEFT, y: 11, fontSize: FONT_SIZE, fill: "black", children: entry.label })) : null, _jsx("defs", { children: _jsx("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "100%", y2: "0%", children: stops.map(stop => (_jsx("stop", { offset: stop.offset, style: { stopColor: stop.color, stopOpacity: stop.opacity ?? 1 } }, stop.offset))) }) }), _jsx("rect", { x: SWATCH_LEFT, y: barTop, width: GRADIENT_BAR_WIDTH, height: GRADIENT_BAR_HEIGHT, fill: `url(#${gradientId})`, rx: 2 }), _jsx("text", { x: SWATCH_LEFT, y: labelY, fontSize: FONT_SIZE, fill: "black", children: minLabel }), _jsx("text", { x: SWATCH_LEFT + GRADIENT_BAR_WIDTH, y: labelY, fontSize: FONT_SIZE, fill: "black", textAnchor: "end", children: maxLabel })] }));
|
|
59
|
+
}
|
|
60
|
+
// Shared SVG color key: one translucent row per entry — a swatch and a label,
|
|
61
|
+
// or a ramp bar with its domain ends — right-aligned within `canvasWidth`, or
|
|
62
|
+
// at `x` when the caller places it (the LGV export's legend gutter). A <g>, so
|
|
63
|
+
// an on-screen SVG overlay and the export share one renderer.
|
|
31
64
|
//
|
|
32
|
-
// `maxHeight` (
|
|
65
|
+
// `maxHeight` (the display height) caps the box: entries past what fits
|
|
33
66
|
// collapse into a trailing "+N more" summary row, so the legend never overflows
|
|
34
67
|
// its display — the full list stays reachable via the track menu.
|
|
35
68
|
//
|
|
@@ -37,29 +70,37 @@ function entrySwatches(entry) {
|
|
|
37
70
|
// pointer-events so it works under a pointer-events:none overlay). Pass it only
|
|
38
71
|
// on interactive paths where the legend can be re-shown — never on the SVG
|
|
39
72
|
// export, which has no way to click it.
|
|
40
|
-
export default function SvgColorLegend({ entries, canvasWidth, maxHeight, onDismiss,
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
export default function SvgColorLegend({ entries, canvasWidth, x, maxHeight, onDismiss, testid, idPrefix, }) {
|
|
74
|
+
const reactId = useId();
|
|
75
|
+
const legendId = idPrefix ?? reactId;
|
|
76
|
+
const shown = maxHeight === undefined ? entries : fitEntries(entries, maxHeight);
|
|
77
|
+
const overflowLabel = entries.length > shown.length
|
|
78
|
+
? `+${entries.length - shown.length} more`
|
|
79
|
+
: undefined;
|
|
47
80
|
// Labels line up across rows, so the swatch column is sized by the row with
|
|
48
|
-
// the most
|
|
49
|
-
// column the others share. One
|
|
81
|
+
// the most swatches — otherwise a two-box row shoves its own label out of the
|
|
82
|
+
// column the others share. One box reproduces the original 16px inset.
|
|
50
83
|
const swatchColumns = Math.max(1, ...shown.map(e => entrySwatches(e).length));
|
|
51
84
|
const textLeft = SWATCH_LEFT + swatchColumns * (LEGEND_SWATCH + SWATCH_GAP) + 2;
|
|
52
|
-
let
|
|
85
|
+
let contentRight = 0;
|
|
53
86
|
for (const entry of shown) {
|
|
54
|
-
|
|
87
|
+
contentRight = Math.max(contentRight, entry.gradient
|
|
88
|
+
? SWATCH_LEFT +
|
|
89
|
+
Math.max(GRADIENT_BAR_WIDTH, measureLegendText(entry.label, FONT_SIZE))
|
|
90
|
+
: textLeft + measureLegendText(entry.label, FONT_SIZE));
|
|
55
91
|
}
|
|
56
92
|
if (overflowLabel !== undefined) {
|
|
57
|
-
|
|
93
|
+
contentRight = Math.max(contentRight, textLeft + measureLegendText(overflowLabel, FONT_SIZE));
|
|
94
|
+
}
|
|
95
|
+
const totalWidth = contentRight + 6 + (onDismiss ? DISMISS_GUTTER : 0);
|
|
96
|
+
const left = x ?? Math.max(0, canvasWidth - totalWidth - 4);
|
|
97
|
+
const rowTops = [];
|
|
98
|
+
let y = 0;
|
|
99
|
+
for (const entry of shown) {
|
|
100
|
+
rowTops.push(y);
|
|
101
|
+
y += rowHeight(entry);
|
|
58
102
|
}
|
|
59
|
-
|
|
60
|
-
const x = Math.max(0, canvasWidth - totalWidth - 4);
|
|
61
|
-
return shown.length || overflowLabel || children ? (_jsxs("g", { transform: `translate(${x} 0)`, "data-testid": testid, children: [shown.map((entry, idx) => (_jsxs("g", { transform: `translate(0 ${idx * LEGEND_ROW_HEIGHT})`, children: [_jsx("rect", { x: 0, y: 0, width: totalWidth, height: LEGEND_ROW_HEIGHT, fill: "rgba(255,255,255,0.95)" }), _jsxs("g", { opacity: entry.hidden ? 0.35 : 1, children: [entry.marker ??
|
|
62
|
-
entrySwatches(entry).map((swatch, i) => (_jsx(LegendSwatchGlyph, { swatch: swatch, size: LEGEND_SWATCH, x: SWATCH_LEFT + i * (LEGEND_SWATCH + SWATCH_GAP), y: 2 }, `${swatch.color}-${swatch.mark ?? 'fill'}`))), _jsx("text", { x: textLeft, y: 11, fontSize: FONT_SIZE, fill: "black", textDecoration: entry.hidden ? 'line-through' : undefined, children: entry.label })] })] }, entry.key))), overflowLabel === undefined ? null : (_jsxs("g", { transform: `translate(0 ${shown.length * LEGEND_ROW_HEIGHT})`, children: [_jsx("rect", { x: 0, y: 0, width: totalWidth, height: LEGEND_ROW_HEIGHT, fill: "rgba(255,255,255,0.95)" }), _jsx("text", { x: textLeft, y: 11, fontSize: FONT_SIZE, fill: "#555", children: overflowLabel })] })), onDismiss === undefined ? null : (_jsxs("g", { transform: `translate(${totalWidth - DISMISS_GUTTER} 0)`, style: { cursor: 'pointer', pointerEvents: 'auto' }, "data-gesture-owner": "true", onClick: () => {
|
|
103
|
+
return shown.length || overflowLabel ? (_jsxs("g", { transform: `translate(${left} 0)`, "data-testid": testid, children: [shown.map((entry, idx) => (_jsx("g", { transform: `translate(0 ${rowTops[idx]})`, children: entry.gradient ? (_jsx(GradientRow, { entry: entry, width: totalWidth, gradientId: svgSafeId(`${legendId}-${entry.key}`) })) : (_jsxs(_Fragment, { children: [_jsx("rect", { x: 0, y: 0, width: totalWidth, height: LEGEND_ROW_HEIGHT, fill: "rgba(255,255,255,0.95)" }), _jsxs("g", { opacity: entry.hidden ? 0.35 : 1, children: [entrySwatches(entry).map((swatch, i) => (_jsx(LegendSwatchGlyph, { swatch: swatch, size: LEGEND_SWATCH, x: SWATCH_LEFT + i * (LEGEND_SWATCH + SWATCH_GAP), y: 2 }, swatch.color))), _jsx("text", { x: textLeft, y: 11, fontSize: FONT_SIZE, fill: "black", textDecoration: entry.hidden ? 'line-through' : undefined, children: entry.label })] })] })) }, entry.key))), overflowLabel === undefined ? null : (_jsxs("g", { transform: `translate(0 ${y})`, children: [_jsx("rect", { x: 0, y: 0, width: totalWidth, height: LEGEND_ROW_HEIGHT, fill: "rgba(255,255,255,0.95)" }), _jsx("text", { x: textLeft, y: 11, fontSize: FONT_SIZE, fill: "#555", children: overflowLabel })] })), onDismiss === undefined ? null : (_jsxs("g", { transform: `translate(${totalWidth - DISMISS_GUTTER} 0)`, style: { cursor: 'pointer', pointerEvents: 'auto' }, "data-gesture-owner": "true", onClick: () => {
|
|
63
104
|
onDismiss();
|
|
64
|
-
}, children: [_jsx("title", { children: "Hide legend" }), _jsx("rect", { x: 0, y: 0, width: DISMISS_GUTTER, height: LEGEND_ROW_HEIGHT, fill: "transparent" }), _jsx("text", { x: DISMISS_GUTTER / 2, y: 9, fontSize: 9, fill: "#777", textAnchor: "middle", children: "\u00D7" })] }))
|
|
105
|
+
}, children: [_jsx("title", { children: "Hide legend" }), _jsx("rect", { x: 0, y: 0, width: DISMISS_GUTTER, height: LEGEND_ROW_HEIGHT, fill: "transparent" }), _jsx("text", { x: DISMISS_GUTTER / 2, y: 9, fontSize: 9, fill: "#777", textAnchor: "middle", children: "\u00D7" })] }))] })) : null;
|
|
65
106
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { LegendSpec, LegendSwatch } from './legendSpec.ts';
|
|
2
|
+
/**
|
|
3
|
+
* One value a categorical scale colors. `value` is the datum the color
|
|
4
|
+
* classifies (an SV type, a sample group, an impact tier), `label` how the key
|
|
5
|
+
* names it. A row with no `color` is a note in the key rather than a swatch —
|
|
6
|
+
* "+N more", or a rule the swatches cannot state.
|
|
7
|
+
*/
|
|
8
|
+
export interface CategoricalEntry {
|
|
9
|
+
value: string;
|
|
10
|
+
label: string;
|
|
11
|
+
color?: string;
|
|
12
|
+
swatches?: LegendSwatch[];
|
|
13
|
+
hidden?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A color vocabulary: a finite set of values, each drawn in one color. ADR-094's
|
|
17
|
+
* palette-index and per-instance-value cardinalities both resolve to this once
|
|
18
|
+
* the values are enumerated.
|
|
19
|
+
*/
|
|
20
|
+
export interface CategoricalScale {
|
|
21
|
+
kind: 'categorical';
|
|
22
|
+
id: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
entries: CategoricalEntry[];
|
|
25
|
+
}
|
|
26
|
+
/** One stop of a continuous ramp, `offset` in `[0, 1]`. */
|
|
27
|
+
export interface RampStop {
|
|
28
|
+
offset: number;
|
|
29
|
+
color: string;
|
|
30
|
+
opacity?: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A scalar through a color ramp: ADR-094's scalar-through-a-scale
|
|
34
|
+
* cardinality. `stops` are what the painter samples — read them out of the
|
|
35
|
+
* same LUT the GPU uploads (`stopsFromRampLut`) so the bar is byte-identical
|
|
36
|
+
* to the plot. `format` prints the domain ends; `toLocale` by default.
|
|
37
|
+
*/
|
|
38
|
+
export interface RampScale {
|
|
39
|
+
kind: 'ramp';
|
|
40
|
+
id: string;
|
|
41
|
+
title?: string;
|
|
42
|
+
domain: [number, number];
|
|
43
|
+
stops: RampStop[];
|
|
44
|
+
format?: (value: number) => string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* What a display says about one of its color channels. A display declares a
|
|
48
|
+
* list of these (`LegendMixin.colorScales`) and the legend derives from it, so
|
|
49
|
+
* the key cannot list a color nothing painted.
|
|
50
|
+
*/
|
|
51
|
+
export type ColorScale = CategoricalScale | RampScale;
|
|
52
|
+
/**
|
|
53
|
+
* The one derivation of a legend from a display's color scales: a section per
|
|
54
|
+
* scale, in the order declared. On screen `FloatingLegend` renders the result
|
|
55
|
+
* and the export flattens it through `legendEntries`, so the two cannot
|
|
56
|
+
* describe different colors.
|
|
57
|
+
*
|
|
58
|
+
* A lone scale names itself: section titles draw only beside other sections,
|
|
59
|
+
* so a lone categorical scale's title becomes the box's (Manhattan's field
|
|
60
|
+
* name over its values) and a lone ramp's goes on its row.
|
|
61
|
+
*/
|
|
62
|
+
export declare function legendSpecOf(scales: ColorScale[]): LegendSpec;
|
|
63
|
+
/** Whether a scale has anything for a key to show. */
|
|
64
|
+
export declare function colorScaleIsEmpty(scale: ColorScale): boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { toLocale } from '../util/numericUtils.js';
|
|
2
|
+
// A lone ramp names itself on its row: `FloatingLegend` and `legendEntries`
|
|
3
|
+
// draw a section's title only beside other sections, and a bar of numbers
|
|
4
|
+
// with nothing saying what they count is not a key.
|
|
5
|
+
function rampItem({ title, domain, stops, format = toLocale }, lone) {
|
|
6
|
+
const [min, max] = domain;
|
|
7
|
+
return {
|
|
8
|
+
label: lone ? (title ?? '') : '',
|
|
9
|
+
gradient: { stops, minLabel: format(min), maxLabel: format(max) },
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function sectionOf(scale, lone) {
|
|
13
|
+
return {
|
|
14
|
+
id: scale.id,
|
|
15
|
+
title: scale.title,
|
|
16
|
+
items: scale.kind === 'categorical'
|
|
17
|
+
? scale.entries.map(entry => ({ ...entry }))
|
|
18
|
+
: [rampItem(scale, lone)],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The one derivation of a legend from a display's color scales: a section per
|
|
23
|
+
* scale, in the order declared. On screen `FloatingLegend` renders the result
|
|
24
|
+
* and the export flattens it through `legendEntries`, so the two cannot
|
|
25
|
+
* describe different colors.
|
|
26
|
+
*
|
|
27
|
+
* A lone scale names itself: section titles draw only beside other sections,
|
|
28
|
+
* so a lone categorical scale's title becomes the box's (Manhattan's field
|
|
29
|
+
* name over its values) and a lone ramp's goes on its row.
|
|
30
|
+
*/
|
|
31
|
+
export function legendSpecOf(scales) {
|
|
32
|
+
const lone = scales.length === 1 ? scales[0] : undefined;
|
|
33
|
+
return {
|
|
34
|
+
title: lone?.kind === 'categorical' ? lone.title : undefined,
|
|
35
|
+
sections: scales.map(scale => sectionOf(scale, scales.length === 1)),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Whether a scale has anything for a key to show. */
|
|
39
|
+
export function colorScaleIsEmpty(scale) {
|
|
40
|
+
return scale.kind === 'categorical' && scale.entries.length === 0;
|
|
41
|
+
}
|
package/esm/ui/colors.d.ts
CHANGED
|
@@ -31,6 +31,17 @@ export declare const paletteColors: {
|
|
|
31
31
|
* re-palettes), while a by-position painter has no value to hash and wraps.
|
|
32
32
|
*/
|
|
33
33
|
export declare const categoricalPalette: string[];
|
|
34
|
+
/**
|
|
35
|
+
* The palette color a categorical value paints, derived from the value rather
|
|
36
|
+
* than from the order it was met: values stream in as regions load, and a
|
|
37
|
+
* discovery-order slot would let one region paint `chr2` blue and the next
|
|
38
|
+
* paint it pink. A non-negative integer takes the slot it names, anchored at
|
|
39
|
+
* 1, so `1`, `2`, `3` walk the palette in order and stay distinct; anything
|
|
40
|
+
* else hashes in, stable for the same reason at the cost of an occasional
|
|
41
|
+
* collision.
|
|
42
|
+
*/
|
|
43
|
+
export declare function categoricalValueColor(value: string): string;
|
|
44
|
+
export declare function categoricalValueColor<T>(value: string, palette: readonly T[]): T;
|
|
34
45
|
/**
|
|
35
46
|
* Deterministic non-negative 32-bit hash of a string.
|
|
36
47
|
*/
|
package/esm/ui/colors.js
CHANGED
|
@@ -112,6 +112,13 @@ export const paletteColors = {
|
|
|
112
112
|
export const categoricalPalette = [
|
|
113
113
|
...new Set([...tableau10, ...set1, ...dark2, ...set2, ...category10].filter(hex => !NEUTRALS.has(hex.toLowerCase()))),
|
|
114
114
|
];
|
|
115
|
+
export function categoricalValueColor(value, palette = categoricalPalette) {
|
|
116
|
+
const n = palette.length;
|
|
117
|
+
const num = Number(value);
|
|
118
|
+
return value !== '' && Number.isInteger(num) && num >= 0
|
|
119
|
+
? palette[(num + n - 1) % n]
|
|
120
|
+
: palette[hashString(value) % n];
|
|
121
|
+
}
|
|
115
122
|
// only category10 and set1 are imported by name; the rest are reached through
|
|
116
123
|
// paletteColors above
|
|
117
124
|
/**
|
package/esm/ui/index.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export { default as MonospaceTextField } from './MonospaceTextField.tsx';
|
|
|
37
37
|
export { default as ShareLinkField } from './ShareLinkField.tsx';
|
|
38
38
|
export { default as SingleSlider } from './SingleSlider.tsx';
|
|
39
39
|
export { default as SliderTooltip } from './SliderTooltip.tsx';
|
|
40
|
-
export {
|
|
40
|
+
export { makeSizeMenu, makeSizeSubMenu } from './makeSizeMenu.tsx';
|
|
41
41
|
export { INLINE_MENU_ROW_WIDTH, ResetToDefaultButton, } from './InlineMenuControls.tsx';
|
|
42
42
|
export { sliderScale } from './sliderScale.ts';
|
|
43
43
|
export type { SliderScale } from './sliderScale.ts';
|
|
@@ -78,15 +78,13 @@ export { default as Crosshairs } from './Crosshairs.tsx';
|
|
|
78
78
|
export { hoverBoxStyle } from './hoverBoxStyle.ts';
|
|
79
79
|
export { useMouseState, useMouseTracking } from './useMouseTracking.ts';
|
|
80
80
|
export type { MouseState, MouseTracker } from './useMouseTracking.ts';
|
|
81
|
-
export { PinAdornment } from './PinAdornment.tsx';
|
|
82
|
-
export { displayTypesWithPromotableSlots, misKindedPins, promotableSlotsWithoutPin, } from './promotablePinCoverage.ts';
|
|
83
81
|
export declare const VIEW_HEADER_HEIGHT = 28;
|
|
84
|
-
export {
|
|
85
|
-
export type { GradientLabel, GradientStop } from './SvgGradientLegend.tsx';
|
|
86
|
-
export { LEGEND_ROW_HEIGHT, LEGEND_SWATCH, default as SvgColorLegend, } from './SvgColorLegend.tsx';
|
|
82
|
+
export { LEGEND_ROW_HEIGHT, LEGEND_SVG_GUTTER_WIDTH, LEGEND_SWATCH, default as SvgColorLegend, } from './SvgColorLegend.tsx';
|
|
87
83
|
export type { ColorLegendEntry } from './SvgColorLegend.tsx';
|
|
88
84
|
export { measureLegendText } from './measureLegendText.ts';
|
|
89
85
|
export { LegendSwatchGlyph } from './LegendSwatchGlyph.tsx';
|
|
90
86
|
export { MAX_LEGEND_ITEMS, legendEntries, legendIsReadable, legendSwatches, nonEmptyLegendSections, } from './legendSpec.ts';
|
|
91
|
-
export type {
|
|
87
|
+
export type { LegendGradient, LegendItem, LegendSection, LegendSpec, LegendSwatch, } from './legendSpec.ts';
|
|
88
|
+
export { colorScaleIsEmpty, legendSpecOf } from './colorScale.ts';
|
|
89
|
+
export type { CategoricalEntry, CategoricalScale, ColorScale, RampScale, RampStop, } from './colorScale.ts';
|
|
92
90
|
export { checkboxItem, radioItems } from './toggleMenuItems.ts';
|
package/esm/ui/index.js
CHANGED
|
@@ -34,7 +34,7 @@ export { default as MonospaceTextField } from './MonospaceTextField.js';
|
|
|
34
34
|
export { default as ShareLinkField } from './ShareLinkField.js';
|
|
35
35
|
export { default as SingleSlider } from './SingleSlider.js';
|
|
36
36
|
export { default as SliderTooltip } from './SliderTooltip.js';
|
|
37
|
-
export {
|
|
37
|
+
export { makeSizeMenu, makeSizeSubMenu } from './makeSizeMenu.js';
|
|
38
38
|
export { INLINE_MENU_ROW_WIDTH, ResetToDefaultButton, } from './InlineMenuControls.js';
|
|
39
39
|
export { sliderScale } from './sliderScale.js';
|
|
40
40
|
export { default as ErrorBar } from './ErrorBar.js';
|
|
@@ -72,20 +72,10 @@ export { launchTargetsMenuItem } from './launchTargetsMenuItem.js';
|
|
|
72
72
|
export { default as Crosshairs } from './Crosshairs.js';
|
|
73
73
|
export { hoverBoxStyle } from './hoverBoxStyle.js';
|
|
74
74
|
export { useMouseState, useMouseTracking } from './useMouseTracking.js';
|
|
75
|
-
export { PinAdornment } from './PinAdornment.js';
|
|
76
|
-
// Not on the React-free `menuItems.ts` entry beside the row builders: this
|
|
77
|
-
// reads a config's slot table, so it pulls `configuration/promotableSlots.ts`
|
|
78
|
-
// in, and that entry exists precisely to stay out of every plugin's eager graph.
|
|
79
|
-
//
|
|
80
|
-
// `pinnedSlots`, the walk underneath it, stays module-private: the question a
|
|
81
|
-
// caller has is "which promotable slots does this menu miss", and answering the
|
|
82
|
-
// half of it that lists what a menu *does* offer needs the schema's slot table
|
|
83
|
-
// to mean anything.
|
|
84
|
-
export { displayTypesWithPromotableSlots, misKindedPins, promotableSlotsWithoutPin, } from './promotablePinCoverage.js';
|
|
85
75
|
export const VIEW_HEADER_HEIGHT = 28;
|
|
86
|
-
export {
|
|
87
|
-
export { LEGEND_ROW_HEIGHT, LEGEND_SWATCH, default as SvgColorLegend, } from './SvgColorLegend.js';
|
|
76
|
+
export { LEGEND_ROW_HEIGHT, LEGEND_SVG_GUTTER_WIDTH, LEGEND_SWATCH, default as SvgColorLegend, } from './SvgColorLegend.js';
|
|
88
77
|
export { measureLegendText } from './measureLegendText.js';
|
|
89
78
|
export { LegendSwatchGlyph } from './LegendSwatchGlyph.js';
|
|
90
79
|
export { MAX_LEGEND_ITEMS, legendEntries, legendIsReadable, legendSwatches, nonEmptyLegendSections, } from './legendSpec.js';
|
|
80
|
+
export { colorScaleIsEmpty, legendSpecOf } from './colorScale.js';
|
|
91
81
|
export { checkboxItem, radioItems } from './toggleMenuItems.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MenuItem } from './MenuTypes.ts';
|
|
2
|
-
import type {
|
|
2
|
+
import type { SettingRowOptions } from './toggleMenuItems.ts';
|
|
3
3
|
/**
|
|
4
4
|
* The "Show legend" checkbox, which seven displays build by hand — Hi-C, LD,
|
|
5
5
|
* alignments, LGVSynteny, multi-wiggle, multi-row features, both multi-sample
|
|
@@ -10,23 +10,12 @@ import type { CheckboxRowOptions } from './toggleMenuItems.ts';
|
|
|
10
10
|
* and would otherwise need an adapter object at the call site. Same argument
|
|
11
11
|
* order as `checkboxItem`, which is all this adds a label to.
|
|
12
12
|
*
|
|
13
|
-
* The `showLegend` **config slots** stay per display: their
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* What they now share is being *promotable* — pass `opts.pin` and the row gains
|
|
17
|
-
* the pin that toggles the legend on every open track of this display type,
|
|
18
|
-
* offering the new state as the display-type default in its snackbar. Every
|
|
19
|
-
* display whose legend is backed by a config slot passes one, and gets it from
|
|
20
|
-
* `LegendMixin`'s `showLegendDisplayTypeDefault` rather than calling
|
|
21
|
-
* `makeTogglePin` itself: the slot is the per-display half, the accessors over
|
|
22
|
-
* it are not.
|
|
23
|
-
*
|
|
24
|
-
* `pin` is optional because two callers have no slot to promote: the Manhattan
|
|
25
|
-
* plot's LD legend and `LinearBasicDisplay`'s color key are a volatile and a
|
|
26
|
-
* per-legend `dismissed` flag respectively, neither of which is config at all.
|
|
13
|
+
* The `showLegend` **config slots** stay per display: their default values
|
|
14
|
+
* legitimately differ (a Hi-C color scale is off by default, a variant genotype
|
|
15
|
+
* key on), and their descriptions describe genuinely different legends.
|
|
27
16
|
*
|
|
28
17
|
* `synteny-core`'s color-by legend has no toggle at all: it comes up with the
|
|
29
18
|
* modes that have a key and is dismissed from its own close button.
|
|
30
19
|
*/
|
|
31
20
|
/** #menuBuilder showLegendCheckboxItem | the shared "Show legend" checkbox */
|
|
32
|
-
export declare function showLegendCheckboxItem(checked: boolean, onToggle: () => void, opts?:
|
|
21
|
+
export declare function showLegendCheckboxItem(checked: boolean, onToggle: () => void, opts?: SettingRowOptions): MenuItem;
|
package/esm/ui/legendMenuItem.js
CHANGED
|
@@ -9,20 +9,9 @@ import { checkboxItem } from './toggleMenuItems.js';
|
|
|
9
9
|
* and would otherwise need an adapter object at the call site. Same argument
|
|
10
10
|
* order as `checkboxItem`, which is all this adds a label to.
|
|
11
11
|
*
|
|
12
|
-
* The `showLegend` **config slots** stay per display: their
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* What they now share is being *promotable* — pass `opts.pin` and the row gains
|
|
16
|
-
* the pin that toggles the legend on every open track of this display type,
|
|
17
|
-
* offering the new state as the display-type default in its snackbar. Every
|
|
18
|
-
* display whose legend is backed by a config slot passes one, and gets it from
|
|
19
|
-
* `LegendMixin`'s `showLegendDisplayTypeDefault` rather than calling
|
|
20
|
-
* `makeTogglePin` itself: the slot is the per-display half, the accessors over
|
|
21
|
-
* it are not.
|
|
22
|
-
*
|
|
23
|
-
* `pin` is optional because two callers have no slot to promote: the Manhattan
|
|
24
|
-
* plot's LD legend and `LinearBasicDisplay`'s color key are a volatile and a
|
|
25
|
-
* per-legend `dismissed` flag respectively, neither of which is config at all.
|
|
12
|
+
* The `showLegend` **config slots** stay per display: their default values
|
|
13
|
+
* legitimately differ (a Hi-C color scale is off by default, a variant genotype
|
|
14
|
+
* key on), and their descriptions describe genuinely different legends.
|
|
26
15
|
*
|
|
27
16
|
* `synteny-core`'s color-by legend has no toggle at all: it comes up with the
|
|
28
17
|
* modes that have a key and is dismissed from its own close button.
|