@jbrowse/bandage-core 4.0.25 → 4.0.26

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.
@@ -387,6 +387,18 @@ function anchorFromPaths(graph, preferred) {
387
387
  function anchorGraph(graph, preferred) {
388
388
  return graph.anchoredBy === "tags" ? graph : anchorFromPaths(graph, preferred);
389
389
  }
390
+ function trimOrigins(origins, paths, stretches) {
391
+ const pieces = /* @__PURE__ */ new Map();
392
+ for (const path of paths) {
393
+ const origin = pathOrigin(path.name).name;
394
+ pieces.set(origin, (pieces.get(origin) ?? 0) + 1);
395
+ }
396
+ return origins.map((o) => {
397
+ const path = paths.find((p) => pathOrigin(p.name).name === o.name);
398
+ const stretch = path ? stretches.get(path.name) : void 0;
399
+ return stretch && pieces.get(o.name) === 1 ? { ...o, start: o.start + stretch.dropped, length: stretch.bp } : o;
400
+ });
401
+ }
390
402
 
391
403
  // src/gfa/gfaConverter.ts
392
404
  function surveySegments(gfaGraph) {
@@ -4278,7 +4290,7 @@ function liftOf(walks, ramp) {
4278
4290
  function rangeText(contig, start, end) {
4279
4291
  const s = Math.round(start);
4280
4292
  const e = Math.round(end);
4281
- return `${contig ? `${contig}:` : ""}${s.toLocaleString()}-${e.toLocaleString()} (${formatBp(e - s)})`;
4293
+ return `${contig ? `${contig}:` : ""}${s.toLocaleString("en-US")}-${e.toLocaleString("en-US")} (${formatBp(e - s)})`;
4282
4294
  }
4283
4295
  function walkKey(walk, reference) {
4284
4296
  const { field } = walk.encoding;
@@ -4299,7 +4311,7 @@ function walkPosition(walk, nodeId, length) {
4299
4311
  return void 0;
4300
4312
  }
4301
4313
  const start = Math.round(range.start + progress * walk.bp - length / 2);
4302
- return `${range.contig}:${start.toLocaleString()}-${(start + length).toLocaleString()}`;
4314
+ return `${range.contig}:${start.toLocaleString("en-US")}-${(start + length).toLocaleString("en-US")}`;
4303
4315
  }
4304
4316
 
4305
4317
  // src/facetGrid.ts
@@ -4703,7 +4715,7 @@ function svgCanvas(width, height) {
4703
4715
  }
4704
4716
 
4705
4717
  // src/version.ts
4706
- var version = "4.0.25";
4718
+ var version = "4.0.26";
4707
4719
 
4708
4720
  // src/viewport.ts
4709
4721
  function axisScaleOf(scale, pixelRows) {
@@ -4798,14 +4810,17 @@ function figureSvg(graph, layout, o = {}) {
4798
4810
  const width = o.width ?? 1200;
4799
4811
  const room = o.height ?? 800;
4800
4812
  const region = o.region;
4801
- const bounds = drawingBounds(layout, { region });
4813
+ const rampDomain = o.colorDomain ?? region;
4814
+ const bounds = drawingBounds(layout, {
4815
+ region: o.fitToDrawing ? void 0 : region
4816
+ });
4802
4817
  const pixelRows = layout.pixelRows ?? false;
4803
4818
  const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
4804
4819
  const colorScheme = resolveColorScheme(o.colorScheme ?? "auto", graph);
4805
- const referenceRamp = colorScheme === "reference-position" && !layout.tubeMap ? computeReferenceRamp(graph, region) : void 0;
4820
+ const referenceRamp = colorScheme === "reference-position" && !layout.tubeMap ? computeReferenceRamp(graph, rampDomain) : void 0;
4806
4821
  const deletions = layout.tubeMap ? [] : deletionEdges(graph);
4807
4822
  const layers = o.walks ?? [];
4808
- const walkRamp = layers.some((l) => l.color?.field === "reference") ? referenceRamp ?? computeReferenceRamp(graph, region) : void 0;
4823
+ const walkRamp = layers.some((l) => l.color?.field === "reference") ? referenceRamp ?? computeReferenceRamp(graph, rampDomain) : void 0;
4809
4824
  const lift = layers.length > 0 && !layout.tubeMap ? walkLift(graph, layers, walkRamp) : void 0;
4810
4825
  const by = o.facet ?? "none";
4811
4826
  const panels = by !== "none" && lift && lift.walks.length > 1 ? facetLifts(graph, lift, layers, walkRamp) : void 0;
@@ -5128,7 +5143,7 @@ function genesFromBed(text) {
5128
5143
  continue;
5129
5144
  }
5130
5145
  const refName = cols[0];
5131
- const name = cols[3] || `${refName}:${start + 1}-${end}`;
5146
+ const name = cols[3] && cols[3] !== "." ? cols[3] : `${refName}:${start + 1}-${end}`;
5132
5147
  const sizes = cols[10]?.split(",").filter((s) => s !== "").map(Number);
5133
5148
  const starts = cols[11]?.split(",").filter((s) => s !== "").map(Number);
5134
5149
  const blocks = sizes?.length && sizes.length === starts?.length ? sizes.map((size, i) => ({
@@ -5435,6 +5450,7 @@ export {
5435
5450
  panSNSample,
5436
5451
  panSNHaplotype,
5437
5452
  panSNContig,
5453
+ trimOrigins,
5438
5454
  loadGraph,
5439
5455
  engineRequest,
5440
5456
  engineKey,
@@ -5452,6 +5468,7 @@ export {
5452
5468
  walkRowsExtent,
5453
5469
  FORCE_LAYOUT_LABEL,
5454
5470
  LAYOUT_MODES,
5471
+ LAYOUT_MODE_VALUES,
5455
5472
  modeUsesLayoutEngine,
5456
5473
  layoutModeByValue,
5457
5474
  COLOR_SCHEMES,
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  HPRC_GBZ,
4
+ LAYOUT_MODE_VALUES,
4
5
  cutGbzRegion,
5
6
  featuresOnBackbone,
6
7
  figureSvg,
@@ -14,10 +15,10 @@ import {
14
15
  loadGraph,
15
16
  openGbz,
16
17
  parseRegion
17
- } from "../chunks/chunk-B5ZHLU4N.js";
18
+ } from "../chunks/chunk-75KVO2WV.js";
18
19
 
19
20
  // src/cli/figure.ts
20
- import { open, readFile, writeFile } from "node:fs/promises";
21
+ import { access, open, readFile, writeFile } from "node:fs/promises";
21
22
  import path from "node:path";
22
23
  import { parseArgs } from "node:util";
23
24
  import { gunzipSync } from "node:zlib";
@@ -36,7 +37,7 @@ function byteSource(location) {
36
37
  return new Uint8Array(await res.arrayBuffer());
37
38
  },
38
39
  async stat() {
39
- const res = await fetch(location, { method: "HEAD" });
40
+ const res = await fetchOk(location, { method: "HEAD" });
40
41
  return { size: Number(res.headers.get("content-length")) };
41
42
  }
42
43
  };
@@ -53,8 +54,15 @@ function byteSource(location) {
53
54
  }
54
55
  };
55
56
  }
57
+ async function fetchOk(location, init) {
58
+ const res = await fetch(location, init);
59
+ if (!res.ok) {
60
+ throw new Error(`HTTP ${res.status} reading ${location}`);
61
+ }
62
+ return res;
63
+ }
56
64
  async function text(location) {
57
- const bytes = isUrl(location) ? new Uint8Array(await (await fetch(location)).arrayBuffer()) : await readFile(location);
65
+ const bytes = isUrl(location) ? new Uint8Array(await (await fetchOk(location)).arrayBuffer()) : await readFile(location);
58
66
  const gzipped = bytes[0] === 31 && bytes[1] === 139;
59
67
  return new TextDecoder().decode(gzipped ? gunzipSync(bytes) : bytes);
60
68
  }
@@ -94,15 +102,43 @@ var engine = async (request) => {
94
102
  const result = bandage.computeLayout(request.graph, request.options);
95
103
  return { result, duration: performance.now() - start };
96
104
  };
105
+ async function exists(location) {
106
+ try {
107
+ await (isUrl(location) ? fetchOk(location, { method: "HEAD" }) : access(location));
108
+ return true;
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+ async function siblingIndex(db) {
114
+ const sibling = db.replace(/\.gbz\.db$/i, ".haplotype-index.db");
115
+ return sibling !== db && await exists(sibling) ? sibling : void 0;
116
+ }
117
+ var UNDRAWABLE = /* @__PURE__ */ new Set(["walkrows", "tubemap", "tubemapref"]);
118
+ function checkLayout(layout) {
119
+ if (layout === void 0) {
120
+ return;
121
+ }
122
+ if (!LAYOUT_MODE_VALUES.includes(layout)) {
123
+ throw new Error(
124
+ `unknown layout "${layout}": one of ${LAYOUT_MODE_VALUES.filter((v) => !UNDRAWABLE.has(v)).join(", ")}`
125
+ );
126
+ }
127
+ if (UNDRAWABLE.has(layout)) {
128
+ throw new Error(`a figure cannot draw the "${layout}" layout yet`);
129
+ }
130
+ }
97
131
  async function renderSpec(spec, base) {
132
+ checkLayout(spec.layout);
98
133
  const resolve = (location) => isUrl(location) ? location : path.resolve(base, location);
99
134
  let source;
100
135
  if (spec.gbz) {
101
136
  const { db, index } = spec.gbz.db === "hprc" ? HPRC_GBZ : spec.gbz;
137
+ const indexLocation = index ? resolve(index) : await siblingIndex(resolve(db));
102
138
  const cut = await cutGbzRegion(
103
139
  await openGbz(
104
140
  byteSource(resolve(db)),
105
- index ? byteSource(resolve(index)) : void 0
141
+ indexLocation ? byteSource(indexLocation) : void 0
106
142
  ),
107
143
  spec.gbz
108
144
  );
@@ -136,14 +172,32 @@ async function renderSpec(spec, base) {
136
172
  spec
137
173
  });
138
174
  }
175
+ var USAGE = "usage: bandage-figure <spec.json> [-o figure.svg]";
176
+ function args() {
177
+ try {
178
+ return parseArgs({
179
+ allowPositionals: true,
180
+ options: {
181
+ out: { type: "string", short: "o" },
182
+ help: { type: "boolean", short: "h" }
183
+ }
184
+ });
185
+ } catch (e) {
186
+ console.error(`${e instanceof Error ? e.message : String(e)}
187
+ ${USAGE}`);
188
+ process.exit(2);
189
+ }
190
+ }
139
191
  async function main() {
140
- const { positionals, values } = parseArgs({
141
- allowPositionals: true,
142
- options: { out: { type: "string", short: "o" } }
143
- });
192
+ const { positionals, values } = args();
193
+ if (values.help) {
194
+ process.stdout.write(`${USAGE}
195
+ `);
196
+ return;
197
+ }
144
198
  const file = positionals[0];
145
199
  if (!file) {
146
- console.error("usage: bandage-figure <spec.json> [-o figure.svg]");
200
+ console.error(USAGE);
147
201
  process.exit(2);
148
202
  }
149
203
  const spec = JSON.parse(await readFile(file, "utf8"));
@@ -154,4 +208,9 @@ async function main() {
154
208
  process.stdout.write(svg);
155
209
  }
156
210
  }
157
- await main();
211
+ try {
212
+ await main();
213
+ } catch (e) {
214
+ console.error(`bandage-figure: ${e instanceof Error ? e.message : String(e)}`);
215
+ process.exit(1);
216
+ }
package/dist/index.js CHANGED
@@ -90,6 +90,7 @@ import {
90
90
  svgCanvas,
91
91
  svgPath,
92
92
  translateCurves,
93
+ trimOrigins,
93
94
  version,
94
95
  viewportOf,
95
96
  walkHighlight,
@@ -101,7 +102,7 @@ import {
101
102
  wellKnownSample,
102
103
  yToXOf,
103
104
  zoomAbout
104
- } from "./chunks/chunk-B5ZHLU4N.js";
105
+ } from "./chunks/chunk-75KVO2WV.js";
105
106
 
106
107
  // src/index.ts
107
108
  import { getDpr } from "@jbrowse/render-core/canvas2dUtils";
@@ -725,10 +726,11 @@ function shapeOf(d, color, alpha) {
725
726
  const commands = parsePath(d);
726
727
  return { commands, color, alpha, ...extentOf(commands) };
727
728
  }
728
- function layerOf(layout, type) {
729
+ function layerOf(layout, type, tubes) {
729
730
  const { shapes } = layout;
731
+ const colorOf = (s) => tubes?.[s.id] ?? s.color;
730
732
  const rects = [...shapes.rectangles, ...shapes.verticalRectangles].filter((r) => r.type === type).map((r) => ({
731
- color: r.color,
733
+ color: colorOf(r),
732
734
  alpha: r.alpha ?? 1,
733
735
  x0: r.xStart,
734
736
  x1: r.xEnd + 1,
@@ -736,22 +738,30 @@ function layerOf(layout, type) {
736
738
  y1: r.yEnd + 1
737
739
  }));
738
740
  const curves = curvePaths(shapes.curves, type).map(
739
- (c) => shapeOf(c.path, c.color, c.alpha ?? 1)
741
+ (c) => shapeOf(c.path, colorOf(c), c.alpha ?? 1)
740
742
  );
741
- const corners = shapes.corners.filter((c) => c.type === type).map((c) => shapeOf(c.path, c.color, 1));
743
+ const corners = shapes.corners.filter((c) => c.type === type).map((c) => shapeOf(c.path, colorOf(c), 1));
742
744
  return { rects, shapes: [...curves, ...corners] };
743
745
  }
744
- function tubeMapPicture(layout) {
746
+ function tubeMapPicture(layout, colors = {}) {
745
747
  const nodes = [];
746
748
  layout.nodes.forEach((node) => {
747
749
  if (node.order >= 0) {
748
750
  const commands = parsePath(nodeOutlinePath(node));
749
- nodes.push({ name: node.name, commands, ...extentOf(commands) });
751
+ nodes.push({
752
+ name: node.name,
753
+ commands,
754
+ ...extentOf(commands),
755
+ color: colors.nodes?.get(node.name)
756
+ });
750
757
  }
751
758
  });
752
759
  return {
753
760
  bounds: layout.bounds,
754
- layers: [layerOf(layout, "haplotype"), layerOf(layout, "read")],
761
+ layers: [
762
+ layerOf(layout, "haplotype", colors.tubes),
763
+ layerOf(layout, "read")
764
+ ],
755
765
  nodes,
756
766
  mismatches: tubeMapMismatches(layout).filter(
757
767
  (m) => m.kind !== "insertion" || !m.softClip
@@ -826,6 +836,7 @@ var MIN_OUTLINE_ALPHA = 0.15;
826
836
  function outlineAlpha(screenWidth) {
827
837
  return Math.max(MIN_OUTLINE_ALPHA, Math.min(1, screenWidth / FULL_OUTLINE_PX));
828
838
  }
839
+ var NODE_TINT_ALPHA = 0.55;
829
840
  function drawTubeMap(ctx, picture, frame) {
830
841
  const { x, y, width } = frame;
831
842
  const visible = (item) => x(item.x1) >= 0 && x(item.x0) <= width;
@@ -847,8 +858,10 @@ function drawTubeMap(ctx, picture, frame) {
847
858
  const lit = node.name === frame.highlightNode;
848
859
  ctx.beginPath();
849
860
  trace(ctx, node.commands, x, y);
850
- ctx.fillStyle = lit ? "rgba(255,192,203,0.5)" : fill;
861
+ const tint = lit ? void 0 : node.color;
862
+ ctx.fillStyle = lit ? "rgba(255,192,203,0.5)" : tint ?? fill;
851
863
  ctx.strokeStyle = lit ? "#ff0000" : stroke;
864
+ ctx.globalAlpha = tint ? NODE_TINT_ALPHA : 1;
852
865
  ctx.fill();
853
866
  ctx.globalAlpha = lit ? 1 : outlineAlpha(x(node.x1) - x(node.x0));
854
867
  ctx.stroke();
@@ -897,6 +910,21 @@ function drawMismatches(ctx, marks, frame) {
897
910
  }
898
911
  }
899
912
 
913
+ // src/tubeMap/nodeColors.ts
914
+ import { abgrToCssRgba as abgrToCssRgba2 } from "@jbrowse/core/util/colorBits";
915
+ function tubeMapNodeColors(graph, colorScheme, referenceRamp) {
916
+ if (colorScheme === "uniform" || colorScheme === "grey") {
917
+ return void 0;
918
+ }
919
+ const range = { ...computeColorSchemeRange(graph), referenceRamp };
920
+ return new Map(
921
+ graph.nodes.map((node, index) => [
922
+ node.id,
923
+ abgrToCssRgba2(getNodeColor(node, index, colorScheme, range))
924
+ ])
925
+ );
926
+ }
927
+
900
928
  // src/tubeMap/warp.ts
901
929
  var MAX_GAP_PX = 24;
902
930
  var isReal = (c) => c.bp1 > c.bp0;
@@ -1696,13 +1724,27 @@ function bubbleSubgraph(graph, segmentIds) {
1696
1724
  const keep = new Set(segmentIds);
1697
1725
  const nodes = graph.nodes.filter((n) => keep.has(n.name));
1698
1726
  const ids = new Set(nodes.map((n) => n.id));
1699
- const paths = graph.paths?.map((p) => ({ ...p, nodeIds: p.nodeIds.filter((id) => ids.has(id)) })).filter((p) => p.nodeIds.length > 0);
1727
+ const lengthOf = new Map(graph.nodes.map((n) => [n.id, n.length]));
1728
+ const bpOf = (steps) => steps.reduce((sum, id) => sum + (lengthOf.get(id) ?? 0), 0);
1729
+ const stretches = /* @__PURE__ */ new Map();
1730
+ const paths = graph.paths?.flatMap((p) => {
1731
+ const first = p.nodeIds.findIndex((id) => ids.has(id));
1732
+ if (first < 0) {
1733
+ return [];
1734
+ }
1735
+ const nodeIds = p.nodeIds.filter((id) => ids.has(id));
1736
+ stretches.set(p.name, {
1737
+ dropped: bpOf(p.nodeIds.slice(0, first)),
1738
+ bp: bpOf(nodeIds)
1739
+ });
1740
+ return [{ ...p, nodeIds }];
1741
+ });
1700
1742
  return {
1701
1743
  name: graph.name,
1702
1744
  nodes,
1703
1745
  edges: graph.edges.filter((e) => ids.has(e.from) && ids.has(e.to)),
1704
1746
  ...paths?.length ? { paths } : {},
1705
- anchorPaths: graph.anchorPaths,
1747
+ anchorPaths: graph.anchorPaths && trimOrigins(graph.anchorPaths, graph.paths ?? [], stretches),
1706
1748
  pathVisits: graph.pathVisits,
1707
1749
  anchoredBy: graph.anchoredBy,
1708
1750
  referencePath: graph.referencePath
@@ -1815,6 +1857,7 @@ export {
1815
1857
  tubeMapFrame,
1816
1858
  tubeMapGenes,
1817
1859
  tubeMapNodeAt,
1860
+ tubeMapNodeColors,
1818
1861
  tubeMapPicture,
1819
1862
  tubeX,
1820
1863
  version,
@@ -20,6 +20,11 @@ export interface FigureOptions {
20
20
  start: number;
21
21
  end: number;
22
22
  };
23
+ colorDomain?: {
24
+ start: number;
25
+ end: number;
26
+ };
27
+ fitToDrawing?: boolean;
23
28
  genes?: GeneModel[];
24
29
  spec?: unknown;
25
30
  }
@@ -19,7 +19,8 @@ export { wheelZoomFactor } from './util/wheelZoom.js';
19
19
  export { REFERENCE_STRIP_ZONE_PX, drawReferenceStrip, referenceStripBlocks, stripBlockAt, } from './referenceStrip.js';
20
20
  export type { StripBlock } from './referenceStrip.js';
21
21
  export { drawTubeMap, tubeMapPicture } from './tubeMap/draw.js';
22
- export type { TubeMapFrame, TubeMapPicture } from './tubeMap/draw.js';
22
+ export type { TubeMapColors, TubeMapFrame, TubeMapPicture, } from './tubeMap/draw.js';
23
+ export { tubeMapNodeColors } from './tubeMap/nodeColors.js';
23
24
  export { referenceKnots, warpX } from './tubeMap/warp.js';
24
25
  export { tubeMapFrame, tubeMapNodeAt } from './tubeMap/frame.js';
25
26
  export type { TubeMapTransform, TubeMapView } from './tubeMap/frame.js';
@@ -1,4 +1,4 @@
1
- import type { Graph, PathOrigin, PathVisit } from './types.js';
1
+ import type { Graph, GraphPath, PathOrigin, PathVisit } from './types.js';
2
2
  export declare function pathOrigin(pathName: string): {
3
3
  name: string;
4
4
  start: number;
@@ -18,3 +18,7 @@ export declare function surveyPaths(paths: PathSteps[], lengthOf: (segmentId: st
18
18
  export declare function chooseReferencePath(anchorPaths: PathOrigin[], preferred: string | undefined): PathOrigin | undefined;
19
19
  export declare function anchorFromPaths(graph: Graph, preferred: string | undefined): Graph;
20
20
  export declare function anchorGraph(graph: Graph, preferred: string | undefined): Graph;
21
+ export declare function trimOrigins(origins: PathOrigin[], paths: GraphPath[], stretches: Map<string, {
22
+ dropped: number;
23
+ bp: number;
24
+ }>): PathOrigin[];
@@ -54,10 +54,15 @@ export interface TubeMapPicture {
54
54
  commands: Command[];
55
55
  x0: number;
56
56
  x1: number;
57
+ color?: string;
57
58
  }[];
58
59
  mismatches: TubeMapMismatch[];
59
60
  }
60
- export declare function tubeMapPicture(layout: TubeMapLayout): TubeMapPicture;
61
+ export interface TubeMapColors {
62
+ tubes?: readonly string[];
63
+ nodes?: ReadonlyMap<string, string>;
64
+ }
65
+ export declare function tubeMapPicture(layout: TubeMapLayout, colors?: TubeMapColors): TubeMapPicture;
61
66
  export interface TubeMapFrame extends TubeMapTransform {
62
67
  width: number;
63
68
  highlightNode?: string | null;
@@ -0,0 +1,4 @@
1
+ import type { ResolvedColorScheme } from '../colorSchemes.js';
2
+ import type { ReferenceRamp } from '../renderer/GeometryBuilder.js';
3
+ import type { Graph } from '../types.js';
4
+ export declare function tubeMapNodeColors(graph: Graph, colorScheme: ResolvedColorScheme, referenceRamp?: ReferenceRamp): Map<string, string> | undefined;
@@ -1 +1 @@
1
- export declare const version = "4.0.25";
1
+ export declare const version = "4.0.26";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/bandage-core",
3
- "version": "4.0.25",
3
+ "version": "4.0.26",
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": {