react-msaview 6.2.2 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/index.js +104 -104
- package/bundle/index.js.map +4 -4
- package/dist/components/MSAViewer.d.ts +10 -1
- package/dist/components/MSAViewer.js +3 -1
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/minimap/Minimap.js +1 -1
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.js +2 -2
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +4 -2
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +5 -4
- package/dist/components/msa/msaRaster.js +10 -6
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +31 -0
- package/dist/components/msa/renderHighlights.js +49 -0
- package/dist/components/msa/renderHighlights.js.map +1 -0
- package/dist/components/msa/renderMSAMouseover.d.ts +3 -1
- package/dist/components/msa/renderMSAMouseover.js +11 -1
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/tracks/drawTracks.js +4 -0
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +31 -0
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/exportFileName.d.ts +6 -0
- package/dist/exportFileName.js +15 -0
- package/dist/exportFileName.js.map +1 -0
- package/dist/headlessRenderEnv.d.ts +4 -1
- package/dist/headlessRenderEnv.js +6 -3
- package/dist/headlessRenderEnv.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +172 -9
- package/dist/model.js +134 -3
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.js +30 -6
- package/dist/renderToSvg.js.map +1 -1
- package/dist/svgTestUtil.d.ts +1662 -0
- package/dist/svgTestUtil.js +28 -0
- package/dist/svgTestUtil.js.map +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/umd.d.ts +1 -0
- package/dist/umd.js +3 -0
- package/dist/umd.js.map +1 -1
- package/dist/util.d.ts +1 -0
- package/dist/util.js +7 -0
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/columnTracks.test.ts +97 -0
- package/src/components/MSAViewer.tsx +13 -0
- package/src/components/minimap/Minimap.tsx +1 -1
- package/src/components/minimap/MinimapSVG.tsx +2 -2
- package/src/components/msa/MSAMouseoverCanvas.tsx +4 -2
- package/src/components/msa/msaRaster.ts +10 -6
- package/src/components/msa/renderHighlights.ts +86 -0
- package/src/components/msa/renderMSAMouseover.ts +14 -0
- package/src/components/tracks/drawTracks.ts +4 -0
- package/src/components/tree/renderTreeCanvas.ts +56 -0
- package/src/exportFileName.test.ts +40 -0
- package/src/exportFileName.ts +20 -0
- package/src/headlessRenderEnv.ts +20 -11
- package/src/highlights.test.ts +70 -0
- package/src/impgRender.test.tsx +2 -4
- package/src/index.ts +4 -0
- package/src/model.ts +162 -10
- package/src/renderColumnTracksSvg.test.tsx +108 -0
- package/src/renderDomainsSvg.test.tsx +25 -10
- package/src/renderHighlightsSvg.test.tsx +85 -0
- package/src/renderMinimapSvg.test.tsx +6 -13
- package/src/renderRasterSvg.test.tsx +21 -93
- package/src/renderSequenceLogoSvg.test.tsx +4 -17
- package/src/renderToSvg.test.tsx +7 -21
- package/src/renderToSvg.tsx +32 -6
- package/src/svgTestUtil.ts +40 -0
- package/src/types.ts +39 -0
- package/src/umd.ts +3 -0
- package/src/util.ts +8 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Shared setup for the tests that drive renderToSvg headlessly.
|
|
2
|
+
import { enableStaticRendering } from 'mobx-react';
|
|
3
|
+
import { installHeadlessRenderEnv } from './headlessRenderEnv.js';
|
|
4
|
+
import MSAModelF from './model.js';
|
|
5
|
+
export function installSvgTestEnv(contextExtras) {
|
|
6
|
+
enableStaticRendering(true);
|
|
7
|
+
installHeadlessRenderEnv(globalThis, contextExtras);
|
|
8
|
+
}
|
|
9
|
+
export function createTestModel(snapshot, width = 800) {
|
|
10
|
+
const model = MSAModelF().create({
|
|
11
|
+
type: 'MsaView',
|
|
12
|
+
msaFormat: 'fasta',
|
|
13
|
+
height: 400,
|
|
14
|
+
...snapshot,
|
|
15
|
+
});
|
|
16
|
+
model.setWidth(width);
|
|
17
|
+
return model;
|
|
18
|
+
}
|
|
19
|
+
// a protein alignment with no two neighbouring cells alike, so any color or
|
|
20
|
+
// letter landing in the wrong cell shows up
|
|
21
|
+
export function syntheticProteinMsa(rows, cols) {
|
|
22
|
+
const letters = 'ACDEFGHIKLMNPQRSTVWY';
|
|
23
|
+
return Array.from({ length: rows }, (_, r) => {
|
|
24
|
+
const seq = Array.from({ length: cols }, (_, c) => letters[(r * 7 + c * 3) % letters.length]).join('');
|
|
25
|
+
return `>seq${r}\n${seq}`;
|
|
26
|
+
}).join('\n');
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=svgTestUtil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svgTestUtil.js","sourceRoot":"","sources":["../src/svgTestUtil.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAElD,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,SAAS,MAAM,YAAY,CAAA;AAMlC,MAAM,UAAU,iBAAiB,CAC/B,aAAiD;IAEjD,qBAAqB,CAAC,IAAI,CAAC,CAAA;IAC3B,wBAAwB,CAAC,UAAU,EAAE,aAAa,CAAC,CAAA;AACrD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAAgC,EAAE,KAAK,GAAG,GAAG;IAC3E,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC,MAAM,CAAC;QAC/B,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,OAAO;QAClB,MAAM,EAAE,GAAG;QACX,GAAG,QAAQ;KACZ,CAAC,CAAA;IACF,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IACrB,OAAO,KAAK,CAAA;AACd,CAAC;AAED,4EAA4E;AAC5E,4CAA4C;AAC5C,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,IAAY;IAC5D,MAAM,OAAO,GAAG,sBAAsB,CAAA;IACtC,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC3C,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CACpB,EAAE,MAAM,EAAE,IAAI,EAAE,EAChB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CACpD,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACV,OAAO,OAAO,CAAC,KAAK,GAAG,EAAE,CAAA;IAC3B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,18 @@ export interface BasicTrack {
|
|
|
23
23
|
ReactComponent: React.FC<any>;
|
|
24
24
|
model: TextTrackModel & BarTrackModel;
|
|
25
25
|
}
|
|
26
|
+
export interface ColumnTrackSpec {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
kind: 'bar' | 'text';
|
|
30
|
+
values?: number[];
|
|
31
|
+
data?: string;
|
|
32
|
+
max?: number;
|
|
33
|
+
color?: string;
|
|
34
|
+
colors?: Record<string, string>;
|
|
35
|
+
row?: string;
|
|
36
|
+
height?: number;
|
|
37
|
+
}
|
|
26
38
|
export type { Annotation };
|
|
27
39
|
export type TidyDomainAnnotation = Annotation;
|
|
28
40
|
export interface DomainBand {
|
|
@@ -31,6 +43,27 @@ export interface DomainBand {
|
|
|
31
43
|
endCol: number;
|
|
32
44
|
stackIndex: number;
|
|
33
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* A persistent, labeled highlight. Coordinates are 1-based and inclusive, as
|
|
48
|
+
* GFF's are. `row` makes `start`/`end` residues of that row; without it they
|
|
49
|
+
* are alignment columns. `rows` marks whole rows instead.
|
|
50
|
+
*/
|
|
51
|
+
export interface Highlight {
|
|
52
|
+
row?: string;
|
|
53
|
+
rows?: string[];
|
|
54
|
+
start?: number;
|
|
55
|
+
end?: number;
|
|
56
|
+
label?: string;
|
|
57
|
+
color?: string;
|
|
58
|
+
}
|
|
59
|
+
/** a Highlight resolved to visible column indices and row indices */
|
|
60
|
+
export interface ResolvedHighlight {
|
|
61
|
+
startCol?: number;
|
|
62
|
+
endCol?: number;
|
|
63
|
+
rowIndices: number[];
|
|
64
|
+
label?: string;
|
|
65
|
+
color?: string;
|
|
66
|
+
}
|
|
34
67
|
export interface Node {
|
|
35
68
|
children?: Node[];
|
|
36
69
|
name?: string;
|
package/dist/umd.d.ts
CHANGED
package/dist/umd.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
// a host that swaps models (the R htmlwidget on re-render) has to destroy the
|
|
3
|
+
// old one, or the disposers its autoruns registered never run
|
|
4
|
+
export { destroy } from '@jbrowse/mobx-state-tree';
|
|
2
5
|
export { default as MSAView } from './components/Loading.js';
|
|
3
6
|
export { default as MSAModelF } from './model.js';
|
|
4
7
|
export * from 'react-dom/client';
|
package/dist/umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"umd.js","sourceRoot":"","sources":["../src/umd.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,yBAA0B,CAAA;AAC7D,OAAO,EAAqB,OAAO,IAAI,SAAS,EAAE,MAAM,YAAY,CAAA;AAEpE,cAAc,kBAAkB,CAAA;AAEhC,OAAO,EAAE,KAAK,EAAE,CAAA"}
|
|
1
|
+
{"version":3,"file":"umd.js","sourceRoot":"","sources":["../src/umd.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,8EAA8E;AAC9E,8DAA8D;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AAClD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,yBAA0B,CAAA;AAC7D,OAAO,EAAqB,OAAO,IAAI,SAAS,EAAE,MAAM,YAAY,CAAA;AAEpE,cAAc,kBAAkB,CAAA;AAEhC,OAAO,EAAE,KAAK,EAAE,CAAA"}
|
package/dist/util.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare function transform<T>(obj: Record<string, T>, cb: (arg0: [string,
|
|
|
6
6
|
export declare function colorContrast(colorScheme: Record<string, string>, theme: Theme): {
|
|
7
7
|
[k: string]: string;
|
|
8
8
|
};
|
|
9
|
+
export declare function dropBlanks<T>(blanks: number[], arr: T[]): T[];
|
|
9
10
|
export declare function skipBlanks(blanks: number[], str: string): string;
|
|
10
11
|
export declare function computeRowInsertions(blanks: number[], seq: string): {
|
|
11
12
|
pos: number;
|
package/dist/util.js
CHANGED
|
@@ -32,6 +32,13 @@ export function colorContrast(colorScheme, theme) {
|
|
|
32
32
|
theme.palette.getContrastText(colord(color).toHex()),
|
|
33
33
|
]);
|
|
34
34
|
}
|
|
35
|
+
export function dropBlanks(blanks, arr) {
|
|
36
|
+
if (blanks.length === 0) {
|
|
37
|
+
return arr;
|
|
38
|
+
}
|
|
39
|
+
const set = new Set(blanks);
|
|
40
|
+
return arr.filter((_, i) => !set.has(i));
|
|
41
|
+
}
|
|
35
42
|
export function skipBlanks(blanks, str) {
|
|
36
43
|
if (blanks.length === 0) {
|
|
37
44
|
return str;
|
package/dist/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,UAAU,MAAM,qBAAqB,CAAA;AAC5C,OAAO,WAAW,MAAM,sBAAsB,CAAA;AAI9C,MAAM,CAAC,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAA;AAEjC,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAkB,CAAA;AAErD,0EAA0E;AAC1E,uEAAuE;AACvE,8DAA8D;AAC9D,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAE,EAAU;IAC9D,MAAM,QAAQ,GAAG,GAAG,KAAK,IAAI,EAAE,EAAE,CAAA;IACjC,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAChD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,MAAM,CAAA;IACf,CAAC;IACD,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACrB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,GAAG,CAAA;QAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACjD,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACjD,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,CAAA;IACxB,mBAAmB,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACzC,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,GAAsB,EACtB,EAAsC;IAEtC,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;AACxD,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,WAAmC,EACnC,KAAY;IAEZ,OAAO,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;QACjD,MAAM;QACN,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;KACrD,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAI,MAAgB,EAAE,GAAQ;IACtD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAA;IAC3B,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;AAC1C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAgB,EAAE,GAAW;IACtD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,CAAA;IACjB,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,KAAK,MAAM,QAAQ,IAAI,MAAM,EAAE,CAAC;QAC9B,IAAI,QAAQ,GAAG,OAAO,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;QAC3C,CAAC;QACD,OAAO,GAAG,QAAQ,GAAG,CAAC,CAAA;IACxB,CAAC;IACD,IAAI,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;IACjC,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAgB,EAAE,GAAW;IAChE,MAAM,UAAU,GAAuC,EAAE,CAAA;IACzD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAA;IAC/B,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,IAAI,gBAAgB,GAAG,CAAC,CAAC,CAAA;IACzB,IAAI,WAAW,GAAa,EAAE,CAAA;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,QAAQ,GAAG,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACnD,oEAAoE;YACpE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC3C,IAAI,gBAAgB,KAAK,UAAU,EAAE,CAAC;oBACpC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAA;gBAC3B,CAAC;qBAAM,CAAC;oBACN,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC3B,UAAU,CAAC,IAAI,CAAC;4BACd,GAAG,EAAE,gBAAgB;4BACrB,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;yBAC9B,CAAC,CAAA;oBACJ,CAAC;oBACD,gBAAgB,GAAG,UAAU,CAAA;oBAC7B,WAAW,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAA;gBACzB,CAAC;YACH,CAAC;YACD,QAAQ,EAAE,CAAA;QACZ,CAAC;aAAM,CAAC;YACN,UAAU,EAAE,CAAA;QACd,CAAC;IACH,CAAC;IACD,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,UAAU,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3E,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,CAAiC;IACnD,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,CAAA;AACxB,CAAC;AAED,8CAA8C;AAC9C,oCAAoC;AACpC,MAAM,UAAU,OAAO,CAAC,CAAU;IAChC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAA;AAC/B,CAAC"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "6.
|
|
1
|
+
export declare const version = "6.3.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '6.
|
|
1
|
+
export const version = '6.3.0';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-msaview",
|
|
3
3
|
"author": "Colin",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.3.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"@gmod/newick": "^1.0.0",
|
|
44
44
|
"colord": "^2.9.3",
|
|
45
45
|
"flatbush": "^4.6.2",
|
|
46
|
-
"@jbrowse/svgcanvas": "6.
|
|
47
|
-
"msa-parsers": "6.
|
|
46
|
+
"@jbrowse/svgcanvas": "6.3.0",
|
|
47
|
+
"msa-parsers": "6.3.0"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"clean": "rimraf dist bundle",
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { getSnapshot } from '@jbrowse/mobx-state-tree'
|
|
2
|
+
import { expect, test } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import stateModelFactory from './model.ts'
|
|
5
|
+
|
|
6
|
+
import type { ColumnTrackSpec } from './types.ts'
|
|
7
|
+
|
|
8
|
+
const MsaView = stateModelFactory()
|
|
9
|
+
|
|
10
|
+
// s1 has a gap at column 1 and s2 a gap at column 3, and every row is gapped at
|
|
11
|
+
// column 5, so hiding gappy columns drops that one
|
|
12
|
+
const msa = '>s1\nA-CDE-F\n>s2\nAB-DE-F\n>s3\nABCDE-F\n'
|
|
13
|
+
|
|
14
|
+
function makeModel(columnTracks: ColumnTrackSpec[]) {
|
|
15
|
+
return MsaView.create({
|
|
16
|
+
type: 'MsaView',
|
|
17
|
+
msaFormat: 'fasta',
|
|
18
|
+
data: { msa },
|
|
19
|
+
columnTracks,
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test('column-indexed values normalize by max and clamp to 0..1', () => {
|
|
24
|
+
const model = makeModel([
|
|
25
|
+
{ id: 't', name: 'T', kind: 'bar', values: [1, 2, 4, -1], max: 2 },
|
|
26
|
+
])
|
|
27
|
+
expect(model.columnTrackContent.get('t')?.values).toEqual([0.5, 1, 1, 0])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('row-indexed values land on the residues of that row', () => {
|
|
31
|
+
const model = makeModel([
|
|
32
|
+
{ id: 't', name: 'T', kind: 'bar', values: [0.1, 0.2, 0.3], row: 's1' },
|
|
33
|
+
])
|
|
34
|
+
// residues 1..3 of s1 are A, C, D at global columns 0, 2, 3
|
|
35
|
+
expect(model.columnTrackContent.get('t')?.values).toEqual([
|
|
36
|
+
0.1, 0, 0.2, 0.3, 0, 0, 0,
|
|
37
|
+
])
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('a row-indexed text track spreads its letters the same way', () => {
|
|
41
|
+
const model = makeModel([
|
|
42
|
+
{ id: 'f', name: 'Frame', kind: 'text', data: '123', row: 's2' },
|
|
43
|
+
])
|
|
44
|
+
// residues of s2 sit at columns 0, 1, 3
|
|
45
|
+
expect(model.columnTrackContent.get('f')?.data).toBe('12 3 ')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('hiding gappy columns drops them from the values and the text', () => {
|
|
49
|
+
const model = makeModel([
|
|
50
|
+
{ id: 't', name: 'T', kind: 'bar', values: [1, 2, 3, 4, 5, 6, 7], max: 7 },
|
|
51
|
+
{ id: 'f', name: 'F', kind: 'text', data: 'abcdefg' },
|
|
52
|
+
])
|
|
53
|
+
model.setHideGaps(true)
|
|
54
|
+
model.setAllowedGappyness(99)
|
|
55
|
+
expect(model.blanks).toEqual([5])
|
|
56
|
+
expect(model.columnTrackContent.get('t')?.values).toHaveLength(6)
|
|
57
|
+
expect(model.columnTrackContent.get('f')?.data).toBe('abcdeg')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('data tracks join the track list with their own color and height', () => {
|
|
61
|
+
const model = makeModel([
|
|
62
|
+
{ id: 't', name: 'dN/dS', kind: 'bar', values: [1], color: 'red' },
|
|
63
|
+
{ id: 'f', name: 'Frame', kind: 'text', data: '1', colors: { 1: 'blue' } },
|
|
64
|
+
])
|
|
65
|
+
const ids = model.tracks.map(t => t.model.id)
|
|
66
|
+
expect(ids).toContain('t')
|
|
67
|
+
expect(ids).toContain('f')
|
|
68
|
+
const bar = model.tracks.find(t => t.model.id === 't')!.model
|
|
69
|
+
expect(bar.barColor).toBe('red')
|
|
70
|
+
expect(bar.height).toBe(model.conservationTrackHeight)
|
|
71
|
+
const text = model.tracks.find(t => t.model.id === 'f')!.model
|
|
72
|
+
expect(text.customColorScheme).toEqual({ 1: 'blue' })
|
|
73
|
+
expect(text.height).toBe(model.rowHeight)
|
|
74
|
+
expect(text.data).toBe('1')
|
|
75
|
+
|
|
76
|
+
expect(model.turnedOnTracks.map(t => t.model.id)).toContain('t')
|
|
77
|
+
model.toggleTrack('t')
|
|
78
|
+
expect(model.turnedOnTracks.map(t => t.model.id)).not.toContain('t')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('setColumnTracks replaces the set and the snapshot carries it', () => {
|
|
82
|
+
const model = makeModel([])
|
|
83
|
+
expect(getSnapshot(model)).not.toHaveProperty('columnTracks')
|
|
84
|
+
model.setColumnTracks([{ id: 't', name: 'T', kind: 'bar', values: [0.5] }])
|
|
85
|
+
expect(getSnapshot(model).columnTracks).toEqual([
|
|
86
|
+
{ id: 't', name: 'T', kind: 'bar', values: [0.5] },
|
|
87
|
+
])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('a track too large to share leaves the snapshot but keeps drawing', () => {
|
|
91
|
+
const model = makeModel([
|
|
92
|
+
{ id: 'big', name: 'Big', kind: 'bar', values: Array(20_000).fill(0.5) },
|
|
93
|
+
{ id: 'small', name: 'Small', kind: 'bar', values: [0.5] },
|
|
94
|
+
])
|
|
95
|
+
expect(model.columnTrackContent.get('big')?.values).toHaveLength(20_000)
|
|
96
|
+
expect(getSnapshot(model).columnTracks?.map(t => t.id)).toEqual(['small'])
|
|
97
|
+
})
|
|
@@ -7,6 +7,7 @@ import { ThemeProvider } from '@mui/material/styles'
|
|
|
7
7
|
import MSAModelF from '../model.ts'
|
|
8
8
|
import Loading from './Loading.tsx'
|
|
9
9
|
|
|
10
|
+
import type { ColumnTrackSpec, Highlight } from '../types.ts'
|
|
10
11
|
import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
|
|
11
12
|
|
|
12
13
|
const theme = createJBrowseTheme()
|
|
@@ -26,8 +27,16 @@ interface MSAViewerProps {
|
|
|
26
27
|
rowHeight?: number
|
|
27
28
|
/** alignment columns (0-based) to highlight with a persistent overlay */
|
|
28
29
|
highlightColumns?: number[]
|
|
30
|
+
/**
|
|
31
|
+
* labeled highlights in 1-based inclusive coordinates: `{start, end}` for
|
|
32
|
+
* alignment columns, `{row, start, end}` for residues of that row, `{rows}`
|
|
33
|
+
* for whole rows, each with an optional `label` and `color`
|
|
34
|
+
*/
|
|
35
|
+
highlights?: Highlight[]
|
|
29
36
|
/** row name to diff every other row against (matches render as ".") */
|
|
30
37
|
relativeTo?: string
|
|
38
|
+
/** tracks supplied as data: per-column bar values or a text row (see docs/layers.md) */
|
|
39
|
+
columnTracks?: ColumnTrackSpec[]
|
|
31
40
|
/** draw the phylogenetic tree (default true); false leaves a labels-only gutter */
|
|
32
41
|
drawTree?: boolean
|
|
33
42
|
/** fixed width (px) of the tree/label area */
|
|
@@ -48,7 +57,9 @@ export default function MSAViewer({
|
|
|
48
57
|
colWidth,
|
|
49
58
|
rowHeight,
|
|
50
59
|
highlightColumns,
|
|
60
|
+
highlights,
|
|
51
61
|
relativeTo,
|
|
62
|
+
columnTracks,
|
|
52
63
|
drawTree,
|
|
53
64
|
treeAreaWidth,
|
|
54
65
|
autoTreeAreaWidth,
|
|
@@ -69,7 +80,9 @@ export default function MSAViewer({
|
|
|
69
80
|
...(colWidth ? { colWidth } : {}),
|
|
70
81
|
...(rowHeight ? { rowHeight } : {}),
|
|
71
82
|
...(highlightColumns ? { highlightColumns } : {}),
|
|
83
|
+
...(highlights ? { highlights } : {}),
|
|
72
84
|
...(relativeTo ? { relativeTo } : {}),
|
|
85
|
+
...(columnTracks ? { columnTracks } : {}),
|
|
73
86
|
...(drawTree !== undefined ? { drawTree } : {}),
|
|
74
87
|
...(treeAreaWidth ? { treeAreaWidth } : {}),
|
|
75
88
|
...(autoTreeAreaWidth ? { autoTreeAreaWidth } : {}),
|
|
@@ -76,7 +76,7 @@ const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
76
76
|
width: msaCanvasWidth,
|
|
77
77
|
height: MINIMAP_BAR_HEIGHT,
|
|
78
78
|
boxSizing: 'border-box',
|
|
79
|
-
border:
|
|
79
|
+
border: `1px solid ${theme.palette.text.secondary}`,
|
|
80
80
|
}}
|
|
81
81
|
/>
|
|
82
82
|
<DragHandle
|
|
@@ -42,7 +42,7 @@ export default function MinimapSVG({
|
|
|
42
42
|
y={0}
|
|
43
43
|
width={msaCanvasWidth}
|
|
44
44
|
height={MINIMAP_BAR_HEIGHT}
|
|
45
|
-
stroke=
|
|
45
|
+
stroke={theme.palette.text.secondary}
|
|
46
46
|
fill="none"
|
|
47
47
|
/>
|
|
48
48
|
<rect
|
|
@@ -51,7 +51,7 @@ export default function MinimapSVG({
|
|
|
51
51
|
width={w}
|
|
52
52
|
height={MINIMAP_BAR_HEIGHT}
|
|
53
53
|
fill={scrollbarThumbFill}
|
|
54
|
-
stroke=
|
|
54
|
+
stroke={theme.palette.text.secondary}
|
|
55
55
|
/>
|
|
56
56
|
<g transform={`translate(0 ${MINIMAP_BAR_HEIGHT})`}>
|
|
57
57
|
<polygon fill={scrollbarThumbFill} points={polygonPoints} />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
3
|
import { isAlive } from '@jbrowse/mobx-state-tree'
|
|
4
|
+
import { useTheme } from '@mui/material'
|
|
4
5
|
import { observer } from 'mobx-react'
|
|
5
6
|
|
|
6
7
|
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
@@ -14,17 +15,18 @@ const MSAMouseoverCanvas = observer(function ({
|
|
|
14
15
|
model: MsaViewModel
|
|
15
16
|
}) {
|
|
16
17
|
const { height, msaCanvasWidth: width, highResScaleFactor } = model
|
|
18
|
+
const theme = useTheme()
|
|
17
19
|
const canvasWidth = width * highResScaleFactor
|
|
18
20
|
const canvasHeight = height * highResScaleFactor
|
|
19
21
|
const ref = useCanvasAutorun({
|
|
20
22
|
draw: ctx => {
|
|
21
23
|
if (isAlive(model)) {
|
|
22
|
-
renderMouseover({ ctx, model })
|
|
24
|
+
renderMouseover({ ctx, model, theme })
|
|
23
25
|
}
|
|
24
26
|
},
|
|
25
27
|
width: canvasWidth,
|
|
26
28
|
height: canvasHeight,
|
|
27
|
-
deps: [model],
|
|
29
|
+
deps: [model, theme],
|
|
28
30
|
})
|
|
29
31
|
|
|
30
32
|
return (
|
|
@@ -320,20 +320,24 @@ export function drawMsaRaster({
|
|
|
320
320
|
/**
|
|
321
321
|
* A raster canvas as a PNG data URI, for embedding in the SVG export.
|
|
322
322
|
*
|
|
323
|
-
* toDataURL is the synchronous read-back
|
|
323
|
+
* toDataURL is the synchronous read-back. The DOM canvas has it, and so does a
|
|
324
|
+
* node canvas standing in for OffscreenCanvas headlessly; a browser
|
|
324
325
|
* OffscreenCanvas -- which is what the tile and thumbnail caches hold wherever
|
|
325
|
-
* it exists -- gets copied onto
|
|
326
|
-
* read-back is unavailable (jsdom) rather than throwing, which is the
|
|
327
|
-
* fall back to drawing the thing in vector.
|
|
326
|
+
* it exists -- gets copied onto a DOM canvas first. Returns undefined wherever
|
|
327
|
+
* the read-back is unavailable (jsdom) rather than throwing, which is the
|
|
328
|
+
* signal to fall back to drawing the thing in vector.
|
|
328
329
|
*/
|
|
329
330
|
export function canvasHref(canvas: RasterCanvas | undefined) {
|
|
330
|
-
if (!canvas
|
|
331
|
+
if (!canvas) {
|
|
331
332
|
return undefined
|
|
332
333
|
}
|
|
333
334
|
try {
|
|
334
|
-
if (canvas
|
|
335
|
+
if ('toDataURL' in canvas && typeof canvas.toDataURL === 'function') {
|
|
335
336
|
return canvas.toDataURL('image/png')
|
|
336
337
|
}
|
|
338
|
+
if (typeof document === 'undefined') {
|
|
339
|
+
return undefined
|
|
340
|
+
}
|
|
337
341
|
const out = document.createElement('canvas')
|
|
338
342
|
out.width = canvas.width
|
|
339
343
|
out.height = canvas.height
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { setFontSize } from '../../setFontSize.ts'
|
|
2
|
+
|
|
3
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
4
|
+
import type { RenderCtx } from '../renderCtx.ts'
|
|
5
|
+
import type { Theme } from '@mui/material'
|
|
6
|
+
|
|
7
|
+
export const highlightFill = 'rgba(255,140,0,0.28)'
|
|
8
|
+
export const highlightBorder = 'rgba(210,90,0,0.95)'
|
|
9
|
+
export const highlightRowFill = 'rgba(255,140,0,0.18)'
|
|
10
|
+
|
|
11
|
+
export const labelFontSize = 11
|
|
12
|
+
const labelPad = 3
|
|
13
|
+
|
|
14
|
+
export function drawHighlightLabel({
|
|
15
|
+
ctx,
|
|
16
|
+
theme,
|
|
17
|
+
label,
|
|
18
|
+
x,
|
|
19
|
+
y,
|
|
20
|
+
spanWidth,
|
|
21
|
+
}: {
|
|
22
|
+
ctx: RenderCtx
|
|
23
|
+
theme: Theme
|
|
24
|
+
label: string
|
|
25
|
+
x: number
|
|
26
|
+
y: number
|
|
27
|
+
spanWidth: number
|
|
28
|
+
}) {
|
|
29
|
+
setFontSize(ctx, labelFontSize)
|
|
30
|
+
ctx.textAlign = 'start'
|
|
31
|
+
const boxWidth = ctx.measureText(label).width + labelPad * 2
|
|
32
|
+
const boxHeight = labelFontSize + labelPad * 2
|
|
33
|
+
const left = boxWidth <= spanWidth ? x : x + spanWidth + 2
|
|
34
|
+
ctx.fillStyle = theme.palette.background.paper
|
|
35
|
+
ctx.fillRect(left, y, boxWidth, boxHeight)
|
|
36
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
37
|
+
ctx.fillText(label, left + labelPad, y + labelPad + labelFontSize - 2)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The persistent `highlights` overlay on the alignment: row tints, bordered
|
|
42
|
+
* column bands, and a label above each band. `offsetX`/`offsetY` are the
|
|
43
|
+
* content coordinates at the canvas origin, the convention renderMSABlock
|
|
44
|
+
* uses, so the live overlay passes -scrollX/-scrollY and the export passes its
|
|
45
|
+
* layout offsets.
|
|
46
|
+
*/
|
|
47
|
+
export function renderHighlights({
|
|
48
|
+
ctx,
|
|
49
|
+
model,
|
|
50
|
+
theme,
|
|
51
|
+
offsetX,
|
|
52
|
+
offsetY,
|
|
53
|
+
width,
|
|
54
|
+
height,
|
|
55
|
+
}: {
|
|
56
|
+
ctx: RenderCtx
|
|
57
|
+
model: MsaViewModel
|
|
58
|
+
theme: Theme
|
|
59
|
+
offsetX: number
|
|
60
|
+
offsetY: number
|
|
61
|
+
width: number
|
|
62
|
+
height: number
|
|
63
|
+
}) {
|
|
64
|
+
const { resolvedHighlights, colWidth, rowHeight } = model
|
|
65
|
+
ctx.lineWidth = 2
|
|
66
|
+
for (const { rowIndices, color } of resolvedHighlights) {
|
|
67
|
+
ctx.fillStyle = color ?? highlightRowFill
|
|
68
|
+
for (const index of rowIndices) {
|
|
69
|
+
ctx.fillRect(0, index * rowHeight - offsetY, width, rowHeight)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (const { startCol, endCol, label, color } of resolvedHighlights) {
|
|
73
|
+
if (startCol === undefined || endCol === undefined) {
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
const x = startCol * colWidth - offsetX
|
|
77
|
+
const spanWidth = (endCol - startCol + 1) * colWidth
|
|
78
|
+
ctx.fillStyle = color ?? highlightFill
|
|
79
|
+
ctx.fillRect(x, 0, spanWidth, height)
|
|
80
|
+
ctx.strokeStyle = color ?? highlightBorder
|
|
81
|
+
ctx.strokeRect(x, 0, spanWidth, height)
|
|
82
|
+
if (label) {
|
|
83
|
+
drawHighlightLabel({ ctx, theme, label, x, y: 0, spanWidth })
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
multiRowHoverColor,
|
|
5
5
|
referenceColor,
|
|
6
6
|
} from '../overlayColors.ts'
|
|
7
|
+
import { renderHighlights } from './renderHighlights.ts'
|
|
7
8
|
|
|
8
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
|
+
import type { Theme } from '@mui/material'
|
|
9
11
|
|
|
10
12
|
// the persistent highlightColumns overlay: a stronger fill plus a solid border
|
|
11
13
|
// so a domain/motif band reads clearly over the colored alignment cells (the
|
|
@@ -16,9 +18,11 @@ const highlightColumnsBorder = 'rgba(210,90,0,0.95)'
|
|
|
16
18
|
export function renderMouseover({
|
|
17
19
|
ctx,
|
|
18
20
|
model,
|
|
21
|
+
theme,
|
|
19
22
|
}: {
|
|
20
23
|
ctx: CanvasRenderingContext2D
|
|
21
24
|
model: MsaViewModel
|
|
25
|
+
theme: Theme
|
|
22
26
|
}) {
|
|
23
27
|
const {
|
|
24
28
|
mouseCol,
|
|
@@ -72,6 +76,16 @@ export function renderMouseover({
|
|
|
72
76
|
ctx.strokeRect(x, 0, w, height)
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
renderHighlights({
|
|
80
|
+
ctx,
|
|
81
|
+
model,
|
|
82
|
+
theme,
|
|
83
|
+
offsetX: -scrollX,
|
|
84
|
+
offsetY: -scrollY,
|
|
85
|
+
width,
|
|
86
|
+
height,
|
|
87
|
+
})
|
|
88
|
+
|
|
75
89
|
ctx.fillStyle = hoverColor
|
|
76
90
|
if (mouseCol !== undefined) {
|
|
77
91
|
colBand(mouseCol)
|
|
@@ -43,6 +43,10 @@ export function drawConservationBars({
|
|
|
43
43
|
// the bar-track values live on model getters (kept off the plain track object so
|
|
44
44
|
// the live canvas autorun stays reactive); select the right one by track id
|
|
45
45
|
export function barTrackValues(model: MsaViewModel, trackId: string) {
|
|
46
|
+
const supplied = model.columnTrackContent.get(trackId)?.values
|
|
47
|
+
if (supplied) {
|
|
48
|
+
return supplied
|
|
49
|
+
}
|
|
46
50
|
return trackId === 'property-conservation'
|
|
47
51
|
? model.propertyConservation
|
|
48
52
|
: model.conservation
|
|
@@ -5,6 +5,10 @@ import {
|
|
|
5
5
|
} from '../../hierarchy.ts'
|
|
6
6
|
import { setFontSize } from '../../setFontSize.ts'
|
|
7
7
|
import { getVisibleLeaves } from '../getVisibleLeaves.ts'
|
|
8
|
+
import {
|
|
9
|
+
drawHighlightLabel,
|
|
10
|
+
highlightRowFill,
|
|
11
|
+
} from '../msa/renderHighlights.ts'
|
|
8
12
|
|
|
9
13
|
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
10
14
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -349,6 +353,55 @@ function renderTreeLabels({
|
|
|
349
353
|
ctx.setLineDash([])
|
|
350
354
|
}
|
|
351
355
|
|
|
356
|
+
// the row sets of `highlights`, tinted across the tree area under the labels
|
|
357
|
+
// with the label in the gutter at the first row of each set
|
|
358
|
+
function renderRowHighlights({
|
|
359
|
+
ctx,
|
|
360
|
+
model,
|
|
361
|
+
theme,
|
|
362
|
+
offsetY,
|
|
363
|
+
blockSizeYOverride,
|
|
364
|
+
}: {
|
|
365
|
+
ctx: RenderCtx
|
|
366
|
+
model: MsaViewModel
|
|
367
|
+
theme: Theme
|
|
368
|
+
offsetY: number
|
|
369
|
+
blockSizeYOverride?: number
|
|
370
|
+
}) {
|
|
371
|
+
const {
|
|
372
|
+
resolvedHighlights,
|
|
373
|
+
rowHeight,
|
|
374
|
+
treeAreaWidth,
|
|
375
|
+
marginLeft,
|
|
376
|
+
blockSize,
|
|
377
|
+
} = model
|
|
378
|
+
const by = blockSizeYOverride ?? blockSize
|
|
379
|
+
for (const { rowIndices, label, color } of resolvedHighlights) {
|
|
380
|
+
ctx.fillStyle = color ?? highlightRowFill
|
|
381
|
+
for (const index of rowIndices) {
|
|
382
|
+
const y = index * rowHeight
|
|
383
|
+
if (inYBlock(y + rowHeight / 2, offsetY, by)) {
|
|
384
|
+
ctx.fillRect(-marginLeft, y, treeAreaWidth, rowHeight)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const first = Math.min(...rowIndices)
|
|
388
|
+
if (
|
|
389
|
+
label &&
|
|
390
|
+
rowIndices.length &&
|
|
391
|
+
inYBlock(first * rowHeight, offsetY, by)
|
|
392
|
+
) {
|
|
393
|
+
drawHighlightLabel({
|
|
394
|
+
ctx,
|
|
395
|
+
theme,
|
|
396
|
+
label,
|
|
397
|
+
x: -marginLeft + 2,
|
|
398
|
+
y: first * rowHeight,
|
|
399
|
+
spanWidth: treeAreaWidth,
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
352
405
|
export function renderTreeCanvas({
|
|
353
406
|
model,
|
|
354
407
|
clickMap,
|
|
@@ -403,6 +456,9 @@ export function renderTreeCanvas({
|
|
|
403
456
|
// every block of every redraw, and `collapsed` is a list
|
|
404
457
|
const collapsedSet = new Set(model.collapsed)
|
|
405
458
|
|
|
459
|
+
renderRowHighlights({ ctx, model, theme, offsetY, blockSizeYOverride })
|
|
460
|
+
setFontSize(ctx, fontSize)
|
|
461
|
+
|
|
406
462
|
if (!noTree && drawTree) {
|
|
407
463
|
renderTree({
|
|
408
464
|
ctx,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { exportFileName } from './exportFileName.ts'
|
|
4
|
+
|
|
5
|
+
test('takes the alignment file name and swaps the extension', () => {
|
|
6
|
+
expect(
|
|
7
|
+
exportFileName(
|
|
8
|
+
{
|
|
9
|
+
uri: 'https://example.org/data/PF00069.stock?dl=1',
|
|
10
|
+
locationType: 'UriLocation',
|
|
11
|
+
},
|
|
12
|
+
'svg',
|
|
13
|
+
),
|
|
14
|
+
).toBe('PF00069.svg')
|
|
15
|
+
expect(
|
|
16
|
+
exportFileName(
|
|
17
|
+
{
|
|
18
|
+
localPath: 'C:\\aligns\\hemoglobin.fa',
|
|
19
|
+
locationType: 'LocalPathLocation',
|
|
20
|
+
},
|
|
21
|
+
'svg',
|
|
22
|
+
),
|
|
23
|
+
).toBe('hemoglobin.svg')
|
|
24
|
+
expect(
|
|
25
|
+
exportFileName(
|
|
26
|
+
{ blobId: 'b1', name: 'pasted.aln', locationType: 'BlobLocation' },
|
|
27
|
+
'svg',
|
|
28
|
+
),
|
|
29
|
+
).toBe('pasted.svg')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('falls back to a plain name without a file', () => {
|
|
33
|
+
expect(exportFileName(undefined, 'svg')).toBe('image.svg')
|
|
34
|
+
expect(
|
|
35
|
+
exportFileName(
|
|
36
|
+
{ uri: 'https://example.org/', locationType: 'UriLocation' },
|
|
37
|
+
'svg',
|
|
38
|
+
),
|
|
39
|
+
).toBe('image.svg')
|
|
40
|
+
})
|