@jbrowse/wiggle-core 5.0.0-beta.4 → 5.0.0-beta.8

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.
@@ -1,4 +1,5 @@
1
1
  import type { ScoreAxisConfigModel } from './scoreAxisConfigSchemaFields.ts';
2
+ import type { ValueScale, YAxis } from '@jbrowse/display-ui';
2
3
  /**
3
4
  * The whole of what `ScoreScaleMixin` needs a composing display to be. Exported
4
5
  * because it is the mixin's contract and `ScoreScaleMixin.test.ts` pins it:
@@ -34,11 +35,29 @@ export interface ScoreScaleHost {
34
35
  * means "autoscale this end". Every consumer that computes a domain reads the
35
36
  * `*Bound` pair.
36
37
  *
38
+ * A display that declares its value scale elsewhere answers
39
+ * `declaredValueScale`, and then the scale type and the pinned bounds come
40
+ * from the declaration rather than from `scaleType`/`minScore`/`maxScore`.
41
+ * Its setters are the display's to override so the edit lands on the same
42
+ * declaration — one owner, whichever it is.
43
+ *
37
44
  * Whether a bound is *configured* is a third question, and `hasManualScoreBounds`
38
45
  * is the only getter that answers it — the resolved pair cannot, since
39
46
  * `defaultScoreDomain` is exactly the hook that turns an unset end into a number.
40
47
  */
41
48
  export declare function ScoreScaleMixin(): import("@jbrowse/mobx-state-tree").IModelType<{}, {
49
+ /**
50
+ * #getter
51
+ * Overridable hook: a value scale the display declares somewhere other
52
+ * than these slots — the mark display's `encoding.y`. Where it answers,
53
+ * it is the owner and the slots below stand in only for the ends it
54
+ * leaves open, so a scale is read from one place whichever place that
55
+ * is. Default none, which is every display whose axis IS these slots.
56
+ */
57
+ readonly declaredValueScale: {
58
+ scaleType?: string;
59
+ domain?: [number | undefined, number | undefined];
60
+ } | undefined;
42
61
  /**
43
62
  * #getter
44
63
  */
@@ -112,6 +131,22 @@ export declare function ScoreScaleMixin(): import("@jbrowse/mobx-state-tree").IM
112
131
  * there is a row that does nothing and never goes away.
113
132
  */
114
133
  readonly hasManualScoreBounds: boolean;
134
+ /**
135
+ * #getter
136
+ * Overridable hook (default none): the scales this display draws its y
137
+ * through. A display that answers it gets an axis per band of each,
138
+ * with its cross-hatches, placed by `DisplayChrome` and
139
+ * `renderDisplaySvg`, and the ticks derived below.
140
+ */
141
+ readonly valueScales: ValueScale[];
142
+ } & {
143
+ /**
144
+ * #getter
145
+ * The axes, one per declared scale whose domain resolved: where each
146
+ * tick lands in the band's own pixel space, through `computeYTicks`
147
+ * unless the scale brought its own ladder.
148
+ */
149
+ readonly axes: YAxis[];
115
150
  } & {
116
151
  /**
117
152
  * #action
@@ -1,5 +1,6 @@
1
1
  import { getConf, setConf } from '@jbrowse/core/configuration';
2
2
  import { types } from '@jbrowse/mobx-state-tree';
3
+ import { computeYTicks } from './computeYTicks.js';
3
4
  // The mixin composes onto a display that declares this, not the other way
4
5
  // round, so its own `self` isn't typed with it. Cast once, narrowed to the
5
6
  // field table beside it rather than `AnyConfigurationModel`, which is what keeps
@@ -32,6 +33,12 @@ const confNode = (self) => self;
32
33
  * means "autoscale this end". Every consumer that computes a domain reads the
33
34
  * `*Bound` pair.
34
35
  *
36
+ * A display that declares its value scale elsewhere answers
37
+ * `declaredValueScale`, and then the scale type and the pinned bounds come
38
+ * from the declaration rather than from `scaleType`/`minScore`/`maxScore`.
39
+ * Its setters are the display's to override so the edit lands on the same
40
+ * declaration — one owner, whichever it is.
41
+ *
35
42
  * Whether a bound is *configured* is a third question, and `hasManualScoreBounds`
36
43
  * is the only getter that answers it — the resolved pair cannot, since
37
44
  * `defaultScoreDomain` is exactly the hook that turns an unset end into a number.
@@ -40,11 +47,23 @@ export function ScoreScaleMixin() {
40
47
  return types
41
48
  .model('ScoreScaleMixin', {})
42
49
  .views(self => ({
50
+ /**
51
+ * #getter
52
+ * Overridable hook: a value scale the display declares somewhere other
53
+ * than these slots — the mark display's `encoding.y`. Where it answers,
54
+ * it is the owner and the slots below stand in only for the ends it
55
+ * leaves open, so a scale is read from one place whichever place that
56
+ * is. Default none, which is every display whose axis IS these slots.
57
+ */
58
+ get declaredValueScale() {
59
+ return undefined;
60
+ },
43
61
  /**
44
62
  * #getter
45
63
  */
46
64
  get scaleType() {
47
- return getConf(confNode(self), 'scaleType');
65
+ return (this.declaredValueScale?.scaleType ??
66
+ getConf(confNode(self), 'scaleType'));
48
67
  },
49
68
  /**
50
69
  * #getter
@@ -100,6 +119,10 @@ export function ScoreScaleMixin() {
100
119
  * The lower bound the config really sets, `undefined` at the sentinel.
101
120
  */
102
121
  get manualMinScore() {
122
+ const declared = this.declaredValueScale?.domain?.[0];
123
+ if (declared !== undefined) {
124
+ return declared;
125
+ }
103
126
  return this.minScore === Number.MIN_VALUE ? undefined : this.minScore;
104
127
  },
105
128
  /**
@@ -107,6 +130,10 @@ export function ScoreScaleMixin() {
107
130
  * The upper bound the config really sets, `undefined` at the sentinel.
108
131
  */
109
132
  get manualMaxScore() {
133
+ const declared = this.declaredValueScale?.domain?.[1];
134
+ if (declared !== undefined) {
135
+ return declared;
136
+ }
110
137
  return this.maxScore === Number.MAX_VALUE ? undefined : this.maxScore;
111
138
  },
112
139
  /**
@@ -135,6 +162,39 @@ export function ScoreScaleMixin() {
135
162
  get hasManualScoreBounds() {
136
163
  return (this.manualMinScore !== undefined || this.manualMaxScore !== undefined);
137
164
  },
165
+ /**
166
+ * #getter
167
+ * Overridable hook (default none): the scales this display draws its y
168
+ * through. A display that answers it gets an axis per band of each,
169
+ * with its cross-hatches, placed by `DisplayChrome` and
170
+ * `renderDisplaySvg`, and the ticks derived below.
171
+ */
172
+ get valueScales() {
173
+ return [];
174
+ },
175
+ }))
176
+ .views(self => ({
177
+ /**
178
+ * #getter
179
+ * The axes, one per declared scale whose domain resolved: where each
180
+ * tick lands in the band's own pixel space, through `computeYTicks`
181
+ * unless the scale brought its own ladder.
182
+ */
183
+ get axes() {
184
+ return self.valueScales.flatMap(scale => {
185
+ const { domain } = scale;
186
+ const ticks = scale.ticks ??
187
+ computeYTicks({
188
+ height: scale.height,
189
+ offset: scale.offset,
190
+ domain,
191
+ scaleType: scale.scaleType,
192
+ minimalTicks: scale.minimalTicks ?? false,
193
+ symlogConstant: scale.symlogConstant,
194
+ });
195
+ return domain && ticks ? [{ ...scale, domain, ticks }] : [];
196
+ });
197
+ },
138
198
  }))
