@jbrowse/bandage-core 4.3.0 → 4.4.1

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.
@@ -5075,7 +5075,16 @@ function placeRowGenes(rows, byRow) {
5075
5075
  }
5076
5076
  var GENE_FONT_PX = 9;
5077
5077
  var GENE_CHAR_PX = 5.2;
5078
- function rowGeneBoxes(genes, X, y) {
5078
+ var MIN_LETTERED_BAR_PX = 8;
5079
+ var genePad = (rowPx, barPx) => Math.min(2, (rowPx - barPx) / 2 - 1);
5080
+ function boxesGenes(rowPx, barPx) {
5081
+ return genePad(rowPx, barPx) >= 1;
5082
+ }
5083
+ function rowGeneBoxes(genes, X, y, { rowPx = ROW_HEIGHT_PX, barPx = BAR_PX } = {}) {
5084
+ if (!boxesGenes(rowPx, barPx)) {
5085
+ return [];
5086
+ }
5087
+ const pad = genePad(rowPx, barPx);
5079
5088
  const boxes = genes.map((g) => {
5080
5089
  const x = X(g.start);
5081
5090
  return {
@@ -5086,7 +5095,7 @@ function rowGeneBoxes(genes, X, y) {
5086
5095
  });
5087
5096
  const taken = [];
5088
5097
  const named = new Set(
5089
- [...boxes].sort((a, b) => a.w - b.w).filter(({ g, x, w }) => {
5098
+ (barPx >= MIN_LETTERED_BAR_PX ? [...boxes] : []).sort((a, b) => a.w - b.w).filter(({ g, x, w }) => {
5090
5099
  const half = g.name.length * GENE_CHAR_PX / 2;
5091
5100
  const at = [x + w / 2 - half, x + w / 2 + half];
5092
5101
  if (2 * half > w + 6 || taken.some(([a, b]) => at[0] < b + 2 && at[1] > a - 2)) {
@@ -5099,9 +5108,9 @@ function rowGeneBoxes(genes, X, y) {
5099
5108
  return boxes.map((box) => ({
5100
5109
  name: box.g.name,
5101
5110
  x: box.x,
5102
- y: y - BAR_PX / 2 - 2,
5111
+ y: y - barPx / 2 - pad,
5103
5112
  w: box.w,
5104
- h: BAR_PX + 4,
5113
+ h: barPx + 2 * pad,
5105
5114
  exons: box.g.exons.map((e) => ({
5106
5115
  x: X(e.start),
5107
5116
  w: Math.max(1, X(e.end) - X(e.start))
@@ -5133,7 +5142,13 @@ function walkRowsKey(bars, o = {}) {
5133
5142
  label: "walked outside the cut"
5134
5143
  });
5135
5144
  }
5136
- if (o.genes) {
5145
+ if (o.genes?.crowded) {
5146
+ entries.push({
5147
+ swatch: { kind: "gene" },
5148
+ label: "genes left out: too many rows to box them in",
5149
+ note: true
5150
+ });
5151
+ } else if (o.genes) {
5137
5152
  entries.push({
5138
5153
  swatch: { kind: "gene" },
5139
5154
  label: "genes, each row's own annotation"
@@ -5260,7 +5275,7 @@ function coalesceRuns(runs, pxPerBp) {
5260
5275
  flush();
5261
5276
  return out;
5262
5277
  }
5263
- function geneTree(box, y) {
5278
+ function geneTree(box, y, barPx) {
5264
5279
  return el(
5265
5280
  "g",
5266
5281
  { class: "row-gene", "data-testid": "graph-walk-gene" },
@@ -5268,9 +5283,9 @@ function geneTree(box, y) {
5268
5283
  ...box.exons.map(
5269
5284
  (e) => el("rect", {
5270
5285
  x: e.x,
5271
- y: y - BAR_PX / 2,
5286
+ y: y - barPx / 2,
5272
5287
  width: e.w,
5273
- height: BAR_PX,
5288
+ height: barPx,
5274
5289
  fill: GENE_INK,
5275
5290
  opacity: 0.35
5276
5291
  })
@@ -5366,9 +5381,10 @@ function walkRowsTree(bars, frame, o = {}) {
5366
5381
  },
5367
5382
  ...runs,
5368
5383
  ...ticks,
5369
- ...rowGeneBoxes(o.rowGenes?.get(row.name) ?? [], along, y).map(
5370
- (box) => geneTree(box, y)
5371
- ),
5384
+ ...rowGeneBoxes(o.rowGenes?.get(row.name) ?? [], along, y, {
5385
+ rowPx,
5386
+ barPx
5387
+ }).map((box) => geneTree(box, y, barPx)),
5372
5388
  call && el("rect", {
5373
5389
  "data-testid": "graph-walk-call",
5374
5390
  x: along(call.bp) - 1,
@@ -5397,6 +5413,183 @@ function walkRowsTree(bars, frame, o = {}) {
5397
5413
  return el("g", {}, ...[reference, ...rows].map(rowTree));
5398
5414
  }
5399
5415
 
5416
+ // src/layout/walkStrip.ts
5417
+ var PAD_PX = 6;
5418
+ var WALK_STRIP_CEILING_PX = 260;
5419
+ var LABEL_FONT_PX = 11;
5420
+ var MIN_ROW_PX = 3;
5421
+ var LABELLED_ROW_PX = 12;
5422
+ var READOUT_ROOM2 = 1.3;
5423
+ var MARK_INK = "#111";
5424
+ var TICK_PX = 3;
5425
+ function stripMarks(graph, rows, node) {
5426
+ const visits = graph.pathVisits?.get(node.name) ?? [];
5427
+ const marks = [];
5428
+ for (const row of rows) {
5429
+ const axis = row.axis;
5430
+ if (!axis) {
5431
+ continue;
5432
+ }
5433
+ const path = pathOrigin(row.name).name;
5434
+ for (const v of visits) {
5435
+ if (v.path !== path) {
5436
+ continue;
5437
+ }
5438
+ const { start, end } = alongRow(axis, v.start, v.start + node.length);
5439
+ if (end > 0 && start < row.bp) {
5440
+ marks.push({ row: row.name, start, bp: end - start });
5441
+ }
5442
+ }
5443
+ }
5444
+ return marks;
5445
+ }
5446
+ var spanCache = /* @__PURE__ */ new WeakMap();
5447
+ function spansOf(graph, name) {
5448
+ let byName = spanCache.get(graph);
5449
+ if (!byName) {
5450
+ byName = /* @__PURE__ */ new Map();
5451
+ spanCache.set(graph, byName);
5452
+ }
5453
+ if (byName.has(name)) {
5454
+ return byName.get(name);
5455
+ }
5456
+ const lengthOf = new Map(graph.nodes.map((n2) => [n2.id, n2.length]));
5457
+ const pieces = (graph.paths ?? []).filter((p) => p.name === name);
5458
+ let spans;
5459
+ if (pieces.every((p) => p.start !== void 0)) {
5460
+ spans = { ids: [], starts: [], ends: [] };
5461
+ for (const piece of [...pieces].sort((a, b) => a.start - b.start)) {
5462
+ let pos = piece.start;
5463
+ for (const id of piece.nodeIds) {
5464
+ const len = lengthOf.get(id) ?? 0;
5465
+ spans.ids.push(id);
5466
+ spans.starts.push(pos);
5467
+ spans.ends.push(pos + len);
5468
+ pos += len;
5469
+ }
5470
+ }
5471
+ }
5472
+ byName.set(name, spans);
5473
+ return spans;
5474
+ }
5475
+ function segmentAt(graph, row, offset, pxPerBp) {
5476
+ const axis = row.axis;
5477
+ const spans = axis && spansOf(graph, row.name);
5478
+ if (!axis || !spans || offset < 0 || offset > row.bp) {
5479
+ return void 0;
5480
+ }
5481
+ const at = axis.reversed ? axis.start - offset : axis.start + offset;
5482
+ const tol = 0.5 / pxPerBp;
5483
+ const bar = axis.reversed ? { start: axis.start - row.bp, end: axis.start } : { start: axis.start, end: axis.start + row.bp };
5484
+ const from = Math.max(at - tol, bar.start);
5485
+ const to = Math.min(at + tol, bar.end);
5486
+ let lo = 0;
5487
+ let hi = spans.starts.length;
5488
+ while (lo < hi) {
5489
+ const mid = lo + hi >> 1;
5490
+ if (spans.ends[mid] <= from) {
5491
+ lo = mid + 1;
5492
+ } else {
5493
+ hi = mid;
5494
+ }
5495
+ }
5496
+ let best;
5497
+ let bestLen = -1;
5498
+ let bestHolds = false;
5499
+ for (let i = lo; i < spans.starts.length && spans.starts[i] < to; i++) {
5500
+ const len = spans.ends[i] - spans.starts[i];
5501
+ const holds = spans.starts[i] <= at && at < spans.ends[i];
5502
+ if (len > bestLen || len === bestLen && holds && !bestHolds) {
5503
+ bestLen = len;
5504
+ best = spans.ids[i];
5505
+ bestHolds = holds;
5506
+ }
5507
+ }
5508
+ return best;
5509
+ }
5510
+ function walkStripFrame(bars, o) {
5511
+ const maxHeight = o.maxHeight ?? WALK_STRIP_CEILING_PX;
5512
+ const labelPx = o.labelPx ?? Math.max(...[bars.reference, ...bars.rows].map((r) => r.label.length)) * LABEL_CHAR_PX + 16;
5513
+ const count = bars.rows.length + 1;
5514
+ const rowPx = Math.max(
5515
+ MIN_ROW_PX,
5516
+ Math.min(ROW_HEIGHT_PX, Math.floor((maxHeight - 2 * PAD_PX) / count))
5517
+ );
5518
+ const labelled = rowPx >= LABELLED_ROW_PX;
5519
+ const readouts = rowPx >= 14;
5520
+ const barPx = Math.max(2, Math.round(rowPx * 0.6));
5521
+ const longest = Math.max(bars.reference.bp, ...bars.rows.map((r) => r.bp), 1);
5522
+ const left = labelled ? labelPx : PAD_PX;
5523
+ const usable = Math.max(1, o.width - left - PAD_PX);
5524
+ const scaleX = usable / (longest * (readouts ? READOUT_ROOM2 : 1));
5525
+ return {
5526
+ scaleX,
5527
+ scaleY: 1,
5528
+ translateX: left - bars.origin * scaleX,
5529
+ translateY: PAD_PX + rowPx / 2,
5530
+ width: o.width,
5531
+ height: count * rowPx + 2 * PAD_PX,
5532
+ rowPx,
5533
+ barPx,
5534
+ readouts,
5535
+ labelled,
5536
+ boxesGenes: boxesGenes(rowPx, barPx)
5537
+ };
5538
+ }
5539
+ function stripRowAt(bars, frame, x, y) {
5540
+ const i = Math.round((y - frame.translateY) / frame.rowPx);
5541
+ const row = [bars.reference, ...bars.rows][i];
5542
+ const offset = (x - frame.translateX) / frame.scaleX - bars.origin;
5543
+ return row && i >= 0 && offset >= 0 && offset <= row.bp ? { index: i, row, offset } : void 0;
5544
+ }
5545
+ function walkMarksTree(bars, frame, marks) {
5546
+ const index = new Map(
5547
+ [bars.reference, ...bars.rows].map((row, i) => [row.name, i])
5548
+ );
5549
+ const X = (offset) => (bars.origin + offset) * frame.scaleX + frame.translateX;
5550
+ return el(
5551
+ "g",
5552
+ { "data-testid": "graph-walk-marks" },
5553
+ ...marks.flatMap((m) => {
5554
+ const i = index.get(m.row);
5555
+ if (i === void 0) {
5556
+ return [];
5557
+ }
5558
+ const y = i * frame.rowPx + frame.translateY;
5559
+ const x = X(m.start);
5560
+ const w = Math.max(2, m.bp * frame.scaleX);
5561
+ const tick = (top) => el("rect", { x, y: top, width: w, height: TICK_PX, fill: MARK_INK });
5562
+ return [
5563
+ tick(y - frame.barPx / 2 - TICK_PX - 1),
5564
+ tick(y + frame.barPx / 2 + 1)
5565
+ ];
5566
+ })
5567
+ );
5568
+ }
5569
+ function walkStripLabelsTree(bars, frame, bold = /* @__PURE__ */ new Set()) {
5570
+ return el(
5571
+ "g",
5572
+ { "font-family": "sans-serif", "font-size": LABEL_FONT_PX, fill: "#333" },
5573
+ ...(frame.labelled ? [bars.reference, ...bars.rows] : []).map(
5574
+ (row, i) => el(
5575
+ "text",
5576
+ {
5577
+ x: 6,
5578
+ y: i * frame.rowPx + frame.translateY + 4,
5579
+ "font-weight": bold.has(row.name) ? 600 : void 0
5580
+ },
5581
+ row.label
5582
+ )
5583
+ )
5584
+ );
5585
+ }
5586
+ function stripGeneGaps(frame, gaps, rowGenes) {
5587
+ if (rowGenes ? !rowGenes.size : !gaps) {
5588
+ return void 0;
5589
+ }
5590
+ return frame.boxesGenes ? gaps : { untracked: 0, unread: 0, ...gaps, crowded: true };
5591
+ }
5592
+
5400
5593
  // src/renderer/svgCanvas.ts
5401
5594
  var n = (v) => +v.toFixed(1);
5402
5595
  function paint(css) {
@@ -5510,7 +5703,7 @@ function svgCanvas(width, height) {
5510
5703
  }
5511
5704
 
5512
5705
  // src/version.ts
5513
- var version = "4.3.0";
5706
+ var version = "4.4.1";
5514
5707
 
5515
5708
  // src/viewport.ts
5516
5709
  function axisScaleOf(scale, pixelRows) {
@@ -5558,6 +5751,7 @@ var FONT = 'font-family="Helvetica, Arial, sans-serif" font-size="11"';
5558
5751
  var BAR_PX2 = 48;
5559
5752
  var SWATCH_PX = 18;
5560
5753
  var KEY_GAP_PX = 16;
5754
+ var STRIP_KEY_PX = 22;
5561
5755
  var FADED = "rgb(160,160,160)";
5562
5756
  var EXON_COLOR = "#daa520";
5563
5757
  var GENE_INK2 = "#1c1c22";
@@ -5615,6 +5809,10 @@ function figureSvg(graph, layout, o = {}) {
5615
5809
  const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
5616
5810
  const colorScheme = resolveColorScheme(o.colorScheme ?? "auto", graph);
5617
5811
  const referenceRamp = colorScheme === "reference-position" && !layout.tubeMap ? computeReferenceRamp(graph, rampDomain) : void 0;
5812
+ const rampInterval = referenceRamp && {
5813
+ start: referenceRamp.start,
5814
+ end: referenceRamp.start + referenceRamp.span
5815
+ };
5618
5816
  const deletions = layout.tubeMap ? [] : deletionEdges(graph);
5619
5817
  const layers = o.walks ?? [];
5620
5818
  const walkRamp = layers.some((l) => l.color?.field === "reference") ? referenceRamp ?? computeReferenceRamp(graph, rampDomain) : void 0;
@@ -5681,10 +5879,7 @@ function figureSvg(graph, layout, o = {}) {
5681
5879
  rows,
5682
5880
  { ...t, width: w, height: h },
5683
5881
  {
5684
- ramp: referenceRamp && {
5685
- start: referenceRamp.start,
5686
- end: referenceRamp.start + referenceRamp.span
5687
- },
5882
+ ramp: rampInterval,
5688
5883
  rowGenes: o.rowGenes
5689
5884
  }
5690
5885
  )
@@ -5807,10 +6002,7 @@ function figureSvg(graph, layout, o = {}) {
5807
6002
  if (rows) {
5808
6003
  const key = walkRowsKeyTree(
5809
6004
  walkRowsKey(rows, {
5810
- ramp: referenceRamp && {
5811
- start: referenceRamp.start,
5812
- end: referenceRamp.start + referenceRamp.span
5813
- },
6005
+ ramp: rampInterval,
5814
6006
  genes: o.rowGenes?.size ? o.rowGeneGaps : void 0
5815
6007
  }),
5816
6008
  x,
@@ -5846,6 +6038,36 @@ function figureSvg(graph, layout, o = {}) {
5846
6038
  );
5847
6039
  height = header + h;
5848
6040
  }
6041
+ const strip = o.walkStrip;
6042
+ if (strip && !rows && !layout.tubeMap) {
6043
+ const frame = walkStripFrame(strip.rows, { width });
6044
+ const key = walkRowsKeyTree(
6045
+ walkRowsKey(strip.rows, {
6046
+ ramp: rampInterval,
6047
+ genes: stripGeneGaps(frame, strip.rowGeneGaps, strip.rowGenes)
6048
+ }),
6049
+ 6,
6050
+ height + frame.height
6051
+ );
6052
+ parts.push(
6053
+ `<line x1="0" x2="${width}" y1="${height + 0.5}" y2="${height + 0.5}" stroke="#ddd"/>`,
6054
+ `<svg y="${height}" width="${width}" height="${frame.height}">${serializeEl(
6055
+ walkRowsTree(strip.rows, frame, {
6056
+ ramp: rampInterval,
6057
+ rowGenes: strip.rowGenes,
6058
+ idPrefix: "strip"
6059
+ })
6060
+ )}${serializeEl(
6061
+ walkStripLabelsTree(
6062
+ strip.rows,
6063
+ frame,
6064
+ new Set(layers.map((l) => l.walk))
6065
+ )
6066
+ )}</svg>`,
6067
+ serializeEl(key.tree)
6068
+ );
6069
+ height += frame.height + STRIP_KEY_PX;
6070
+ }
5849
6071
  const metadata = JSON.stringify({
5850
6072
  generator: `@jbrowse/bandage-core@${version}`,
5851
6073
  ...o.spec === void 0 ? {} : { spec: o.spec }
@@ -6456,7 +6678,6 @@ export {
6456
6678
  panSNSample,
6457
6679
  panSNHaplotype,
6458
6680
  panSNContig,
6459
- pathOrigin,
6460
6681
  trimOrigins,
6461
6682
  loadGraph,
6462
6683
  engineRequest,
@@ -6543,6 +6764,15 @@ export {
6543
6764
  walkRowsKeyTree,
6544
6765
  coalesceRuns,
6545
6766
  walkRowsTree,
6767
+ WALK_STRIP_CEILING_PX,
6768
+ LABELLED_ROW_PX,
6769
+ stripMarks,
6770
+ segmentAt,
6771
+ walkStripFrame,
6772
+ stripRowAt,
6773
+ walkMarksTree,
6774
+ walkStripLabelsTree,
6775
+ stripGeneGaps,
6546
6776
  svgCanvas,
6547
6777
  version,
6548
6778
  axisScaleOf,
@@ -16,8 +16,9 @@ import {
16
16
  loadGraph,
17
17
  openGbz,
18
18
  parseRegion,
19
- refNameBinding
20
- } from "../chunks/chunk-IFVLNU7X.js";
19
+ refNameBinding,
20
+ walkRows
21
+ } from "../chunks/chunk-JJU4D2SE.js";
21
22
 
22
23
  // src/cli/figure.ts
23
24
  import { access, open, readFile, writeFile } from "node:fs/promises";
@@ -146,7 +147,8 @@ async function renderSpec(spec, base) {
146
147
  byteSource(resolve(db)),
147
148
  indexLocation ? byteSource(indexLocation) : void 0
148
149
  ),
149
- spec.gbz
150
+ // the strip measures whole walks, which a cut only follows when asked
151
+ spec.walkStrip ? { snarls: "overlapping", ...spec.gbz } : spec.gbz
150
152
  );
151
153
  source = { ...cut, name: `${cut.sample} ${spec.gbz.region}` };
152
154
  } else if (spec.gfa) {
@@ -170,8 +172,10 @@ async function renderSpec(spec, base) {
170
172
  },
171
173
  engine
172
174
  )).result;
175
+ const stripRows = spec.walkStrip ? walkRows(graph, source.region) : void 0;
173
176
  return figureSvg(graph, layout, {
174
177
  ...spec,
178
+ walkStrip: stripRows && { rows: stripRows },
175
179
  walks: spec.walks?.map((w) => typeof w === "string" ? { walk: w } : w),
176
180
  region: source.region,
177
181
  genes: spec.genes ? await readGenes(spec.genes, graph, resolve) : void 0,
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  GENE_INK,
14
14
  HALO_FACTOR,
15
15
  HPRC_GBZ,
16
+ LABELLED_ROW_PX,
16
17
  LABEL_CHAR_PX,
17
18
  LABEL_PAD,
18
19
  LABEL_PX,
@@ -28,6 +29,7 @@ import {
28
29
  ROW_HEIGHT_PX,
29
30
  WALK_FIELDS,
30
31
  WALK_SCHEMES,
32
+ WALK_STRIP_CEILING_PX,
31
33
  WELL_KNOWN_SAMPLES,
32
34
  alongRow,
33
35
  assemblyWalk,
@@ -87,7 +89,6 @@ import {
87
89
  parseRegion,
88
90
  pathColorsLegible,
89
91
  pathLegend,
90
- pathOrigin,
91
92
  pathRibbonOffsets,
92
93
  placeRowGenes,
93
94
  rampHue,
@@ -107,7 +108,11 @@ import {
107
108
  runPaint,
108
109
  schemeCss,
109
110
  screenToLayout,
111
+ segmentAt,
110
112
  serializeEl,
113
+ stripGeneGaps,
114
+ stripMarks,
115
+ stripRowAt,
111
116
  svgCanvas,
112
117
  svgPath,
113
118
  translateCurves,
@@ -118,6 +123,7 @@ import {
118
123
  walkHighlight,
119
124
  walkKey,
120
125
  walkLift,
126
+ walkMarksTree,
121
127
  walkPosition,
122
128
  walkRowReadout,
123
129
  walkRows,
@@ -125,10 +131,12 @@ import {
125
131
  walkRowsKey,
126
132
  walkRowsKeyTree,
127
133
  walkRowsTree,
134
+ walkStripFrame,
135
+ walkStripLabelsTree,
128
136
  wellKnownSample,
129
137
  yToXOf,
130
138
  zoomAbout
131
- } from "./chunks/chunk-IFVLNU7X.js";
139
+ } from "./chunks/chunk-JJU4D2SE.js";
132
140
 
133
141
  // src/index.ts
134
142
  import { getDpr } from "@jbrowse/render-core/canvas2dUtils";
@@ -1776,155 +1784,6 @@ function bubbleSubgraph(graph, segmentIds) {
1776
1784
  referencePath: graph.referencePath
1777
1785
  };
1778
1786
  }
1779
-
1780
- // src/layout/walkStrip.ts
1781
- var PAD_PX = 6;
1782
- var MIN_ROW_PX = 3;
1783
- var LABELLED_ROW_PX = 12;
1784
- var READOUT_ROOM = 1.3;
1785
- var MARK_INK = "#111";
1786
- var TICK_PX2 = 3;
1787
- function stripMarks(graph, rows, node) {
1788
- const visits = graph.pathVisits?.get(node.name) ?? [];
1789
- const marks = [];
1790
- for (const row of rows) {
1791
- const axis = row.axis;
1792
- if (!axis) {
1793
- continue;
1794
- }
1795
- const path = pathOrigin(row.name).name;
1796
- for (const v of visits) {
1797
- if (v.path !== path) {
1798
- continue;
1799
- }
1800
- const { start, end } = alongRow(axis, v.start, v.start + node.length);
1801
- if (end > 0 && start < row.bp) {
1802
- marks.push({ row: row.name, start, bp: end - start });
1803
- }
1804
- }
1805
- }
1806
- return marks;
1807
- }
1808
- var spanCache = /* @__PURE__ */ new WeakMap();
1809
- function spansOf(graph, name) {
1810
- let byName = spanCache.get(graph);
1811
- if (!byName) {
1812
- byName = /* @__PURE__ */ new Map();
1813
- spanCache.set(graph, byName);
1814
- }
1815
- if (byName.has(name)) {
1816
- return byName.get(name);
1817
- }
1818
- const lengthOf = new Map(graph.nodes.map((n) => [n.id, n.length]));
1819
- const pieces = (graph.paths ?? []).filter((p) => p.name === name);
1820
- let spans;
1821
- if (pieces.every((p) => p.start !== void 0)) {
1822
- spans = { ids: [], starts: [], ends: [] };
1823
- for (const piece of [...pieces].sort((a, b) => a.start - b.start)) {
1824
- let pos = piece.start;
1825
- for (const id of piece.nodeIds) {
1826
- const len = lengthOf.get(id) ?? 0;
1827
- spans.ids.push(id);
1828
- spans.starts.push(pos);
1829
- spans.ends.push(pos + len);
1830
- pos += len;
1831
- }
1832
- }
1833
- }
1834
- byName.set(name, spans);
1835
- return spans;
1836
- }
1837
- function segmentAt(graph, row, offset, pxPerBp) {
1838
- const axis = row.axis;
1839
- const spans = axis && spansOf(graph, row.name);
1840
- if (!axis || !spans || offset < 0 || offset > row.bp) {
1841
- return void 0;
1842
- }
1843
- const at = axis.reversed ? axis.start - offset : axis.start + offset;
1844
- const tol = 0.5 / pxPerBp;
1845
- const bar = axis.reversed ? { start: axis.start - row.bp, end: axis.start } : { start: axis.start, end: axis.start + row.bp };
1846
- const from = Math.max(at - tol, bar.start);
1847
- const to = Math.min(at + tol, bar.end);
1848
- let lo = 0;
1849
- let hi = spans.starts.length;
1850
- while (lo < hi) {
1851
- const mid = lo + hi >> 1;
1852
- if (spans.ends[mid] <= from) {
1853
- lo = mid + 1;
1854
- } else {
1855
- hi = mid;
1856
- }
1857
- }
1858
- let best;
1859
- let bestLen = -1;
1860
- let bestHolds = false;
1861
- for (let i = lo; i < spans.starts.length && spans.starts[i] < to; i++) {
1862
- const len = spans.ends[i] - spans.starts[i];
1863
- const holds = spans.starts[i] <= at && at < spans.ends[i];
1864
- if (len > bestLen || len === bestLen && holds && !bestHolds) {
1865
- bestLen = len;
1866
- best = spans.ids[i];
1867
- bestHolds = holds;
1868
- }
1869
- }
1870
- return best;
1871
- }
1872
- function walkStripFrame(bars, o) {
1873
- const count = bars.rows.length + 1;
1874
- const rowPx = Math.max(
1875
- MIN_ROW_PX,
1876
- Math.min(ROW_HEIGHT_PX, Math.floor((o.maxHeight - 2 * PAD_PX) / count))
1877
- );
1878
- const labelled = rowPx >= LABELLED_ROW_PX;
1879
- const readouts = rowPx >= 14;
1880
- const barPx = Math.max(2, Math.round(rowPx * 0.6));
1881
- const longest = Math.max(bars.reference.bp, ...bars.rows.map((r) => r.bp), 1);
1882
- const left = labelled ? o.labelPx : PAD_PX;
1883
- const usable = Math.max(1, o.width - left - PAD_PX);
1884
- const scaleX = usable / (longest * (readouts ? READOUT_ROOM : 1));
1885
- return {
1886
- scaleX,
1887
- scaleY: 1,
1888
- translateX: left - bars.origin * scaleX,
1889
- translateY: PAD_PX + rowPx / 2,
1890
- width: o.width,
1891
- height: count * rowPx + 2 * PAD_PX,
1892
- rowPx,
1893
- barPx,
1894
- readouts,
1895
- labelled
1896
- };
1897
- }
1898
- function stripRowAt(bars, frame, x, y) {
1899
- const i = Math.round((y - frame.translateY) / frame.rowPx);
1900
- const row = [bars.reference, ...bars.rows][i];
1901
- const offset = (x - frame.translateX) / frame.scaleX - bars.origin;
1902
- return row && i >= 0 && offset >= 0 && offset <= row.bp ? { index: i, row, offset } : void 0;
1903
- }
1904
- function walkMarksTree(bars, frame, marks) {
1905
- const index = new Map(
1906
- [bars.reference, ...bars.rows].map((row, i) => [row.name, i])
1907
- );
1908
- const X = (offset) => (bars.origin + offset) * frame.scaleX + frame.translateX;
1909
- return el(
1910
- "g",
1911
- { "data-testid": "graph-walk-marks" },
1912
- ...marks.flatMap((m) => {
1913
- const i = index.get(m.row);
1914
- if (i === void 0) {
1915
- return [];
1916
- }
1917
- const y = i * frame.rowPx + frame.translateY;
1918
- const x = X(m.start);
1919
- const w = Math.max(2, m.bp * frame.scaleX);
1920
- const tick = (top) => el("rect", { x, y: top, width: w, height: TICK_PX2, fill: MARK_INK });
1921
- return [
1922
- tick(y - frame.barPx / 2 - TICK_PX2 - 1),
1923
- tick(y + frame.barPx / 2 + 1)
1924
- ];
1925
- })
1926
- );
1927
- }
1928
1787
  export {
1929
1788
  BUBBLE_KIND_COLORS,
1930
1789
  BUBBLE_KIND_NAMES,
@@ -1958,6 +1817,7 @@ export {
1958
1817
  ON_REFERENCE as WALK_ON_REFERENCE,
1959
1818
  OUTSIDE_CUT as WALK_OUTSIDE_CUT,
1960
1819
  WALK_SCHEMES,
1820
+ WALK_STRIP_CEILING_PX,
1961
1821
  WELL_KNOWN_SAMPLES,
1962
1822
  alongRow,
1963
1823
  assemblyWalk,
@@ -2051,6 +1911,7 @@ export {
2051
1911
  segmentAt,
2052
1912
  serializeEl,
2053
1913
  stripBlockAt,
1914
+ stripGeneGaps,
2054
1915
  stripMarks,
2055
1916
  stripRowAt,
2056
1917
  svgCanvas,
@@ -2076,6 +1937,7 @@ export {
2076
1937
  walkRowsKeyTree,
2077
1938
  walkRowsTree,
2078
1939
  walkStripFrame,
1940
+ walkStripLabelsTree,
2079
1941
  warpX,
2080
1942
  wellKnownSample,
2081
1943
  wheelZoomFactor,
@@ -3,7 +3,7 @@ import type { FigureOptions } from '../figure.js';
3
3
  import type { GbzSource } from '../gbzCut.js';
4
4
  import type { LayoutModeValue } from '../layoutModes.js';
5
5
  import type { WalkLayer } from '../walkEncoding.js';
6
- export interface FigureSpec extends Omit<FigureOptions, 'walks' | 'region' | 'genes' | 'spec'> {
6
+ export interface FigureSpec extends Omit<FigureOptions, 'walks' | 'region' | 'genes' | 'spec' | 'walkStrip'> {
7
7
  gfa?: string;
8
8
  gbz?: GbzSource;
9
9
  region?: string;
@@ -12,6 +12,7 @@ export interface FigureSpec extends Omit<FigureOptions, 'walks' | 'region' | 'ge
12
12
  quality?: number;
13
13
  bubbleSpread?: BubbleSpread;
14
14
  walks?: (string | WalkLayer)[];
15
+ walkStrip?: boolean;
15
16
  genes?: {
16
17
  file: string;
17
18
  index?: string;
@@ -2,6 +2,7 @@ import type { ColorScheme } from './colorSchemes.js';
2
2
  import type { FacetInput } from './facetGrid.js';
3
3
  import type { GeneModel } from './genes/genePins.js';
4
4
  import type { GeneGaps, RowGene, WalkRowsWithCalls } from './layout/walkRowDraw.js';
5
+ import type { WalkRows } from './layout/walkRows.js';
5
6
  import type { NodeWidth } from './nodeWidths.js';
6
7
  import type { Graph, LayoutResult } from './types.js';
7
8
  import type { WalkLayer } from './walkEncoding.js';
@@ -30,5 +31,10 @@ export interface FigureOptions {
30
31
  walkRows?: WalkRowsWithCalls;
31
32
  rowGenes?: Map<string, RowGene[]>;
32
33
  rowGeneGaps?: GeneGaps;
34
+ walkStrip?: {
35
+ rows: WalkRows;
36
+ rowGenes?: Map<string, RowGene[]>;
37
+ rowGeneGaps?: GeneGaps;
38
+ };
33
39
  }
34
40
  export declare function figureSvg(graph: Graph, layout: LayoutResult, o?: FigureOptions): string;
@@ -58,7 +58,7 @@ export { walkRows } from './layout/walkRows.js';
58
58
  export type { WalkAxis, WalkRow, WalkRows, WalkRun } from './layout/walkRows.js';
59
59
  export { BAR_PX as WALK_BAR_PX, DISAGREES as WALK_DISAGREES, GAP_PX as WALK_GAP_PX, GENE_INK as WALK_GENE_INK, OFF_REFERENCE as WALK_OFF_REFERENCE, ON_REFERENCE as WALK_ON_REFERENCE, OUTSIDE_CUT as WALK_OUTSIDE_CUT, alongRow, coalesceRuns, placeRowGenes, readoutPlacement, rowGeneBoxes, rowSpan, runPaint, unitTicks, walkRowReadout, walkRowsKey, walkRowsKeyTree, walkRowsTree, } from './layout/walkRowDraw.js';
60
60
  export type { GeneBox, GeneGaps, KeyEntry, KeySwatch, ReadoutCall, RowGene, WalkRowsFrame, WalkRowsWithCalls, } from './layout/walkRowDraw.js';
61
- export { LABELLED_ROW_PX, segmentAt, stripMarks, stripRowAt, walkMarksTree, walkStripFrame, } from './layout/walkStrip.js';
61
+ export { LABELLED_ROW_PX, WALK_STRIP_CEILING_PX, segmentAt, stripGeneGaps, stripMarks, stripRowAt, walkMarksTree, walkStripFrame, walkStripLabelsTree, } from './layout/walkStrip.js';
62
62
  export type { StripFrame, StripMark } from './layout/walkStrip.js';
63
63
  export { el, serializeEl } from './el.js';
64
64
  export type { El, ElAttrs } from './el.js';
@@ -50,6 +50,7 @@ export interface RowGene {
50
50
  }[];
51
51
  }
52
52
  export declare function placeRowGenes(rows: WalkRow[], byRow: Map<string, GeneModel[]>): Map<string, RowGene[]>;
53
+ export declare function boxesGenes(rowPx: number, barPx: number): boolean;
53
54
  export interface GeneBox {
54
55
  name: string;
55
56
  x: number;
@@ -66,7 +67,10 @@ export interface GeneBox {
66
67
  size: number;
67
68
  };
68
69
  }
69
- export declare function rowGeneBoxes(genes: RowGene[], X: (offset: number) => number, y: number): GeneBox[];
70
+ export declare function rowGeneBoxes(genes: RowGene[], X: (offset: number) => number, y: number, { rowPx, barPx }?: {
71
+ rowPx?: number | undefined;
72
+ barPx?: number | undefined;
73
+ }): GeneBox[];
70
74
  export type KeySwatch = {
71
75
  kind: 'bar';
72
76
  fill: string;
@@ -82,6 +86,7 @@ export interface KeyEntry {
82
86
  note?: true;
83
87
  }
84
88
  export interface GeneGaps {
89
+ crowded?: boolean;
85
90
  untracked: number;
86
91
  unplaced?: number;
87
92
  unread: number;
@@ -1,6 +1,7 @@
1
- import type { WalkRowsFrame } from './walkRowDraw.js';
1
+ import type { GeneGaps, RowGene, WalkRowsFrame } from './walkRowDraw.js';
2
2
  import type { WalkRow, WalkRows } from './walkRows.js';
3
3
  import type { Graph, GraphNode } from '../types.js';
4
+ export declare const WALK_STRIP_CEILING_PX = 260;
4
5
  export declare const LABELLED_ROW_PX = 12;
5
6
  export interface StripMark {
6
7
  row: string;
@@ -14,11 +15,12 @@ export interface StripFrame extends WalkRowsFrame {
14
15
  barPx: number;
15
16
  readouts: boolean;
16
17
  labelled: boolean;
18
+ boxesGenes: boolean;
17
19
  }
18
20
  export declare function walkStripFrame(bars: WalkRows, o: {
19
21
  width: number;
20
- maxHeight: number;
21
- labelPx: number;
22
+ maxHeight?: number;
23
+ labelPx?: number;
22
24
  }): StripFrame;
23
25
  export declare function stripRowAt(bars: WalkRows, frame: StripFrame, x: number, y: number): {
24
26
  index: number;
@@ -26,3 +28,5 @@ export declare function stripRowAt(bars: WalkRows, frame: StripFrame, x: number,
26
28
  offset: number;
27
29
  } | undefined;
28
30
  export declare function walkMarksTree(bars: WalkRows, frame: StripFrame, marks: StripMark[]): import("../index.js").El;
31
+ export declare function walkStripLabelsTree(bars: WalkRows, frame: StripFrame, bold?: ReadonlySet<string>): import("../index.js").El;
32
+ export declare function stripGeneGaps(frame: StripFrame, gaps: GeneGaps | undefined, rowGenes?: Map<string, RowGene[]>): GeneGaps | undefined;
@@ -1 +1 @@
1
- export declare const version = "4.3.0";
1
+ export declare const version = "4.4.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/bandage-core",
3
- "version": "4.3.0",
3
+ "version": "4.4.1",
4
4
  "description": "Pangenome graph layout and drawing with no host: GFA in, the Bandage force-directed engine and reference-anchored layouts, a Canvas2D renderer, hit testing and labels out",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {