jbrowse-plugin-mafviewer 1.0.2 → 1.0.4
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/CHANGELOG.md +3 -0
- package/README.md +183 -0
- package/dist/jbrowse-plugin-mafviewer.umd.development.js +239 -136
- package/dist/jbrowse-plugin-mafviewer.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +1 -1
- package/package.json +8 -10
- package/src/BigMafAdapter/BigMafAdapter.ts +8 -18
- package/src/LinearMafDisplay/components/ColorLegend.tsx +27 -28
- package/src/LinearMafDisplay/components/ReactComponent.tsx +4 -1
- package/src/LinearMafDisplay/components/SetRowHeight.tsx +76 -0
- package/src/LinearMafDisplay/components/YScaleBars.tsx +7 -3
- package/src/LinearMafDisplay/configSchema.ts +2 -3
- package/src/LinearMafDisplay/renderSvg.tsx +13 -4
- package/src/LinearMafDisplay/stateModel.ts +83 -20
- package/src/LinearMafRenderer/LinearMafRenderer.ts +110 -50
- package/src/LinearMafRenderer/components/ReactComponent.tsx +4 -1
- package/src/MafAddTrackWorkflow/AddTrackWorkflow.tsx +1 -0
- package/src/MafTabixAdapter/MafTabixAdapter.ts +11 -20
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@jbrowse/core/Plugin'), require('@jbrowse/core/pluggableElementTypes'), require('@jbrowse/core/configuration'), require('@jbrowse/core/data_adapters/BaseAdapter'), require('mobx-state-tree'), require('@jbrowse/core/util'), require('@jbrowse/core/util/rxjs'), require('react'), require('mobx-react'), require('@
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', '@jbrowse/core/Plugin', '@jbrowse/core/pluggableElementTypes', '@jbrowse/core/configuration', '@jbrowse/core/data_adapters/BaseAdapter', 'mobx-state-tree', '@jbrowse/core/util', '@jbrowse/core/util/rxjs', 'react', 'mobx-react', '@
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.JBrowsePluginMafViewer = {}, global.JBrowseExports["@jbrowse/core/Plugin"], global.JBrowseExports["@jbrowse/core/pluggableElementTypes"], global.JBrowseExports["@jbrowse/core/configuration"], global.JBrowseExports["@jbrowse/core/data_adapters/BaseAdapter"], global.JBrowseExports["mobx-state-tree"], global.JBrowseExports["@jbrowse/core/util"], global.JBrowseExports["@jbrowse/core/util/rxjs"], global.JBrowseExports.react, global.JBrowseExports["mobx-react"], global.JBrowseExports["@
|
|
5
|
-
})(this, (function (exports, Plugin, pluggableElementTypes, configuration, BaseAdapter, mobxStateTree, util, rxjs, React, mobxReact,
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@jbrowse/core/Plugin'), require('@jbrowse/core/pluggableElementTypes'), require('@jbrowse/core/configuration'), require('@jbrowse/core/data_adapters/BaseAdapter'), require('mobx-state-tree'), require('@jbrowse/core/util'), require('@jbrowse/core/util/rxjs'), require('react'), require('mobx-react'), require('@mui/material'), require('@jbrowse/core/ui'), require('tss-react/mui')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', '@jbrowse/core/Plugin', '@jbrowse/core/pluggableElementTypes', '@jbrowse/core/configuration', '@jbrowse/core/data_adapters/BaseAdapter', 'mobx-state-tree', '@jbrowse/core/util', '@jbrowse/core/util/rxjs', 'react', 'mobx-react', '@mui/material', '@jbrowse/core/ui', 'tss-react/mui'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.JBrowsePluginMafViewer = {}, global.JBrowseExports["@jbrowse/core/Plugin"], global.JBrowseExports["@jbrowse/core/pluggableElementTypes"], global.JBrowseExports["@jbrowse/core/configuration"], global.JBrowseExports["@jbrowse/core/data_adapters/BaseAdapter"], global.JBrowseExports["mobx-state-tree"], global.JBrowseExports["@jbrowse/core/util"], global.JBrowseExports["@jbrowse/core/util/rxjs"], global.JBrowseExports.react, global.JBrowseExports["mobx-react"], global.JBrowseExports["@mui/material"], global.JBrowseExports["@jbrowse/core/ui"], global.JBrowseExports["tss-react/mui"]));
|
|
5
|
+
})(this, (function (exports, Plugin, pluggableElementTypes, configuration, BaseAdapter, mobxStateTree, util, rxjs, React, mobxReact, material, ui, mui) { 'use strict';
|
|
6
6
|
|
|
7
|
-
var version = "1.0.
|
|
7
|
+
var version = "1.0.4";
|
|
8
8
|
|
|
9
9
|
const configSchema$2 = configuration.ConfigurationSchema('BigMafAdapter', {
|
|
10
10
|
/**
|
|
@@ -649,28 +649,19 @@
|
|
|
649
649
|
const alignments = {};
|
|
650
650
|
const blocks2 = [];
|
|
651
651
|
for (const block of blocks) {
|
|
652
|
-
if (block
|
|
653
|
-
if (
|
|
654
|
-
|
|
655
|
-
alns.push(aln);
|
|
652
|
+
if (block.startsWith('s')) {
|
|
653
|
+
if (aln) {
|
|
654
|
+
alns.push(block.split(/ +/)[6]);
|
|
656
655
|
blocks2.push(block);
|
|
657
656
|
}
|
|
658
657
|
else {
|
|
659
|
-
|
|
658
|
+
aln = block.split(/ +/)[6];
|
|
659
|
+
alns.push(aln);
|
|
660
660
|
blocks2.push(block);
|
|
661
661
|
}
|
|
662
662
|
}
|
|
663
663
|
}
|
|
664
|
-
|
|
665
|
-
if (aln) {
|
|
666
|
-
for (let i = 0; i < aln?.length; i++) {
|
|
667
|
-
if (aln[i] !== '-') {
|
|
668
|
-
for (let j = 0; j < alns.length; j++) {
|
|
669
|
-
alns2[j] += alns[j][i];
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
664
|
+
// eslint-disable-next-line unicorn/no-for-loop
|
|
674
665
|
for (let i = 0; i < blocks2.length; i++) {
|
|
675
666
|
const elt = blocks2[i];
|
|
676
667
|
const ad = elt.split(/ +/);
|
|
@@ -683,7 +674,7 @@
|
|
|
683
674
|
srcSize: +ad[2],
|
|
684
675
|
strand: ad[3] === '+' ? 1 : -1,
|
|
685
676
|
unknown: +ad[4],
|
|
686
|
-
data:
|
|
677
|
+
data: alns[i],
|
|
687
678
|
};
|
|
688
679
|
}
|
|
689
680
|
observer.next(new util.SimpleFeature({
|
|
@@ -692,7 +683,7 @@
|
|
|
692
683
|
start: feature.get('start'),
|
|
693
684
|
end: feature.get('end'),
|
|
694
685
|
refName: feature.get('refName'),
|
|
695
|
-
seq:
|
|
686
|
+
seq: alns[0],
|
|
696
687
|
alignments: alignments,
|
|
697
688
|
},
|
|
698
689
|
}));
|
|
@@ -738,19 +729,42 @@
|
|
|
738
729
|
|
|
739
730
|
function configSchemaF(pluginManager) {
|
|
740
731
|
const LinearGenomePlugin = pluginManager.getPlugin('LinearGenomeViewPlugin');
|
|
741
|
-
|
|
742
|
-
const { linearBasicDisplayConfigSchemaFactory } = LinearGenomePlugin.exports;
|
|
732
|
+
const { baseLinearDisplayConfigSchema } = LinearGenomePlugin.exports;
|
|
743
733
|
return configuration.ConfigurationSchema('LinearMafDisplay', {
|
|
744
734
|
/**
|
|
745
735
|
* #slot
|
|
746
736
|
*/
|
|
747
737
|
renderer: pluginManager.pluggableConfigSchemaType('renderer'),
|
|
748
738
|
}, {
|
|
749
|
-
baseConfiguration:
|
|
739
|
+
baseConfiguration: baseLinearDisplayConfigSchema,
|
|
750
740
|
explicitlyTyped: true,
|
|
751
741
|
});
|
|
752
742
|
}
|
|
753
743
|
|
|
744
|
+
const useStyles$1 = mui.makeStyles()({
|
|
745
|
+
root: {
|
|
746
|
+
width: 500,
|
|
747
|
+
},
|
|
748
|
+
});
|
|
749
|
+
const SetRowHeightDialog = mobxReact.observer(function (props) {
|
|
750
|
+
const { model, handleClose } = props;
|
|
751
|
+
const { classes } = useStyles$1();
|
|
752
|
+
const [rowHeight, setRowHeight] = React.useState(`${model.rowHeight}`);
|
|
753
|
+
const [rowProportion, setRowProportion] = React.useState(`${model.rowProportion}`);
|
|
754
|
+
return (React.createElement(ui.Dialog, { open: true, onClose: handleClose, title: "Filter options" },
|
|
755
|
+
React.createElement(material.DialogContent, { className: classes.root },
|
|
756
|
+
React.createElement(material.Typography, null, "Set row height and the proportion of the row height to use for drawing each row"),
|
|
757
|
+
React.createElement(material.TextField, { value: rowHeight, onChange: event => setRowHeight(event.target.value), placeholder: "Enter row height" }),
|
|
758
|
+
React.createElement(material.TextField, { value: rowProportion, onChange: event => setRowProportion(event.target.value), placeholder: "Enter row proportion" }),
|
|
759
|
+
React.createElement(material.DialogActions, null,
|
|
760
|
+
React.createElement(material.Button, { variant: "contained", color: "primary", type: "submit", autoFocus: true, onClick: () => {
|
|
761
|
+
model.setRowProportion(+rowProportion);
|
|
762
|
+
model.setRowHeight(+rowHeight);
|
|
763
|
+
handleClose();
|
|
764
|
+
} }, "Submit"),
|
|
765
|
+
React.createElement(material.Button, { variant: "contained", color: "secondary", onClick: () => handleClose() }, "Cancel")))));
|
|
766
|
+
});
|
|
767
|
+
|
|
754
768
|
function isStrs(array) {
|
|
755
769
|
return typeof array[0] === 'string';
|
|
756
770
|
}
|
|
@@ -760,10 +774,9 @@
|
|
|
760
774
|
*/
|
|
761
775
|
function stateModelFactory(configSchema, pluginManager) {
|
|
762
776
|
const LinearGenomePlugin = pluginManager.getPlugin('LinearGenomeViewPlugin');
|
|
763
|
-
|
|
764
|
-
const { linearBasicDisplayModelFactory } = LinearGenomePlugin.exports;
|
|
777
|
+
const { BaseLinearDisplay } = LinearGenomePlugin.exports;
|
|
765
778
|
return mobxStateTree.types
|
|
766
|
-
.compose('LinearMafDisplay',
|
|
779
|
+
.compose('LinearMafDisplay', BaseLinearDisplay, mobxStateTree.types.model({
|
|
767
780
|
/**
|
|
768
781
|
* #property
|
|
769
782
|
*/
|
|
@@ -772,28 +785,51 @@
|
|
|
772
785
|
* #property
|
|
773
786
|
*/
|
|
774
787
|
configuration: configuration.ConfigurationReference(configSchema),
|
|
788
|
+
/**
|
|
789
|
+
* #property
|
|
790
|
+
*/
|
|
791
|
+
rowHeight: 15,
|
|
792
|
+
/**
|
|
793
|
+
* #property
|
|
794
|
+
*/
|
|
795
|
+
rowProportion: 0.8,
|
|
796
|
+
/**
|
|
797
|
+
* #property
|
|
798
|
+
*/
|
|
799
|
+
showAllLetters: false,
|
|
775
800
|
}))
|
|
776
801
|
.volatile(() => ({
|
|
777
802
|
prefersOffset: true,
|
|
778
803
|
}))
|
|
779
|
-
.
|
|
804
|
+
.actions(self => ({
|
|
780
805
|
/**
|
|
781
|
-
* #
|
|
806
|
+
* #action
|
|
782
807
|
*/
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
808
|
+
setRowHeight(n) {
|
|
809
|
+
self.rowHeight = n;
|
|
810
|
+
},
|
|
811
|
+
/**
|
|
812
|
+
* #action
|
|
813
|
+
*/
|
|
814
|
+
setRowProportion(n) {
|
|
815
|
+
self.rowProportion = n;
|
|
816
|
+
},
|
|
817
|
+
/**
|
|
818
|
+
* #action
|
|
819
|
+
*/
|
|
820
|
+
setShowAllLetters(f) {
|
|
821
|
+
self.showAllLetters = f;
|
|
791
822
|
},
|
|
823
|
+
}))
|
|
824
|
+
.views(self => ({
|
|
792
825
|
/**
|
|
793
826
|
* #getter
|
|
794
827
|
*/
|
|
795
|
-
get
|
|
796
|
-
|
|
828
|
+
get samples() {
|
|
829
|
+
const r = self.adapterConfig.samples;
|
|
830
|
+
return isStrs(r)
|
|
831
|
+
? r.map(elt => ({ id: elt, label: elt, color: undefined }))
|
|
832
|
+
: r;
|
|
797
833
|
},
|
|
798
834
|
/**
|
|
799
835
|
* #getter
|
|
@@ -814,21 +850,51 @@
|
|
|
814
850
|
},
|
|
815
851
|
}))
|
|
816
852
|
.views(self => {
|
|
817
|
-
const { renderProps: superRenderProps } = self;
|
|
853
|
+
const { trackMenuItems: superTrackMenuItems, renderProps: superRenderProps, } = self;
|
|
818
854
|
return {
|
|
819
855
|
/**
|
|
820
856
|
* #method
|
|
821
857
|
*/
|
|
822
858
|
renderProps() {
|
|
859
|
+
const { showAllLetters, rendererConfig, samples, rowHeight, rowProportion, } = self;
|
|
823
860
|
return {
|
|
824
861
|
...superRenderProps(),
|
|
825
|
-
|
|
826
|
-
|
|
862
|
+
config: rendererConfig,
|
|
863
|
+
samples,
|
|
864
|
+
rowHeight,
|
|
865
|
+
rowProportion,
|
|
866
|
+
showAllLetters,
|
|
827
867
|
};
|
|
828
868
|
},
|
|
869
|
+
/**
|
|
870
|
+
* #method
|
|
871
|
+
*/
|
|
872
|
+
trackMenuItems() {
|
|
873
|
+
return [
|
|
874
|
+
...superTrackMenuItems(),
|
|
875
|
+
{
|
|
876
|
+
label: 'Set row height',
|
|
877
|
+
onClick: () => {
|
|
878
|
+
util.getSession(self).queueDialog(handleClose => [
|
|
879
|
+
SetRowHeightDialog,
|
|
880
|
+
{ model: self, handleClose },
|
|
881
|
+
]);
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
label: 'Show all letters',
|
|
886
|
+
type: 'checkbox',
|
|
887
|
+
checked: self.showAllLetters,
|
|
888
|
+
onClick: () => {
|
|
889
|
+
self.setShowAllLetters(!self.showAllLetters);
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
];
|
|
893
|
+
},
|
|
829
894
|
};
|
|
830
895
|
})
|
|
831
896
|
.actions(self => {
|
|
897
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
832
898
|
const { renderSvg: superRenderSvg } = self;
|
|
833
899
|
return {
|
|
834
900
|
/**
|
|
@@ -836,7 +902,6 @@
|
|
|
836
902
|
*/
|
|
837
903
|
async renderSvg(opts) {
|
|
838
904
|
const { renderSvg } = await Promise.resolve().then(function () { return renderSvg$1; });
|
|
839
|
-
// @ts-expect-error
|
|
840
905
|
return renderSvg(self, opts, superRenderSvg);
|
|
841
906
|
},
|
|
842
907
|
};
|
|
@@ -848,19 +913,15 @@
|
|
|
848
913
|
return React.createElement("rect", { ...props, fill: color });
|
|
849
914
|
};
|
|
850
915
|
|
|
851
|
-
const ColorLegend = mobxReact.observer(function ({ model, labelWidth, }) {
|
|
916
|
+
const ColorLegend = mobxReact.observer(function ({ model, labelWidth, svgFontSize, }) {
|
|
852
917
|
const { samples, rowHeight } = model;
|
|
853
|
-
const
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
return (React.createElement(React.Fragment, { key: `${sample.id}-${idx}` },
|
|
861
|
-
React.createElement(RectBg, { y: idx * rowHeight + 1, x: extraOffset, width: legendWidth, height: boxHeight, color: sample.color }),
|
|
862
|
-
canDisplayLabel ? (React.createElement("text", { y: idx * rowHeight + 14, x: extraOffset + colorBoxWidth + 2, fontSize: svgFontSize }, sample.label)) : null));
|
|
863
|
-
}))) : null;
|
|
918
|
+
const canDisplayLabel = rowHeight >= 10;
|
|
919
|
+
const boxHeight = Math.min(20, rowHeight);
|
|
920
|
+
return samples ? (React.createElement(React.Fragment, null,
|
|
921
|
+
samples.map((sample, idx) => (React.createElement(RectBg, { key: `${sample.id}-${idx}`, y: idx * rowHeight, x: 0, width: labelWidth + 5, height: boxHeight, color: sample.color }))),
|
|
922
|
+
canDisplayLabel
|
|
923
|
+
? samples.map((sample, idx) => (React.createElement("text", { key: `${sample.id}-${idx}`, y: idx * rowHeight + rowHeight / 2, dominantBaseline: "middle", x: 2, fontSize: svgFontSize }, sample.label)))
|
|
924
|
+
: null)) : null;
|
|
864
925
|
});
|
|
865
926
|
|
|
866
927
|
const Wrapper = mobxReact.observer(function ({ children, model, exportSVG, }) {
|
|
@@ -882,14 +943,14 @@
|
|
|
882
943
|
const YScaleBars = mobxReact.observer(function (props) {
|
|
883
944
|
const { model } = props;
|
|
884
945
|
const { rowHeight, samples } = model;
|
|
885
|
-
const svgFontSize = Math.min(rowHeight,
|
|
886
|
-
const canDisplayLabel = rowHeight
|
|
946
|
+
const svgFontSize = Math.min(Math.max(rowHeight, 10), 14);
|
|
947
|
+
const canDisplayLabel = rowHeight >= 10;
|
|
887
948
|
const minWidth = 20;
|
|
888
949
|
const labelWidth = Math.max(...(samples
|
|
889
950
|
.map(s => util.measureText(s.label, svgFontSize))
|
|
890
951
|
.map(width => (canDisplayLabel ? width : minWidth)) || [0]));
|
|
891
952
|
return (React.createElement(Wrapper, { ...props },
|
|
892
|
-
React.createElement(ColorLegend, { model: model, labelWidth: labelWidth })));
|
|
953
|
+
React.createElement(ColorLegend, { model: model, labelWidth: labelWidth, svgFontSize: svgFontSize })));
|
|
893
954
|
});
|
|
894
955
|
|
|
895
956
|
const LinearMafDisplay = mobxReact.observer(function (props) {
|
|
@@ -925,20 +986,6 @@
|
|
|
925
986
|
explicitlyTyped: true,
|
|
926
987
|
});
|
|
927
988
|
|
|
928
|
-
function getCorrectionFactor(scale) {
|
|
929
|
-
if (scale >= 1) {
|
|
930
|
-
return 0.6;
|
|
931
|
-
}
|
|
932
|
-
else if (scale >= 0.2) {
|
|
933
|
-
return 0.05;
|
|
934
|
-
}
|
|
935
|
-
else if (scale >= 0.02) {
|
|
936
|
-
return 0.03;
|
|
937
|
-
}
|
|
938
|
-
else {
|
|
939
|
-
return 0.02;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
989
|
function getContrastBaseMap(theme) {
|
|
943
990
|
return Object.fromEntries(Object.entries(getColorBaseMap(theme)).map(([key, value]) => [
|
|
944
991
|
key,
|
|
@@ -955,16 +1002,19 @@
|
|
|
955
1002
|
};
|
|
956
1003
|
}
|
|
957
1004
|
function makeImageData({ ctx, renderArgs, }) {
|
|
958
|
-
const { regions, bpPerPx, rowHeight, theme: configTheme, samples, } = renderArgs;
|
|
1005
|
+
const { regions, bpPerPx, rowHeight, showAllLetters, theme: configTheme, samples, rowProportion, } = renderArgs;
|
|
959
1006
|
const [region] = regions;
|
|
960
1007
|
const features = renderArgs.features;
|
|
961
|
-
const h = rowHeight;
|
|
1008
|
+
const h = rowHeight * rowProportion;
|
|
962
1009
|
const theme = ui.createJBrowseTheme(configTheme);
|
|
963
1010
|
const colorForBase = getColorBaseMap(theme);
|
|
964
1011
|
const contrastForBase = getContrastBaseMap(theme);
|
|
965
1012
|
const sampleToRowMap = new Map(samples.map((s, i) => [s.id, i]));
|
|
966
1013
|
const scale = 1 / bpPerPx;
|
|
967
|
-
const
|
|
1014
|
+
const f = 0.4;
|
|
1015
|
+
const h2 = rowHeight / 2;
|
|
1016
|
+
const hp2 = h / 2;
|
|
1017
|
+
const offset = (rowHeight - h) / 2;
|
|
968
1018
|
// sample as alignments
|
|
969
1019
|
ctx.font = 'bold 10px Courier New,monospace';
|
|
970
1020
|
for (const feature of features.values()) {
|
|
@@ -974,67 +1024,123 @@
|
|
|
974
1024
|
for (const [sample, val] of Object.entries(vals)) {
|
|
975
1025
|
const origAlignment = val.data;
|
|
976
1026
|
const alignment = origAlignment.toLowerCase();
|
|
977
|
-
// gaps
|
|
978
|
-
ctx.beginPath();
|
|
979
|
-
ctx.fillStyle = 'black';
|
|
980
|
-
const offset0 = (5 / 12) * h;
|
|
981
|
-
const h6 = h / 6;
|
|
982
1027
|
const row = sampleToRowMap.get(sample);
|
|
983
1028
|
if (row === undefined) {
|
|
984
1029
|
throw new Error(`unknown sample encountered: ${sample}`);
|
|
985
1030
|
}
|
|
986
|
-
const t =
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1031
|
+
const t = rowHeight * row;
|
|
1032
|
+
// gaps
|
|
1033
|
+
ctx.beginPath();
|
|
1034
|
+
ctx.fillStyle = 'black';
|
|
1035
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
1036
|
+
if (seq[i] !== '-') {
|
|
1037
|
+
if (alignment[i] === '-') {
|
|
1038
|
+
const l = leftPx + scale * o;
|
|
1039
|
+
ctx.moveTo(l, t + h2);
|
|
1040
|
+
ctx.lineTo(l + scale + f, t + h2);
|
|
1041
|
+
}
|
|
1042
|
+
o++;
|
|
991
1043
|
}
|
|
992
1044
|
}
|
|
993
|
-
ctx.
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1045
|
+
ctx.stroke();
|
|
1046
|
+
if (!showAllLetters) {
|
|
1047
|
+
// matches
|
|
1048
|
+
ctx.beginPath();
|
|
1049
|
+
ctx.fillStyle = 'lightgrey';
|
|
1050
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
1051
|
+
if (seq[i] !== '-') {
|
|
1052
|
+
const c = alignment[i];
|
|
1053
|
+
const l = leftPx + scale * o;
|
|
1054
|
+
if (seq[i] === c && c !== '-') {
|
|
1055
|
+
ctx.rect(l, offset + t, scale + f, h);
|
|
1056
|
+
}
|
|
1057
|
+
o++;
|
|
1058
|
+
}
|
|
1004
1059
|
}
|
|
1060
|
+
ctx.fill();
|
|
1005
1061
|
}
|
|
1006
|
-
ctx.fill();
|
|
1007
1062
|
// mismatches
|
|
1008
|
-
for (let i = 0; i < alignment.length; i++) {
|
|
1063
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
1009
1064
|
const c = alignment[i];
|
|
1010
|
-
if (seq[i] !==
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1065
|
+
if (seq[i] !== '-') {
|
|
1066
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
1067
|
+
const l = leftPx + scale * o;
|
|
1068
|
+
ctx.fillStyle =
|
|
1069
|
+
colorForBase[c] ?? 'black';
|
|
1070
|
+
ctx.fillRect(l, offset + t, scale + f, h);
|
|
1071
|
+
}
|
|
1072
|
+
o++;
|
|
1015
1073
|
}
|
|
1016
1074
|
}
|
|
1017
1075
|
// font
|
|
1018
1076
|
const charSize = { w: 10 };
|
|
1019
1077
|
if (scale >= charSize.w) {
|
|
1020
|
-
for (let i = 0; i < alignment.length; i++) {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1078
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
1079
|
+
if (seq[i] !== '-') {
|
|
1080
|
+
const l = leftPx + scale * o;
|
|
1081
|
+
const offset = (scale - charSize.w) / 2 + 1;
|
|
1082
|
+
const c = alignment[i];
|
|
1083
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
1084
|
+
ctx.fillStyle = contrastForBase[c] ?? 'white';
|
|
1085
|
+
ctx.fillText(origAlignment[i], l + offset, hp2 + t + 3);
|
|
1086
|
+
}
|
|
1087
|
+
o++;
|
|
1027
1088
|
}
|
|
1028
1089
|
}
|
|
1029
1090
|
}
|
|
1030
1091
|
}
|
|
1031
1092
|
}
|
|
1093
|
+
// second pass for insertions, has slightly improved look since the
|
|
1094
|
+
// insertions are always 'on top' of the other features
|
|
1095
|
+
for (const feature of features.values()) {
|
|
1096
|
+
const [leftPx] = util.featureSpanPx(feature, region, bpPerPx);
|
|
1097
|
+
const vals = feature.get('alignments');
|
|
1098
|
+
const seq = feature.get('seq').toLowerCase();
|
|
1099
|
+
for (const [sample, val] of Object.entries(vals)) {
|
|
1100
|
+
const origAlignment = val.data;
|
|
1101
|
+
const alignment = origAlignment.toLowerCase();
|
|
1102
|
+
const row = sampleToRowMap.get(sample);
|
|
1103
|
+
if (row === undefined) {
|
|
1104
|
+
throw new Error(`unknown sample encountered: ${sample}`);
|
|
1105
|
+
}
|
|
1106
|
+
const t = rowHeight * row;
|
|
1107
|
+
ctx.beginPath();
|
|
1108
|
+
ctx.fillStyle = 'purple';
|
|
1109
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
1110
|
+
let ins = '';
|
|
1111
|
+
while (seq[i] === '-') {
|
|
1112
|
+
if (alignment[i] !== '-') {
|
|
1113
|
+
ins += alignment[i];
|
|
1114
|
+
}
|
|
1115
|
+
i++;
|
|
1116
|
+
}
|
|
1117
|
+
if (ins.length > 0) {
|
|
1118
|
+
const l = leftPx + scale * o - 1;
|
|
1119
|
+
ctx.rect(l, offset + t + 1, 1, h - 1);
|
|
1120
|
+
ctx.rect(l - 2, offset + t, 5, 1);
|
|
1121
|
+
ctx.rect(l - 2, offset + t + h - 1, 5, 1);
|
|
1122
|
+
}
|
|
1123
|
+
o++;
|
|
1124
|
+
}
|
|
1125
|
+
ctx.fill();
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1032
1128
|
}
|
|
1033
1129
|
class LinearMafRenderer extends pluggableElementTypes.FeatureRendererType {
|
|
1130
|
+
getExpandedRegion(region) {
|
|
1131
|
+
const { start, end } = region;
|
|
1132
|
+
const bpExpansion = 1;
|
|
1133
|
+
return {
|
|
1134
|
+
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
1135
|
+
...region,
|
|
1136
|
+
start: Math.floor(Math.max(start - bpExpansion, 0)),
|
|
1137
|
+
end: Math.ceil(end + bpExpansion),
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1034
1140
|
async render(renderProps) {
|
|
1035
1141
|
const { regions, bpPerPx, samples, rowHeight } = renderProps;
|
|
1036
1142
|
const [region] = regions;
|
|
1037
|
-
const height = samples.length * rowHeight;
|
|
1143
|
+
const height = samples.length * rowHeight + 100;
|
|
1038
1144
|
const width = (region.end - region.start) / bpPerPx;
|
|
1039
1145
|
const features = await this.getFeatures(renderProps);
|
|
1040
1146
|
const res = await util.renderToAbstractCanvas(width, height, renderProps, ctx => makeImageData({
|
|
@@ -1133,6 +1239,10 @@
|
|
|
1133
1239
|
const { adapter } = await this.setup();
|
|
1134
1240
|
return adapter.getRefNames();
|
|
1135
1241
|
}
|
|
1242
|
+
async getHeader() {
|
|
1243
|
+
const { adapter } = await this.setup();
|
|
1244
|
+
return adapter.getHeader();
|
|
1245
|
+
}
|
|
1136
1246
|
getFeatures(query) {
|
|
1137
1247
|
return rxjs.ObservableCreate(async (observer) => {
|
|
1138
1248
|
const { adapter } = await this.setup();
|
|
@@ -1140,31 +1250,17 @@
|
|
|
1140
1250
|
for (const feature of features) {
|
|
1141
1251
|
const data = feature.get('field5').split(',');
|
|
1142
1252
|
const alignments = {};
|
|
1143
|
-
const main = data[0];
|
|
1144
|
-
const aln = main.split(':')[5];
|
|
1145
1253
|
const alns = data.map(elt => elt.split(':')[5]);
|
|
1146
|
-
const
|
|
1147
|
-
// remove extraneous data in other alignments
|
|
1148
|
-
// reason being: cannot represent missing data in main species that are in others)
|
|
1149
|
-
for (let i = 0, o = 0; i < aln.length; i++, o++) {
|
|
1150
|
-
if (aln[i] !== '-') {
|
|
1151
|
-
for (let j = 0; j < data.length; j++) {
|
|
1152
|
-
alns2[j] += alns[j][i];
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
for (let j = 0; j < data.length; j++) {
|
|
1157
|
-
const elt = data[j];
|
|
1254
|
+
for (const [j, elt] of data.entries()) {
|
|
1158
1255
|
const ad = elt.split(':');
|
|
1159
|
-
const org = ad[0].split('.')
|
|
1160
|
-
const chr = ad[0].split('.')[1];
|
|
1256
|
+
const [org, chr] = ad[0].split('.');
|
|
1161
1257
|
alignments[org] = {
|
|
1162
|
-
chr
|
|
1258
|
+
chr,
|
|
1163
1259
|
start: +ad[1],
|
|
1164
1260
|
srcSize: +ad[2],
|
|
1165
1261
|
strand: ad[3] === '-' ? -1 : 1,
|
|
1166
1262
|
unknown: +ad[4],
|
|
1167
|
-
data:
|
|
1263
|
+
data: alns[j],
|
|
1168
1264
|
};
|
|
1169
1265
|
}
|
|
1170
1266
|
observer.next(new util.SimpleFeature({
|
|
@@ -1175,8 +1271,8 @@
|
|
|
1175
1271
|
refName: feature.get('refName'),
|
|
1176
1272
|
name: feature.get('name'),
|
|
1177
1273
|
score: feature.get('score'),
|
|
1178
|
-
alignments
|
|
1179
|
-
seq:
|
|
1274
|
+
alignments,
|
|
1275
|
+
seq: alns[0],
|
|
1180
1276
|
},
|
|
1181
1277
|
}));
|
|
1182
1278
|
}
|
|
@@ -1216,6 +1312,7 @@
|
|
|
1216
1312
|
const [error, setError] = React.useState();
|
|
1217
1313
|
const [trackName, setTrackName] = React.useState('MAF track');
|
|
1218
1314
|
const [choice, setChoice] = React.useState('BigMafAdapter');
|
|
1315
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1219
1316
|
const rootModel = mobxStateTree.getRoot(model);
|
|
1220
1317
|
return (React.createElement(material.Paper, { className: classes.paper },
|
|
1221
1318
|
React.createElement(material.Paper, null,
|
|
@@ -1299,11 +1396,17 @@
|
|
|
1299
1396
|
}
|
|
1300
1397
|
|
|
1301
1398
|
async function renderSvg(self, opts, superRenderSvg) {
|
|
1302
|
-
const {
|
|
1399
|
+
const { height, id } = self;
|
|
1400
|
+
const { offsetPx, width } = util.getContainingView(self);
|
|
1401
|
+
const clipid = `mafclip-${id}`;
|
|
1303
1402
|
return (React.createElement(React.Fragment, null,
|
|
1304
|
-
React.createElement("
|
|
1305
|
-
|
|
1306
|
-
|
|
1403
|
+
React.createElement("defs", null,
|
|
1404
|
+
React.createElement("clipPath", { id: clipid },
|
|
1405
|
+
React.createElement("rect", { x: 0, y: 0, width: width, height: height }))),
|
|
1406
|
+
React.createElement("g", { clipPath: `url(#${clipid})` },
|
|
1407
|
+
React.createElement("g", { id: "snpcov" }, await superRenderSvg(opts)),
|
|
1408
|
+
React.createElement("g", { transform: `translate(${Math.max(-offsetPx, 0)})` },
|
|
1409
|
+
React.createElement(YScaleBars, { model: self, orientation: "left", exportSVG: true })))));
|
|
1307
1410
|
}
|
|
1308
1411
|
|
|
1309
1412
|
var renderSvg$1 = {
|