139
199
  .actions(self => ({
140
200
  /**
@@ -1,4 +1,4 @@
1
- import type { YScaleTicks } from './yScaleTicks.ts';
1
+ import type { YScaleTicks } from '@jbrowse/display-ui';
2
2
  export declare function computeYTicks(opts: {
3
3
  height: number;
4
4
  domain: [number, number] | number[] | undefined;
@@ -1,6 +1,5 @@
1
- import { YSCALEBAR_LABEL_OFFSET } from './constants.js';
1
+ import { YSCALEBAR_LABEL_OFFSET, axisPlotBox } from '@jbrowse/display-ui';
2
2
  import { getScale } from './scale.js';
3
- import { axisPlotBox } from './yScaleTicks.js';
4
3
  // Builds Y-axis tick positions for a wiggle-family display: tick values come
5
4
  // from d3's scale.ticks(4) at normal heights, or fall back to the domain
6
5
  // endpoints for short tracks / when the user opts into minimal ticks.
@@ -1,2 +1 @@
1
- export declare const YSCALEBAR_LABEL_OFFSET = 5;
2
- export declare const ONSCREEN_AXIS_LEFT_PX = 50;
1
+ export { YSCALEBAR_LABEL_OFFSET } from '@jbrowse/display-ui/yAxisConstants';
package/esm/constants.js CHANGED
@@ -1,10 +1 @@
1
- // Vertical inset the single-wiggle family reserves above and below the plot so
2
- // the top/bottom y-axis labels aren't clipped. Lives in its own module because
3
- // both index.ts (which re-exports computeYTicks) and computeYTicks.ts need it —
4
- // importing it from index.ts would close a cycle.
5
- export const YSCALEBAR_LABEL_OFFSET = 5;
6
- // Left indent of the on-screen y-axis overlay (single-wiggle / manhattan) and of
7
- // multi-wiggle's per-row scalebars. The axis is right-oriented on screen, so its
8
- // labels grow into the plot from here; the indent keeps them clear of the track
9
- // label. Export uses its own anchor (the content left edge).
10
- export const ONSCREEN_AXIS_LEFT_PX = 50;
1
+ export { YSCALEBAR_LABEL_OFFSET } from '@jbrowse/display-ui/yAxisConstants';
@@ -1,7 +1,7 @@
1
1
  import type { WiggleDataResult } from './dataTypes.ts';
2
2
  import type { WiggleRenderingBackend } from './renderingBackendTypes.ts';
3
- import type { YScaleTicks } from './yScaleTicks.ts';
4
3
  import type { RegionHost } from '@jbrowse/display-kit/regionHost';
4
+ import type { YScaleTicks } from '@jbrowse/display-ui';
5
5
  import type { DisplayPhase } from '@jbrowse/render-core/displayPhase';
6
6
  import type { RenderLifecycleModel } from '@jbrowse/render-core/useRenderingBackend';
7
7
  export interface WiggleGpuDisplayModel<TRenderingBackend = WiggleRenderingBackend, TData = WiggleDataResult> extends RenderLifecycleModel<TRenderingBackend> {
package/esm/index.d.ts CHANGED
@@ -7,15 +7,12 @@ export { SCALE_TYPE_LINEAR, SCALE_TYPE_LOG, SCALE_TYPE_SYMLOG, makeScoreNormaliz
7
7
  export type { WiggleScaleType } from './normalize.ts';
8
8
  export { autoscaleDomainFromStats, computeAutoscaleDomain, computeScoreStats, domainFromStats, getEffectiveScores, } from './autoscale.ts';
9
9
  export type { Dataset, FeatureArrays, ScoreStats } from './autoscale.ts';
10
- export { ONSCREEN_AXIS_LEFT_PX, YSCALEBAR_LABEL_OFFSET } from './constants.ts';
10
+ export { AXIS_GUTTER_WIDTH_PX, AxisGutter, CrossHatchLines, CrossHatches, SCORE_CAPTION_HEIGHT, ScoreDomainCaption, ScoreRuleLines, ScoreRules, YSCALEBAR_LABEL_OFFSET, YScaleBar, YScaleBarOverlay, axisDrawn, axisPlotBox, clampStrokeInsideAxis, scoreToAxisY, } from '@jbrowse/display-ui';
11
+ export type { ValueScale, YAxis, YScaleTicks } from '@jbrowse/display-ui';
11
12
  export { computeYTicks } from './computeYTicks.ts';
12
13
  export { resolveRenderState } from './resolveRenderState.ts';
13
14
  export { visibleStatsDomain } from './visibleStatsDomain.ts';
14
15
  export type { SettledBlocksView, VisibleEntry, VisibleStatsDomainSpec, } from './visibleStatsDomain.ts';
15
- export { AXIS_GUTTER_WIDTH_PX, axisPlotBox, clampStrokeInsideAxis, leftAxisSpineX, scoreToAxisY, } from './yScaleTicks.ts';
16
- export type { YScaleTicks } from './yScaleTicks.ts';
17
- export { CrossHatchLines, default as CrossHatches } from './CrossHatches.tsx';
18
- export { ScoreRuleLines, default as ScoreRules } from './ScoreRules.tsx';
19
16
  export { parseScoreRules, scoreRuleMarks, widenRangeToRules, } from './scoreRuleMarks.ts';
20
17
  export type { ScoreRule, ScoreRuleMark } from './scoreRuleMarks.ts';
21
18
  export { makeResolutionSubMenuItem } from './ResolutionStepper.tsx';
@@ -26,9 +23,6 @@ export { DEFAULT_AUTOSCALE_OPTIONS } from './autoscale.ts';
26
23
  export { makeAutoscaleTypeSubMenu, makeCrossHatchItem, makeScaleTypeSubMenu, makeScoreSubMenu, makeSetMinMaxScoreItem, } from './scoreMenuItems.ts';
27
24
  export type { ScoreScaleModel } from './scoreMenuItems.ts';
28
25
  export { ScoreScaleMixin } from './ScoreScaleMixin.ts';
29
- export { default as YScaleBar } from './YScaleBar.tsx';
30
- export { SvgYScaleGutter, default as YScaleGutter } from './YScaleGutter.tsx';
31
- export { default as YScaleBarOverlay } from './YScaleBarOverlay.tsx';
32
26
  export { MIN_FILL_WIDTH_PX, NO_PREV_START, RENDERING_TYPE_DENSITY, RENDERING_TYPE_LINE, RENDERING_TYPE_LINE_CENTER, RENDERING_TYPE_SCATTER, RENDERING_TYPE_XYPLOT, } from './renderingBackendTypes.ts';
33
27
  export type { SourceRenderData, WiggleGPURenderState, WiggleRenderingBackend, WiggleRenderingType, } from './renderingBackendTypes.ts';
34
28
  export type { SourceInfo, WiggleDataResult, WiggleFeatureArrays, WiggleSourceData, } from './dataTypes.ts';
package/esm/index.js CHANGED
@@ -3,13 +3,13 @@ export { getNiceDomain, getNiceScale, getScale } from './scale.js';
3
3
  export { toP } from './formatNumber.js';
4
4
  export { SCALE_TYPE_LINEAR, SCALE_TYPE_LOG, SCALE_TYPE_SYMLOG, makeScoreNormalizer, resolveSymlogConstant, scaleTypeFromString, } from './normalize.js';
5
5
  export { autoscaleDomainFromStats, computeAutoscaleDomain, computeScoreStats, domainFromStats, getEffectiveScores, } from './autoscale.js';
6
- export { ONSCREEN_AXIS_LEFT_PX, YSCALEBAR_LABEL_OFFSET } from './constants.js';
6
+ // The y-axis primitives live in `@jbrowse/display-ui`, where the chrome
7
+ // places them for any display declaring `valueScales`; re-exported here so
8
+ // their published names hold.
9
+ export { AXIS_GUTTER_WIDTH_PX, AxisGutter, CrossHatchLines, CrossHatches, SCORE_CAPTION_HEIGHT, ScoreDomainCaption, ScoreRuleLines, ScoreRules, YSCALEBAR_LABEL_OFFSET, YScaleBar, YScaleBarOverlay, axisDrawn, axisPlotBox, clampStrokeInsideAxis, scoreToAxisY, } from '@jbrowse/display-ui';
7
10
  export { computeYTicks } from './computeYTicks.js';
8
11
  export { resolveRenderState } from './resolveRenderState.js';
9
12
  export { visibleStatsDomain } from './visibleStatsDomain.js';
10
- export { AXIS_GUTTER_WIDTH_PX, axisPlotBox, clampStrokeInsideAxis, leftAxisSpineX, scoreToAxisY, } from './yScaleTicks.js';
11
- export { CrossHatchLines, default as CrossHatches } from './CrossHatches.js';
12
- export { ScoreRuleLines, default as ScoreRules } from './ScoreRules.js';
13
13
  export { parseScoreRules, scoreRuleMarks, widenRangeToRules, } from './scoreRuleMarks.js';
14
14
  export { makeResolutionSubMenuItem } from './ResolutionStepper.js';
15
15
  export { makeScatterPointSizeMenuItem } from './pointSizeMenu.js';
@@ -18,9 +18,6 @@ export { default as SetMinMaxDialog } from './SetMinMaxDialog.js';
18
18
  export { DEFAULT_AUTOSCALE_OPTIONS } from './autoscale.js';
19
19
  export { makeAutoscaleTypeSubMenu, makeCrossHatchItem, makeScaleTypeSubMenu, makeScoreSubMenu, makeSetMinMaxScoreItem, } from './scoreMenuItems.js';
20
20
  export { ScoreScaleMixin } from './ScoreScaleMixin.js';
21
- export { default as YScaleBar } from './YScaleBar.js';
22
- export { SvgYScaleGutter, default as YScaleGutter } from './YScaleGutter.js';
23
- export { default as YScaleBarOverlay } from './YScaleBarOverlay.js';
24
21
  export { MIN_FILL_WIDTH_PX, NO_PREV_START, RENDERING_TYPE_DENSITY, RENDERING_TYPE_LINE, RENDERING_TYPE_LINE_CENTER, RENDERING_TYPE_SCATTER, RENDERING_TYPE_XYPLOT, } from './renderingBackendTypes.js';
25
22
  export {} from './displayModel.js';
26
23
  export { collectWiggleTransferables } from './transferables.js';
@@ -1,13 +1,14 @@
1
- import type { ConfigModelForFields, ResolvableDisplay } from '@jbrowse/core/configuration';
1
+ import type { ConfigModelForFields } from '@jbrowse/core/configuration';
2
2
  import type { MenuItem } from '@jbrowse/core/ui';
3
3
  export declare function makeScatterPointSizeMenuItem(self: {
4
4
  scatterPointSize: number;
5
5
  setScatterPointSize: (n?: number) => void;
6
- } & ResolvableDisplay<ConfigModelForFields<{
7
- scatterPointSize: {
8
- type: 'maybeNumber';
9
- promotedBase: number;
10
- };
11
- }>>, opts: {
6
+ configuration: ConfigModelForFields<{
7
+ scatterPointSize: {
8
+ type: 'number';
9
+ defaultValue: number;
10
+ };
11
+ }>;
12
+ }, opts: {
12
13
  label: string;
13
14
  }): MenuItem;
@@ -1,16 +1,15 @@
1
- import { makePromotableSizeMenu } from '@jbrowse/core/ui';
1
+ import { getSlotDefinition } from '@jbrowse/core/configuration';
2
+ import { makeSizeMenu } from '@jbrowse/core/ui';
2
3
  // Wires a display's shared `scatterPointSize`/`setScatterPointSize` (from
3
- // WiggleScoreConfigMixin) to makePromotableSizeMenu. Used by both the wiggle
4
- // scatter and GWAS Manhattan track menus so the slider/reset/pin behavior can't
5
- // drift. The `scatterPointSize` slot is promotable, so the row carries the
6
- // "default for all tracks of this type" pin.
4
+ // WiggleScoreConfigMixin) to makeSizeMenu. Used by both the wiggle scatter and
5
+ // GWAS Manhattan track menus so the slider/reset behavior can't drift.
7
6
  export function makeScatterPointSizeMenuItem(self, opts) {
8
- return makePromotableSizeMenu({
7
+ return makeSizeMenu({
9
8
  label: opts.label,
10
9
  title: 'Point size',
11
- display: self,
12
- slot: 'scatterPointSize',
13
10
  getValue: () => self.scatterPointSize,
11
+ isDefault: self.scatterPointSize ===
12
+ getSlotDefinition(self.configuration, 'scatterPointSize').defaultValue,
14
13
  onChange: n => {
15
14
  self.setScatterPointSize(n);
16
15
  },
@@ -1,3 +1,4 @@
1
+ import type { ScoreRuleMark } from '@jbrowse/display-ui';
1
2
  /**
2
3
  * #api
3
4
  * One horizontal rule across a score plot, at a score the user chose.
@@ -14,9 +15,7 @@ export interface ScoreRule {
14
15
  label?: string;
15
16
  color?: string;
16
17
  }
17
- export interface ScoreRuleMark extends ScoreRule {
18
- y: number;
19
- }
18
+ export type { ScoreRuleMark } from '@jbrowse/display-ui';
20
19
  /**
21
20
  * #api
22
21
  * Screen y for each rule that falls inside the plotted domain, dropping the
@@ -1,4 +1,4 @@
1
- import { clampStrokeInsideAxis, scoreToAxisY } from './yScaleTicks.js';
1
+ import { clampStrokeInsideAxis, scoreToAxisY } from '@jbrowse/display-ui';
2
2
  /**
3
3
  * #api
4
4
  * Screen y for each rule that falls inside the plotted domain, dropping the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/wiggle-core",
3
- "version": "5.0.0-beta.4",
3
+ "version": "5.0.0-beta.8",
4
4
  "type": "module",
5
5
  "description": "Shared scale and autoscale utilities for wiggle and coverage displays",
6
6
  "keywords": [
@@ -32,9 +32,10 @@
32
32
  "@mui/icons-material": "^9.4.0",
33
33
  "@mui/material": "^9.4.0",
34
34
  "mobx-react": "^10.0.2",
35
- "@jbrowse/display-kit": "^5.0.0-beta.4",
36
- "@jbrowse/core": "^5.0.0-beta.4",
37
- "@jbrowse/render-core": "^5.0.0-beta.4"
35
+ "@jbrowse/display-kit": "^5.0.0-beta.8",
36
+ "@jbrowse/core": "^5.0.0-beta.8",
37
+ "@jbrowse/display-ui": "^5.0.0-beta.8",
38
+ "@jbrowse/render-core": "^5.0.0-beta.8"
38
39
  },
39
40
  "peerDependencies": {
40
41
  "react": ">=19.0.0"
@@ -1,11 +0,0 @@
1
- import type { YScaleTicks } from './yScaleTicks.ts';
2
- export declare function CrossHatchLines({ ticks, width, offsetY, }: {
3
- ticks: YScaleTicks;
4
- width: number;
5
- offsetY?: number;
6
- }): import("react").JSX.Element;
7
- export default function CrossHatches({ ticks, width, height, }: {
8
- ticks: YScaleTicks;
9
- width: number;
10
- height: number;
11
- }): import("react").JSX.Element;
@@ -1,32 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
- import { clampStrokeInsideAxis } from './yScaleTicks.js';
3
- // Bare <line> set for the Y-scale tick guide lines, no wrapping <svg>. Shared by
4
- // the absolutely-positioned on-screen CrossHatches overlay and the flat SVG
5
- // export (single- and multi-wiggle), so the three can't drift. `offsetY` shifts
6
- // every line down for per-row rendering in multi-wiggle.
7
- export function CrossHatchLines({ ticks, width, offsetY = 0, }) {
8
- return (_jsx(_Fragment, { children: ticks.items.map(({ value, y }) => {
9
- // Same clamp the axis ticks take, so a guide line and the tick it
10
- // belongs to can't disagree at the bottom edge — where, in multi-wiggle,
11
- // "one pixel lower" is the next sample's row. Only that edge moves; the
12
- // guide lines don't crispen to a half pixel the way the axis does.
13
- const lineY = offsetY + clampStrokeInsideAxis(y, ticks.yBottom);
14
- return (_jsx("line", { x1: 0, x2: width, y1: lineY, y2: lineY, stroke: "rgb(200,200,200)",
15
- // a separate stroke-opacity attribute (not baked into an rgba()
16
- // string) survives the SVG export, whose renderToStaticMarkup strips
17
- // rgba() alpha
18
- strokeOpacity: 0.8, strokeWidth: 1 }, `${value}-${y}`));
19
- }) }));
20
- }
21
- // Horizontal guide lines at each Y-scale tick. Pointer-events disabled so
22
- // the underlying canvas still receives mouse events.
23
- export default function CrossHatches({ ticks, width, height, }) {
24
- return (_jsx("svg", { style: {
25
- position: 'absolute',
26
- top: 0,
27
- left: 0,
28
- pointerEvents: 'none',
29
- height,
30
- width,
31
- }, children: _jsx(CrossHatchLines, { ticks: ticks, width: width }) }));
32
- }
@@ -1,11 +0,0 @@
1
- import type { ScoreRuleMark } from './scoreRuleMarks.ts';
2
- export declare function ScoreRuleLines({ marks, width, offsetY, }: {
3
- marks: ScoreRuleMark[];
4
- width: number;
5
- offsetY?: number;
6
- }): import("react").JSX.Element;
7
- export default function ScoreRules({ marks, width, height, }: {
8
- marks: ScoreRuleMark[];
9
- width: number;
10
- height: number;
11
- }): import("react").JSX.Element;
package/esm/ScoreRules.js DELETED
@@ -1,37 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { usePalette } from '@jbrowse/core/ui/PaletteContext';
3
- const DEFAULT_RULE_COLOR = 'rgb(120,120,120)';
4
- // Bare marks, no wrapping <svg>, so the on-screen overlay and the SVG export
5
- // draw the same elements from the same y. Same split as CrossHatchLines.
6
- export function ScoreRuleLines({ marks, width, offsetY = 0, }) {
7
- // A caption sits over the plot, not beside it, so it needs the halo the tick
8
- // labels next to it already draw (YScaleBar) — without one a rule crossing
9
- // filled bars captions itself in grey on the fill and cannot be read. From
10
- // the palette rather than a hardcoded white, which inverts into a glare in
11
- // dark mode.
12
- const haloColor = usePalette().background.default;
13
- return (_jsx(_Fragment, { children: marks.map(({ value, label, color, y }) => {
14
- const lineY = offsetY + y;
15
- const stroke = color ?? DEFAULT_RULE_COLOR;
16
- return (_jsxs("g", { children: [_jsx("line", { x1: 0, x2: width, y1: lineY, y2: lineY, stroke: stroke,
17
- // separate attribute rather than an rgba() string: the SVG
18
- // export's renderToStaticMarkup strips rgba() alpha
19
- strokeOpacity: 0.9, strokeWidth: 1, strokeDasharray: "4 3" }), label ? (_jsx("text", { x: width - 4, y: lineY - 2, textAnchor: "end", fontSize: 10,
20
- // full alpha, unlike the line: 0.9 is there to let the rule
21
- // recede into the plot, and a caption that has to be read wants
22
- // the opposite
23
- fill: stroke, stroke: haloColor, strokeWidth: 2.5, paintOrder: "stroke", children: label })) : null] }, `${value}-${y}`));
24
- }) }));
25
- }
26
- // Pointer-events disabled so the canvas underneath still gets mouse events,
27
- // matching CrossHatches.
28
- export default function ScoreRules({ marks, width, height, }) {
29
- return (_jsx("svg", { style: {
30
- position: 'absolute',
31
- top: 0,
32
- left: 0,
33
- pointerEvents: 'none',
34
- height,
35
- width,
36
- }, children: _jsx(ScoreRuleLines, { marks: marks, width: width }) }));
37
- }
@@ -1,6 +0,0 @@
1
- import type { YScaleTicks } from './yScaleTicks.ts';
2
- export default function YScaleBar({ ticks, orientation, insetLabels, }: {
3
- ticks: YScaleTicks | undefined;
4
- orientation?: 'left' | 'right';
5
- insetLabels?: boolean;
6
- }): import("react").JSX.Element | null;
package/esm/YScaleBar.js DELETED
@@ -1,46 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { usePalette } from '@jbrowse/core/ui/PaletteContext';
3
- import { clampStrokeInsideAxis } from './yScaleTicks.js';
4
- // Not an `observer`, and deliberately: there is nothing here to observe. Every
5
- // caller already reads `model.ticks` inside its own observer and hands the
6
- // resolved plain object down, and `usePalette` is a React context, not MobX.
7
- // Wrapping it anyway allocated a Reaction per instance — multi-wiggle renders
8
- // one per sample row, on screen as well as in export — and, per CLAUDE.md, put
9
- // the component out of `babel-plugin-react-compiler`'s reach, so it got neither
10
- // the MobX tracking it doesn't need nor the memoization it does. Its sibling
11
- // `CrossHatchLines`, consuming the same ticks, is already plain.
12
- // Half the vertical space a 10px label needs, halo included: digits reach about
13
- // 3.6px above the centered baseline and the 2.5px-wide background stroke another
14
- // 1.25px past that.
15
- const LABEL_HALF_HEIGHT_PX = 5;
16
- export default function YScaleBar({ ticks, orientation, insetLabels, }) {
17
- const palette = usePalette();
18
- if (!ticks) {
19
- return null;
20
- }
21
- const { items, yTop, yBottom } = ticks;
22
- const bg = palette.background.default;
23
- const fg = palette.text.primary;
24
- const isLeft = orientation !== 'right';
25
- const k = isLeft ? -1 : 1;
26
- const tickLength = 6;
27
- // Crispen to `y + 0.5` so each 1px stroke fills one pixel instead of
28
- // straddling two, then clamp, which only bites at the bottom edge: there the
29
- // stroke goes on the last pixel inside the box rather than the first one
30
- // below it.
31
- const strokeY = (y) => clampStrokeInsideAxis(y + 0.5, yBottom);
32
- // The tick keeps the group transform; only the text moves off it, and only
33
- // under `insetLabels` within half a label of an end. A tick is where the data
34
- // is, so it never moves — the label is then drawn against the tick rather than
35
- // across it, which is what any axis does at the edge of its own frame.
36
- // `undefined` rather than 0 for the labels that don't move, so the markup this
37
- // emits is unchanged wherever the inset doesn't apply.
38
- const labelDy = (sy) => {
39
- if (!insetLabels) {
40
- return undefined;
41
- }
42
- const inset = Math.min(Math.max(sy, yTop + LABEL_HALF_HEIGHT_PX), yBottom - LABEL_HALF_HEIGHT_PX);
43
- return inset === sy ? undefined : inset - sy;
44
- };
45
- return (_jsxs("g", { fontSize: 10, textAnchor: isLeft ? 'end' : 'start', stroke: fg, strokeWidth: 1, children: [_jsx("path", { fill: "none", d: `M${k * tickLength} ${strokeY(yTop)}H0.5V${strokeY(yBottom)}H${k * tickLength}` }), items.map(({ value, y, label }) => (_jsxs("g", { transform: `translate(0,${strokeY(y)})`, children: [_jsx("line", { x2: k * tickLength }), _jsx("text", { stroke: bg, strokeWidth: 2.5, paintOrder: "stroke", fill: fg, y: labelDy(strokeY(y)), dy: "0.32em", x: k * 9, children: label ?? value })] }, `${value}-${y}`)))] }));
46
- }
@@ -1,6 +0,0 @@
1
- import type { YScaleTicks } from './yScaleTicks.ts';
2
- export default function YScaleBarOverlay({ ticks, height, width, }: {
3
- ticks: YScaleTicks;
4
- height: number;
5
- width: number;
6
- }): import("react").JSX.Element;
@@ -1,24 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import YScaleBar from './YScaleBar.js';
3
- import { ONSCREEN_AXIS_LEFT_PX } from './constants.js';
4
- // Y-axis tick labels positioned absolutely, indented from the track's left edge
5
- // so a right-oriented axis reads inside the plot. The SVG export instead anchors
6
- // a left-oriented axis at the content edge, putting the labels in the export
7
- // margin — see WiggleFamilySvgFrame.
8
- //
9
- // `width` is the track's, not a fixed strip: an <svg> clips to its own box, and
10
- // the hardcoded 70px this used to carry silently truncated any label past ~11
11
- // characters. Not hypothetical on a log axis — a domain under 1 nices to
12
- // negative powers of two, so `0.0009765625` is an ordinary tick on a
13
- // mappability track and the labels grow exactly where the numbers get small.
14
- // The track's own right edge is the real boundary.
15
- export default function YScaleBarOverlay({ ticks, height, width, }) {
16
- return (_jsx("svg", { style: {
17
- position: 'absolute',
18
- top: 0,
19
- left: ONSCREEN_AXIS_LEFT_PX,
20
- pointerEvents: 'none',
21
- height,
22
- width: Math.max(0, width - ONSCREEN_AXIS_LEFT_PX),
23
- }, children: _jsx(YScaleBar, { ticks: ticks, orientation: "right" }) }));
24
- }
@@ -1,27 +0,0 @@
1
- import type { YScaleTicks } from './yScaleTicks.ts';
2
- /**
3
- * A left-oriented Y-axis in the shared gutter, at a band's top-left corner on
4
- * screen. Every stacked band that carries one — the alignments coverage band,
5
- * MAF's coverage and conservation bands — draws it through this, and its export
6
- * emits `SvgYScaleGutter` with the same spine placement, so the on-screen and
7
- * exported axes cannot drift apart.
8
- *
9
- * `left` is the gutter's own left edge; the spine is placed inside the gutter
10
- * from there, so a caller positioning the box does not also offset the spine.
11
- */
12
- export default function YScaleGutter({ top, left, height, ticks, }: {
13
- top: number;
14
- left?: number;
15
- height: number;
16
- ticks: YScaleTicks;
17
- }): import("react").JSX.Element;
18
- /**
19
- * The gutter's `<g>` on its own, for a caller that already owns an `<svg>` —
20
- * the SVG export, whose bands are positioned by the enclosing document. `left`
21
- * is the gutter's left edge in that document.
22
- */
23
- export declare function SvgYScaleGutter({ left, y, ticks, }: {
24
- left?: number;
25
- y: number;
26
- ticks: YScaleTicks;
27
- }): import("react").JSX.Element;
@@ -1,31 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import YScaleBar from './YScaleBar.js';
3
- import { AXIS_GUTTER_WIDTH_PX, leftAxisSpineX } from './yScaleTicks.js';
4
- /**
5
- * A left-oriented Y-axis in the shared gutter, at a band's top-left corner on
6
- * screen. Every stacked band that carries one — the alignments coverage band,
7
- * MAF's coverage and conservation bands — draws it through this, and its export
8
- * emits `SvgYScaleGutter` with the same spine placement, so the on-screen and
9
- * exported axes cannot drift apart.
10
- *
11
- * `left` is the gutter's own left edge; the spine is placed inside the gutter
12
- * from there, so a caller positioning the box does not also offset the spine.
13
- */
14
- export default function YScaleGutter({ top, left = 0, height, ticks, }) {
15
- return (_jsx("svg", { style: {
16
- position: 'absolute',
17
- top,
18
- left,
19
- pointerEvents: 'none',
20
- height,
21
- width: AXIS_GUTTER_WIDTH_PX,
22
- }, children: _jsx(SvgYScaleGutter, { y: 0, ticks: ticks }) }));
23
- }
24
- /**
25
- * The gutter's `<g>` on its own, for a caller that already owns an `<svg>` —
26
- * the SVG export, whose bands are positioned by the enclosing document. `left`
27
- * is the gutter's left edge in that document.
28
- */
29
- export function SvgYScaleGutter({ left = 0, y, ticks, }) {
30
- return (_jsx("g", { transform: `translate(${leftAxisSpineX(left)}, ${y})`, children: _jsx(YScaleBar, { ticks: ticks, orientation: "left" }) }));
31
- }
@@ -1,59 +0,0 @@
1
- export interface YScaleTicks {
2
- items: {
3
- value: number;
4
- y: number;
5
- label?: string;
6
- }[];
7
- yTop: number;
8
- yBottom: number;
9
- }
10
- /**
11
- * The plot box a wiggle-family display insets inside its own height, for both
12
- * sides of the contract above: the renderer positions its canvas at `yTop` and
13
- * draws `plotHeight` tall, and the axis puts domain-max at `yTop` and
14
- * domain-min at `yBottom`.
15
- *
16
- * One function because the two have to agree exactly, and `height - 2 * offset`
17
- * spelled out at each end is how they stopped: `computeYTicks` derived its own
18
- * bottom as `height - offset - 1`, and every tick above it inherited a fraction
19
- * of that pixel.
20
- */
21
- export declare function axisPlotBox(height: number, offset?: number): {
22
- yTop: number;
23
- yBottom: number;
24
- plotHeight: number;
25
- };
26
- /**
27
- * Screen y for an already-normalized ([0,1], 0 at domain-min) score. The
28
- * composition of {@link axisPlotBox} with a `makeScoreNormalizer` result, which
29
- * is what a tick at that score must land on.
30
- */
31
- export declare function scoreToAxisY(normalized: number, box: ReturnType<typeof axisPlotBox>): number;
32
- /**
33
- * Width of the gutter a left-oriented {@link YScaleBar} is drawn in, and the x
34
- * inside that gutter its spine goes at.
35
- *
36
- * `YScaleBar` grows its ticks and numbers *away* from the spine, so a
37
- * left-oriented axis has to inset its spine by the label width or the labels
38
- * land at negative x — which is what once put an exported coverage axis off the
39
- * image entirely. Both numbers live here, beside the component whose own
40
- * geometry (`x = ±9`, `fontSize 10`, `textAnchor: end`) is what makes them the
41
- * right ones: the alignments coverage axis and the MAF band axes had each
42
- * arrived at this same 50/45 pair independently, so a change to the labels moved
43
- * neither.
44
- *
45
- * An axis drawn over its own plot rather than in reserved space may narrow the
46
- * gutter — every px of it is a px of data hidden. Such an axis still places its
47
- * spine at `width - 1`, the same rule stated against its own width.
48
- */
49
- export declare const AXIS_GUTTER_WIDTH_PX = 50;
50
- export declare function leftAxisSpineX(gutterLeft?: number): number;
51
- /**
52
- * Keep a 1px stroke's center inside the axis box. At `yBottom` — the box's own
53
- * bottom edge — the stroke has to cover the pixel *above* the boundary, since
54
- * the one below belongs to whatever is drawn next; in multi-wiggle, where rows
55
- * stack edge to edge, that is literally the next sample's first row of pixels.
56
- * Callers pass whatever stroke center their own convention gives (the axis
57
- * crispens to `y + 0.5`, the guide lines don't); only the bottom edge moves.
58
- */
59
- export declare function clampStrokeInsideAxis(strokeY: number, yBottom: number): number;
@@ -1,59 +0,0 @@
1
- import { YSCALEBAR_LABEL_OFFSET } from './constants.js';
2
- /**
3
- * The plot box a wiggle-family display insets inside its own height, for both
4
- * sides of the contract above: the renderer positions its canvas at `yTop` and
5
- * draws `plotHeight` tall, and the axis puts domain-max at `yTop` and
6
- * domain-min at `yBottom`.
7
- *
8
- * One function because the two have to agree exactly, and `height - 2 * offset`
9
- * spelled out at each end is how they stopped: `computeYTicks` derived its own
10
- * bottom as `height - offset - 1`, and every tick above it inherited a fraction
11
- * of that pixel.
12
- */
13
- export function axisPlotBox(height, offset = YSCALEBAR_LABEL_OFFSET) {
14
- return {
15
- yTop: offset,
16
- yBottom: height - offset,
17
- plotHeight: height - 2 * offset,
18
- };
19
- }
20
- /**
21
- * Screen y for an already-normalized ([0,1], 0 at domain-min) score. The
22
- * composition of {@link axisPlotBox} with a `makeScoreNormalizer` result, which
23
- * is what a tick at that score must land on.
24
- */
25
- export function scoreToAxisY(normalized, box) {
26
- return box.yTop + (1 - normalized) * box.plotHeight;
27
- }
28
- /**
29
- * Width of the gutter a left-oriented {@link YScaleBar} is drawn in, and the x
30
- * inside that gutter its spine goes at.
31
- *
32
- * `YScaleBar` grows its ticks and numbers *away* from the spine, so a
33
- * left-oriented axis has to inset its spine by the label width or the labels
34
- * land at negative x — which is what once put an exported coverage axis off the
35
- * image entirely. Both numbers live here, beside the component whose own
36
- * geometry (`x = ±9`, `fontSize 10`, `textAnchor: end`) is what makes them the
37
- * right ones: the alignments coverage axis and the MAF band axes had each
38
- * arrived at this same 50/45 pair independently, so a change to the labels moved
39
- * neither.
40
- *
41
- * An axis drawn over its own plot rather than in reserved space may narrow the
42
- * gutter — every px of it is a px of data hidden. Such an axis still places its
43
- * spine at `width - 1`, the same rule stated against its own width.
44
- */
45
- export const AXIS_GUTTER_WIDTH_PX = 50;
46
- export function leftAxisSpineX(gutterLeft = 0) {
47
- return gutterLeft + AXIS_GUTTER_WIDTH_PX - YSCALEBAR_LABEL_OFFSET;
48
- }
49
- /**
50
- * Keep a 1px stroke's center inside the axis box. At `yBottom` — the box's own
51
- * bottom edge — the stroke has to cover the pixel *above* the boundary, since
52
- * the one below belongs to whatever is drawn next; in multi-wiggle, where rows
53
- * stack edge to edge, that is literally the next sample's first row of pixels.
54
- * Callers pass whatever stroke center their own convention gives (the axis
55
- * crispens to `y + 0.5`, the guide lines don't); only the bottom edge moves.
56
- */
57
- export function clampStrokeInsideAxis(strokeY, yBottom) {
58
- return Math.min(strokeY, yBottom - 0.5);
59
- }