@mstone6969/graph 0.1.0 → 0.2.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/README.md +111 -14
- package/dist/ExcelWriter-3ZSYUH4T.cjs +154 -0
- package/dist/GraphType.js +24 -38
- package/dist/PdfWriter-6HJFYMCO.cjs +205 -0
- package/dist/catalog-YWX6FHRM.cjs +8 -0
- package/dist/catalog.js +5 -1
- package/dist/charts/bars/index.js +2 -12
- package/dist/charts/comparison/EffectForestPlot.js +1 -1
- package/dist/charts/comparison/index.js +1 -8
- package/dist/charts/demography/Nomogram.js +3 -3
- package/dist/charts/density/HexbinChart.js +6 -4
- package/dist/charts/diagrams/ArchitectureCanvas.js +68 -0
- package/dist/charts/diagrams/ArchitectureDiagram.js +331 -0
- package/dist/charts/diagrams/ArchitectureLayout.js +106 -0
- package/dist/charts/diagrams/ArchitectureLinkLabels.js +78 -0
- package/dist/charts/diagrams/ArchitectureModel.js +108 -0
- package/dist/charts/diagrams/ArchitectureRouter.js +348 -0
- package/dist/charts/diagrams/ArchitectureSamples.js +312 -0
- package/dist/charts/diagrams/ArchitectureShapes.js +130 -0
- package/dist/charts/diagrams/ArchitectureText.js +25 -0
- package/dist/charts/diagrams/DiagramText.js +38 -0
- package/dist/charts/diagrams/EntityRelationshipDiagram.js +465 -0
- package/dist/charts/diagrams/ErCrowsFoot.js +33 -0
- package/dist/charts/diagrams/ErSamples.js +205 -0
- package/dist/charts/diagrams/ErSchema.js +122 -0
- package/dist/charts/diagrams/LogicCircuit.js +154 -0
- package/dist/charts/diagrams/LogicCircuitLayout.js +600 -0
- package/dist/charts/diagrams/LogicCircuitSamples.js +66 -0
- package/dist/charts/diagrams/LogicGateDiagram.js +216 -0
- package/dist/charts/diagrams/LogicGateSymbol.js +113 -0
- package/dist/charts/diagrams/NetworkTopologyDiagram.js +247 -0
- package/dist/charts/diagrams/SequenceDiagram.js +351 -0
- package/dist/charts/diagrams/SequenceLayout.js +422 -0
- package/dist/charts/diagrams/SequenceModel.js +221 -0
- package/dist/charts/diagrams/TopologyLayout.js +143 -0
- package/dist/charts/diagrams/architectureEnums.js +50 -0
- package/dist/charts/diagrams/index.js +26 -0
- package/dist/charts/diagrams/logicEnums.js +42 -0
- package/dist/charts/diagrams/sequenceEnums.js +40 -0
- package/dist/charts/distribution/HistogramChart.js +25 -3
- package/dist/charts/distribution/index.js +1 -3
- package/dist/charts/earthscience/HovmollerDiagram.js +477 -0
- package/dist/charts/earthscience/HovmollerEnums.js +42 -0
- package/dist/charts/earthscience/HovmollerField.js +180 -0
- package/dist/charts/earthscience/HovmollerSample.js +40 -0
- package/dist/charts/earthscience/HovmollerSpeed.js +71 -0
- package/dist/charts/earthscience/index.js +10 -2
- package/dist/charts/engineering/ConstellationDiagram.js +26 -6
- package/dist/charts/engineering/EyeDiagram.js +2 -1
- package/dist/charts/engineering/PoleZeroPlot.js +32 -8
- package/dist/charts/financial/RelativeStrengthRotation.js +434 -0
- package/dist/charts/financial/RelativeStrengthRotationMath.js +71 -0
- package/dist/charts/financial/RelativeStrengthRotationSamples.js +27 -0
- package/dist/charts/financial/index.js +5 -3
- package/dist/charts/flow/index.js +1 -5
- package/dist/charts/frameworks/AnsoffMatrix.js +523 -0
- package/dist/charts/frameworks/FrameworkText.js +98 -0
- package/dist/charts/frameworks/NpsGauge.js +279 -0
- package/dist/charts/frameworks/NpsSurvey.js +58 -0
- package/dist/charts/frameworks/PerceptualMap.js +419 -0
- package/dist/charts/frameworks/PerceptualProjection.js +142 -0
- package/dist/charts/frameworks/PositioningSamples.js +191 -0
- package/dist/charts/frameworks/RaciMatrix.js +458 -0
- package/dist/charts/frameworks/RaciPlan.js +81 -0
- package/dist/charts/frameworks/RaciSamples.js +76 -0
- package/dist/charts/frameworks/SwotMatrix.js +358 -0
- package/dist/charts/frameworks/SwotSamples.js +85 -0
- package/dist/charts/frameworks/VanWestendorpChart.js +187 -0
- package/dist/charts/frameworks/VanWestendorpSurvey.js +197 -0
- package/dist/charts/frameworks/index.js +26 -0
- package/dist/charts/frameworks/raciEnums.js +30 -0
- package/dist/charts/frameworks/swotEnums.js +34 -0
- package/dist/charts/genomics/CircosPlot.js +1 -1
- package/dist/charts/geoflows/GeoFlowChart.js +2 -2
- package/dist/charts/geoflows/GeoFlowSample.js +2 -1
- package/dist/charts/geomaps/FloorPlanMap.js +324 -0
- package/dist/charts/geomaps/FloorPlanModel.js +179 -0
- package/dist/charts/geomaps/FloorPlanSamples.js +416 -0
- package/dist/charts/geomaps/GeoLand.js +2 -2
- package/dist/charts/geomaps/GeoSamples.js +2 -1
- package/dist/charts/geomaps/GraticuleMap.js +2 -2
- package/dist/charts/geomaps/RoomLabelFit.js +200 -0
- package/dist/charts/geomaps/TissotMap.js +2 -2
- package/dist/charts/geomaps/index.js +8 -4
- package/dist/charts/hierarchy/SunburstChart.js +17 -10
- package/dist/charts/hierarchy/index.js +2 -11
- package/dist/charts/indicators/BulletChart.js +8 -2
- package/dist/charts/indicators/SpeedometerChart.js +1 -1
- package/dist/charts/indicators/ThermometerChart.js +1 -1
- package/dist/charts/lifescience/LoessFit.js +113 -0
- package/dist/charts/lifescience/MaPlot.js +337 -0
- package/dist/charts/lifescience/MaPlotSamples.js +35 -0
- package/dist/charts/lifescience/OncoGlyph.js +137 -0
- package/dist/charts/lifescience/OncoPrint.js +373 -0
- package/dist/charts/lifescience/OncoPrintMatrix.js +107 -0
- package/dist/charts/lifescience/OncoPrintSamples.js +142 -0
- package/dist/charts/lifescience/PedigreeChart.js +306 -0
- package/dist/charts/lifescience/PedigreeLayout.js +261 -0
- package/dist/charts/lifescience/PedigreeRows.js +70 -0
- package/dist/charts/lifescience/PedigreeSamples.js +161 -0
- package/dist/charts/lifescience/PedigreeSymbol.js +47 -0
- package/dist/charts/lifescience/RamachandranPlot.js +364 -0
- package/dist/charts/lifescience/RamachandranRegions.js +2712 -0
- package/dist/charts/lifescience/RamachandranSamples.js +235 -0
- package/dist/charts/lifescience/index.js +25 -0
- package/dist/charts/lifescience/pedigreeEnums.js +21 -0
- package/dist/charts/lifescience/ramachandranEnums.js +34 -0
- package/dist/charts/linesareas/DualAxisChart.js +1 -1
- package/dist/charts/linesareas/LineEnums.js +1 -15
- package/dist/charts/linesareas/MultiLineChart.js +1 -1
- package/dist/charts/linesareas/TrendChart.js +15 -5
- package/dist/charts/linesareas/index.js +2 -6
- package/dist/charts/massive/MassiveChart.js +3 -2
- package/dist/charts/mathematical/FourierSeriesChart.js +7 -1
- package/dist/charts/mathematical/index.js +2 -6
- package/dist/charts/matrix/GridPlot.js +27 -2
- package/dist/charts/matrix/TilegramChart.js +2 -2
- package/dist/charts/mlcharts/AttentionGrid.js +79 -0
- package/dist/charts/mlcharts/AttentionHeatmap.js +329 -0
- package/dist/charts/mlcharts/AttentionModel.js +132 -0
- package/dist/charts/mlcharts/AttentionSamples.js +82 -0
- package/dist/charts/mlcharts/CellNumber.js +8 -0
- package/dist/charts/mlcharts/CurveDiagnosticSamples.js +95 -0
- package/dist/charts/mlcharts/CurvePickMark.js +31 -0
- package/dist/charts/mlcharts/DecisionBoundaryChart.js +316 -0
- package/dist/charts/mlcharts/DecisionSamples.js +60 -0
- package/dist/charts/mlcharts/DecisionSurface.js +130 -0
- package/dist/charts/mlcharts/DistinctTicks.js +13 -0
- package/dist/charts/mlcharts/ElbowMath.js +153 -0
- package/dist/charts/mlcharts/ElbowPlot.js +273 -0
- package/dist/charts/mlcharts/ExplainabilitySamples.js +70 -0
- package/dist/charts/mlcharts/FeatureImportanceChart.js +150 -0
- package/dist/charts/mlcharts/ImportanceRanking.js +98 -0
- package/dist/charts/mlcharts/LearningCurveChart.js +276 -0
- package/dist/charts/mlcharts/LearningCurveMath.js +92 -0
- package/dist/charts/mlcharts/PartialDependence.js +92 -0
- package/dist/charts/mlcharts/PartialDependencePlot.js +225 -0
- package/dist/charts/mlcharts/WideLabel.js +28 -0
- package/dist/charts/mlcharts/curveDiagnosticEnums.js +37 -0
- package/dist/charts/mlcharts/index.js +32 -0
- package/dist/charts/proportion/index.js +5 -9
- package/dist/charts/scatter/CategoryScatterBase.js +1 -4
- package/dist/charts/scatter/ConnectedScatter.js +7 -4
- package/dist/charts/scatter/MarginalScatter.js +13 -8
- package/dist/charts/scatter/index.js +4 -17
- package/dist/charts/scientific/SpectrogramChart.js +9 -3
- package/dist/charts/scientific/index.js +2 -2
- package/dist/charts/signals/index.js +3 -13
- package/dist/charts/statistics/LorenzCurve.js +7 -4
- package/dist/charts/threebars/Candlestick3DChart.js +18 -2
- package/dist/charts/threeglobe/GlobeChart.js +2 -2
- package/dist/charts/threeglobe/GlobeSamples.js +2 -1
- package/dist/charts/threeglobe/RingGlobe.js +1 -1
- package/dist/charts/threenetwork/ConeTreeChart.js +2 -1
- package/dist/charts/threenetwork/Gantt3DChart.js +3 -2
- package/dist/charts/threesurfaces/Ridgeline3DChart.js +22 -4
- package/dist/charts/threevolume/LorenzAttractor.js +19 -0
- package/dist/charts/threevolume/VolumeRaycastChart.js +4 -3
- package/dist/charts/threevolume/volumeEnums.js +1 -0
- package/dist/charts/timeline/index.js +2 -10
- package/dist/chunk-753UBLV2.cjs +33 -0
- package/dist/chunk-VX54A7KA.cjs +17 -0
- package/dist/{chunk-W5GAAAZY.cjs → chunk-WAODDPYX.cjs} +24874 -17620
- package/dist/chunk-Z3JPIJSC.cjs +338 -0
- package/dist/core/CartesianChart.js +87 -11
- package/dist/core/Chart.js +63 -15
- package/dist/core/Chart2D.js +54 -17
- package/dist/core/Chart3D.js +56 -20
- package/dist/core/ChartExportError.js +2 -0
- package/dist/core/ChartExporter.js +50 -8
- package/dist/core/ColumnCheck.js +53 -0
- package/dist/core/Context2D.js +29 -15
- package/dist/core/Context3D.js +7 -2
- package/dist/core/ExcelWriter.js +152 -0
- package/dist/core/ExportText.js +28 -0
- package/dist/core/MarkOrigin.js +88 -0
- package/dist/core/MarkStyle.js +294 -0
- package/dist/core/Palette.js +27 -7
- package/dist/core/PdfPage.js +13 -0
- package/dist/core/PdfWriter.js +245 -0
- package/dist/core/Scales.js +52 -2
- package/dist/core/SceneOverlay.js +4 -4
- package/dist/core/TextFit.js +37 -0
- package/dist/core/Theme.js +28 -1
- package/dist/core/enums.js +33 -1
- package/dist/core/pathMorph.js +15 -0
- package/dist/data/SampleData.js +1 -17
- package/dist/data/WorldSample.js +23 -0
- package/dist/index.cjs +810 -691
- package/dist/index.d.cts +8390 -2925
- package/dist/index.d.ts +8392 -2927
- package/dist/index.js +106 -56
- package/dist/react/ChartView.js +18 -8
- package/dist/react/GraphHandle.js +2 -0
- package/dist/react/components.js +74 -116
- package/package.json +13 -3
- package/dist/catalog-4UP2YFNR.cjs +0 -7
- package/dist/charts/bars/BulletBar.js +0 -184
- package/dist/charts/bars/ErrorBarColumn.js +0 -147
- package/dist/charts/bars/HistogramBar.js +0 -217
- package/dist/charts/bars/MarimekkoBar.js +0 -191
- package/dist/charts/bars/ParetoBar.js +0 -143
- package/dist/charts/comparison/PanelChart.js +0 -70
- package/dist/charts/comparison/SmallMultipleBars.js +0 -134
- package/dist/charts/comparison/SpanChart.js +0 -118
- package/dist/charts/comparison/TrellisChart.js +0 -158
- package/dist/charts/distribution/RidgeSlabs3DChart.js +0 -169
- package/dist/charts/financial/Candlestick3D.js +0 -157
- package/dist/charts/flow/BumpFlow.js +0 -212
- package/dist/charts/flow/StreamFlow.js +0 -174
- package/dist/charts/geomaps/ThiessenMap.js +0 -106
- package/dist/charts/hierarchy/ConeTree3D.js +0 -135
- package/dist/charts/hierarchy/Hierarchy3DChart.js +0 -27
- package/dist/charts/hierarchy/SpherePack3D.js +0 -81
- package/dist/charts/hierarchy/SunburstTower3D.js +0 -93
- package/dist/charts/hierarchy/TreemapSkyline3D.js +0 -98
- package/dist/charts/linesareas/ConnectedScatterTime.js +0 -206
- package/dist/charts/linesareas/SmallMultipleLines.js +0 -153
- package/dist/charts/mathematical/ParametricSurface3D.js +0 -234
- package/dist/charts/mathematical/StrangeAttractor3D.js +0 -251
- package/dist/charts/proportion/ConeFunnel.js +0 -126
- package/dist/charts/proportion/ExtrudedPieChart.js +0 -123
- package/dist/charts/scatter/BeeswarmScatter.js +0 -151
- package/dist/charts/scatter/BinnedScatterBase.js +0 -51
- package/dist/charts/scatter/ContourScatter.js +0 -116
- package/dist/charts/scatter/GridBinScatter.js +0 -98
- package/dist/charts/scatter/HexbinScatter.js +0 -74
- package/dist/charts/scatter/StripScatter.js +0 -107
- package/dist/charts/scatter/SunflowerScatter.js +0 -109
- package/dist/charts/signals/ConstellationChart.js +0 -153
- package/dist/charts/signals/CorrelogramChart.js +0 -171
- package/dist/charts/signals/EyeDiagramChart.js +0 -155
- package/dist/charts/signals/PoleZeroChart.js +0 -164
- package/dist/charts/signals/SpectrogramChart.js +0 -79
- package/dist/charts/timeline/CalendarSkylineChart.js +0 -128
- package/dist/charts/timeline/ScheduleBlocksChart.js +0 -119
- package/dist/charts/timeline/SpiralTimeline.js +0 -154
- package/dist/charts/timeline/StreamTimeline.js +0 -141
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/charts/lifescience/OncoGlyph.ts
|
|
2
|
+
/** Alteration classes an oncoprint draws. Declared in the order a cell's primary class is chosen. */
|
|
3
|
+
var OncoAlteration = /* @__PURE__ */ function(OncoAlteration) {
|
|
4
|
+
OncoAlteration["Amplification"] = "amplification";
|
|
5
|
+
OncoAlteration["Deletion"] = "deep-deletion";
|
|
6
|
+
OncoAlteration["Fusion"] = "fusion";
|
|
7
|
+
OncoAlteration["Truncating"] = "truncating";
|
|
8
|
+
OncoAlteration["Splice"] = "splice";
|
|
9
|
+
OncoAlteration["Inframe"] = "inframe";
|
|
10
|
+
OncoAlteration["Missense"] = "missense";
|
|
11
|
+
OncoAlteration["Other"] = "other";
|
|
12
|
+
OncoAlteration["Gain"] = "gain";
|
|
13
|
+
OncoAlteration["Loss"] = "shallow-loss";
|
|
14
|
+
return OncoAlteration;
|
|
15
|
+
}({});
|
|
16
|
+
/** Cell layers, bottom to top. One event from each can share a cell. */
|
|
17
|
+
var OncoLayer = /* @__PURE__ */ function(OncoLayer) {
|
|
18
|
+
OncoLayer["CopyNumber"] = "copy-number";
|
|
19
|
+
OncoLayer["Structural"] = "structural";
|
|
20
|
+
OncoLayer["Sequence"] = "sequence";
|
|
21
|
+
return OncoLayer;
|
|
22
|
+
}({});
|
|
23
|
+
var band = (top, bottom, left = 0, right = 1) => [
|
|
24
|
+
[left, top],
|
|
25
|
+
[right, top],
|
|
26
|
+
[right, bottom],
|
|
27
|
+
[left, bottom]
|
|
28
|
+
];
|
|
29
|
+
var SPECS = {
|
|
30
|
+
["amplification"]: {
|
|
31
|
+
label: "Amplification",
|
|
32
|
+
layer: "copy-number",
|
|
33
|
+
shape: [band(0, 1)],
|
|
34
|
+
polarity: 1
|
|
35
|
+
},
|
|
36
|
+
["deep-deletion"]: {
|
|
37
|
+
label: "Deep deletion",
|
|
38
|
+
layer: "copy-number",
|
|
39
|
+
shape: [band(0, .28), band(.72, 1)],
|
|
40
|
+
polarity: -1
|
|
41
|
+
},
|
|
42
|
+
["fusion"]: {
|
|
43
|
+
label: "Fusion",
|
|
44
|
+
layer: "structural",
|
|
45
|
+
shape: [[
|
|
46
|
+
[0, .1],
|
|
47
|
+
[1, .5],
|
|
48
|
+
[0, .9]
|
|
49
|
+
]]
|
|
50
|
+
},
|
|
51
|
+
["truncating"]: {
|
|
52
|
+
label: "Truncating",
|
|
53
|
+
layer: "sequence",
|
|
54
|
+
shape: [[
|
|
55
|
+
[0, .28],
|
|
56
|
+
[1, .28],
|
|
57
|
+
[.5, .72]
|
|
58
|
+
]]
|
|
59
|
+
},
|
|
60
|
+
["splice"]: {
|
|
61
|
+
label: "Splice",
|
|
62
|
+
layer: "sequence",
|
|
63
|
+
shape: [band(.12, .88, .28, .72)]
|
|
64
|
+
},
|
|
65
|
+
["inframe"]: {
|
|
66
|
+
label: "In-frame",
|
|
67
|
+
layer: "sequence",
|
|
68
|
+
shape: [[
|
|
69
|
+
[.5, .2],
|
|
70
|
+
[1, .5],
|
|
71
|
+
[.5, .8],
|
|
72
|
+
[0, .5]
|
|
73
|
+
]]
|
|
74
|
+
},
|
|
75
|
+
["missense"]: {
|
|
76
|
+
label: "Missense",
|
|
77
|
+
layer: "sequence",
|
|
78
|
+
shape: [band(.36, .64)]
|
|
79
|
+
},
|
|
80
|
+
["other"]: {
|
|
81
|
+
label: "Other",
|
|
82
|
+
layer: "sequence",
|
|
83
|
+
shape: [band(.2, .34, .15, .85), band(.66, .8, .15, .85)]
|
|
84
|
+
},
|
|
85
|
+
["gain"]: {
|
|
86
|
+
label: "Gain",
|
|
87
|
+
layer: "copy-number",
|
|
88
|
+
shape: [band(0, .5)],
|
|
89
|
+
polarity: .5
|
|
90
|
+
},
|
|
91
|
+
["shallow-loss"]: {
|
|
92
|
+
label: "Shallow loss",
|
|
93
|
+
layer: "copy-number",
|
|
94
|
+
shape: [band(.5, 1)],
|
|
95
|
+
polarity: -.5
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/** cBioPortal OncoPrinter terms, MAF `Variant_Classification` values, Ensembl VEP consequences and common words. Tested in order. */
|
|
99
|
+
var CLASSES = [
|
|
100
|
+
[/^(silent|synonymous|intron|igr|rna|[35]'?_?(utr|flank)|[35]_prime|upstream|downstream|intergenic|non_coding)/, null],
|
|
101
|
+
[/^(amp|amplification|amplified)$/, "amplification"],
|
|
102
|
+
[/^(homdel|deep_?deletion|deletion|homozygous_?deletion)$/, "deep-deletion"],
|
|
103
|
+
[/^gain$/, "gain"],
|
|
104
|
+
[/^(hetloss|shallow_?(deletion|loss)|loss)$/, "shallow-loss"],
|
|
105
|
+
[/fusion|rearrangement|translocation|structural/, "fusion"],
|
|
106
|
+
[/^in_?frame/, "inframe"],
|
|
107
|
+
[/^(trunc|nonsense|frame_?shift|stop_?gained|nonstop)/, "truncating"],
|
|
108
|
+
[/^splice/, "splice"],
|
|
109
|
+
[/^missense/, "missense"]
|
|
110
|
+
];
|
|
111
|
+
/** What each alteration class means and the silhouette it draws, so class survives without colour. */
|
|
112
|
+
var OncoGlyph = class {
|
|
113
|
+
static order = Object.values(OncoAlteration);
|
|
114
|
+
static label(type) {
|
|
115
|
+
return SPECS[type].label;
|
|
116
|
+
}
|
|
117
|
+
static layer(type) {
|
|
118
|
+
return SPECS[type].layer;
|
|
119
|
+
}
|
|
120
|
+
/** Copy-number polarity from -1 to 1, or `undefined` for a class with identity rather than direction. */
|
|
121
|
+
static polarity(type) {
|
|
122
|
+
return SPECS[type].polarity;
|
|
123
|
+
}
|
|
124
|
+
/** The class of a cell's text, `null` for a blank or a variant that does not alter the protein, or `Other`. */
|
|
125
|
+
static classify(value) {
|
|
126
|
+
const word = String(value ?? "").trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
127
|
+
if (!word) return null;
|
|
128
|
+
const hit = CLASSES.find(([pattern]) => pattern.test(word));
|
|
129
|
+
return hit ? hit[1] : "other";
|
|
130
|
+
}
|
|
131
|
+
/** The glyph as path data, filling the box. */
|
|
132
|
+
static path(type, x, y, width, height) {
|
|
133
|
+
const round = (value) => +value.toFixed(2);
|
|
134
|
+
return SPECS[type].shape.map((polygon) => `M${polygon.map(([u, v]) => `${round(x + u * width)},${round(y + v * height)}`).join("L")}Z`).join("");
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
export { OncoAlteration, OncoGlyph, OncoLayer };
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { Chart2D } from "../../core/Chart2D.js";
|
|
2
|
+
import { ChartFamily, DataShape, Dimension, LegendPosition } from "../../core/enums.js";
|
|
3
|
+
import { OncoAlteration, OncoGlyph } from "./OncoGlyph.js";
|
|
4
|
+
import { FrameworkText } from "../frameworks/FrameworkText.js";
|
|
5
|
+
import { OncoPrintMatrix, OncoSort } from "./OncoPrintMatrix.js";
|
|
6
|
+
import { OncoPrintSamples } from "./OncoPrintSamples.js";
|
|
7
|
+
// src/charts/lifescience/OncoPrint.ts
|
|
8
|
+
/** Which marginal bars sit around the matrix. */
|
|
9
|
+
var OncoTracks = /* @__PURE__ */ function(OncoTracks) {
|
|
10
|
+
OncoTracks["Both"] = "both-bars";
|
|
11
|
+
OncoTracks["Frequency"] = "gene-frequency";
|
|
12
|
+
OncoTracks["Burden"] = "sample-burden";
|
|
13
|
+
OncoTracks["None"] = "no-bars";
|
|
14
|
+
return OncoTracks;
|
|
15
|
+
}({});
|
|
16
|
+
/** What a marginal bar shows. */
|
|
17
|
+
var OncoBars = /* @__PURE__ */ function(OncoBars) {
|
|
18
|
+
OncoBars["Stacked"] = "by-alteration";
|
|
19
|
+
OncoBars["Total"] = "total";
|
|
20
|
+
return OncoBars;
|
|
21
|
+
}({});
|
|
22
|
+
/** Whether samples with no alteration in these genes keep a column. */
|
|
23
|
+
var OncoUnaltered = /* @__PURE__ */ function(OncoUnaltered) {
|
|
24
|
+
OncoUnaltered["Keep"] = "keep-unaltered";
|
|
25
|
+
OncoUnaltered["Hide"] = "hide-unaltered";
|
|
26
|
+
return OncoUnaltered;
|
|
27
|
+
}({});
|
|
28
|
+
var CHAR = 6.2;
|
|
29
|
+
var PERCENT = 34;
|
|
30
|
+
var LEGEND_ROW = 20;
|
|
31
|
+
var SWATCH = {
|
|
32
|
+
width: 9,
|
|
33
|
+
height: 16
|
|
34
|
+
};
|
|
35
|
+
var GAP = 2;
|
|
36
|
+
var clip = (text, room) => FrameworkText.fit(text, room, 11);
|
|
37
|
+
var percent = (share) => share > 0 && share < .01 ? "<1%" : `${Math.round(share * 100)}%`;
|
|
38
|
+
var plural = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
39
|
+
/**
|
|
40
|
+
* The cBioPortal gene-by-sample view (Gao et al. 2013, Sci Signal 6:pl1; Cerami et al. 2012, Cancer
|
|
41
|
+
* Discov 2:401). Copy number, structural and sequence events layer in a cell, and every class has its
|
|
42
|
+
* own silhouette. The default memo sort follows `memoSort` as used for MEMo (Ciriello et al. 2012,
|
|
43
|
+
* Genome Res 22:398), which turns mutual exclusivity into a staircase.
|
|
44
|
+
*/
|
|
45
|
+
var OncoPrint = class OncoPrint extends Chart2D {
|
|
46
|
+
static meta = {
|
|
47
|
+
id: "oncoprint",
|
|
48
|
+
name: "OncoPrint",
|
|
49
|
+
family: ChartFamily.Scientific,
|
|
50
|
+
dimension: Dimension.Two,
|
|
51
|
+
shape: DataShape.Table,
|
|
52
|
+
description: "Maps genes by samples with a glyph for each alteration type, to show recurrence and mutual exclusivity in a cohort. The sample cohort is synthetic.",
|
|
53
|
+
tags: [
|
|
54
|
+
"genomics",
|
|
55
|
+
"cancer",
|
|
56
|
+
"mutation",
|
|
57
|
+
"cbioportal",
|
|
58
|
+
"mutual exclusivity"
|
|
59
|
+
],
|
|
60
|
+
sample: (seed) => OncoPrintSamples.cohort(seed),
|
|
61
|
+
variants: {
|
|
62
|
+
sort: Object.values(OncoSort),
|
|
63
|
+
tracks: Object.values(OncoTracks),
|
|
64
|
+
bars: Object.values(OncoBars),
|
|
65
|
+
unaltered: Object.values(OncoUnaltered)
|
|
66
|
+
},
|
|
67
|
+
valid: (v) => v.tracks !== "no-bars" || v.bars === "by-alteration"
|
|
68
|
+
};
|
|
69
|
+
defaults() {
|
|
70
|
+
return {
|
|
71
|
+
sample: "sample",
|
|
72
|
+
gene: "gene",
|
|
73
|
+
alteration: "alteration",
|
|
74
|
+
sort: OncoSort.Memo,
|
|
75
|
+
tracks: "both-bars",
|
|
76
|
+
bars: "by-alteration",
|
|
77
|
+
unaltered: "keep-unaltered"
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Glyphs need shape, which the shared swatch legend cannot draw, so the chart draws its own. */
|
|
81
|
+
legendItems() {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
#matrix() {
|
|
85
|
+
const { data, sample, gene, alteration } = this.options;
|
|
86
|
+
return new OncoPrintMatrix(data, {
|
|
87
|
+
sample,
|
|
88
|
+
gene,
|
|
89
|
+
alteration
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
#order(matrix) {
|
|
93
|
+
return matrix.arrange(this.options.sort, this.options.unaltered === "hide-unaltered");
|
|
94
|
+
}
|
|
95
|
+
/** Seats classes in a fixed order, so each keeps its colour whatever the data holds. The rare `Other` takes the blue that deletions use. */
|
|
96
|
+
#seat(ctx) {
|
|
97
|
+
for (const type of [OncoAlteration.Other, ...OncoGlyph.order]) if (OncoGlyph.polarity(type) === void 0) ctx.palette.color(OncoGlyph.label(type));
|
|
98
|
+
}
|
|
99
|
+
#fill(ctx, type) {
|
|
100
|
+
const polarity = OncoGlyph.polarity(type);
|
|
101
|
+
return polarity !== void 0 ? ctx.palette.diverging(polarity) : ctx.palette.color(OncoGlyph.label(type));
|
|
102
|
+
}
|
|
103
|
+
#legend(types, width) {
|
|
104
|
+
if (this.legendPosition === LegendPosition.None) return [];
|
|
105
|
+
let x = 0, y = 0;
|
|
106
|
+
return types.map((type) => {
|
|
107
|
+
const text = OncoGlyph.label(type), cost = SWATCH.width + 5 + text.length * CHAR + 14;
|
|
108
|
+
if (x && x + cost > width) {
|
|
109
|
+
x = 0;
|
|
110
|
+
y += LEGEND_ROW;
|
|
111
|
+
}
|
|
112
|
+
const at = {
|
|
113
|
+
type,
|
|
114
|
+
x,
|
|
115
|
+
y,
|
|
116
|
+
text
|
|
117
|
+
};
|
|
118
|
+
x += cost;
|
|
119
|
+
return at;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
#layout(ctx, { genes, samples }, legendHeight) {
|
|
123
|
+
const { width, height } = ctx.plot, { tracks } = this.options;
|
|
124
|
+
const frequency = tracks === "both-bars" || tracks === "gene-frequency";
|
|
125
|
+
const burden = tracks === "both-bars" || tracks === "sample-burden";
|
|
126
|
+
const longest = Math.max(...genes.map((gene) => FrameworkText.width(gene, 11)));
|
|
127
|
+
const labels = Math.min(longest, 96, width * .2) + 6;
|
|
128
|
+
const barWidth = frequency ? Math.max(40, Math.min(96, width * .15)) : 0;
|
|
129
|
+
const bandHeight = burden ? Math.max(28, Math.min(56, height * .16)) : frequency ? 16 : 0;
|
|
130
|
+
const x = labels + PERCENT + GAP, y = bandHeight + (bandHeight ? 4 : 0);
|
|
131
|
+
const matrixWidth = Math.max(10, width - x - (barWidth ? barWidth + 10 : 0));
|
|
132
|
+
const matrixHeight = Math.max(10, height - y - legendHeight);
|
|
133
|
+
return {
|
|
134
|
+
matrix: {
|
|
135
|
+
x,
|
|
136
|
+
y,
|
|
137
|
+
width: matrixWidth,
|
|
138
|
+
height: matrixHeight
|
|
139
|
+
},
|
|
140
|
+
labelX: labels - 6,
|
|
141
|
+
percentX: x - 6,
|
|
142
|
+
barX: x + matrixWidth + 10,
|
|
143
|
+
barWidth,
|
|
144
|
+
bandHeight,
|
|
145
|
+
legendY: y + matrixHeight + 10,
|
|
146
|
+
rowHeight: matrixHeight / genes.length,
|
|
147
|
+
columnWidth: matrixWidth / samples.length
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** Gaps close up as cells narrow, since a 2px gap would leave no cell. */
|
|
151
|
+
static #inset(step) {
|
|
152
|
+
return step >= 6 ? GAP : step >= 3 ? 1 : 0;
|
|
153
|
+
}
|
|
154
|
+
#cells(ctx, matrix, { genes, samples }, layout) {
|
|
155
|
+
const { matrix: box, rowHeight, columnWidth } = layout;
|
|
156
|
+
const across = OncoPrint.#inset(columnWidth), down = OncoPrint.#inset(rowHeight);
|
|
157
|
+
const left = (i) => box.x + i * columnWidth + across / 2, top = (j) => box.y + j * rowHeight + down / 2;
|
|
158
|
+
const width = Math.max(.5, columnWidth - across), height = Math.max(.5, rowHeight - down);
|
|
159
|
+
const tracks = genes.map((gene, j) => ({
|
|
160
|
+
gene,
|
|
161
|
+
d: samples.map((_, i) => `M${left(i).toFixed(2)},${top(j).toFixed(2)}h${width.toFixed(2)}v${height.toFixed(2)}h${(-width).toFixed(2)}z`).join("")
|
|
162
|
+
}));
|
|
163
|
+
const rows = ctx.marks("tracks", "path", tracks, (track) => track.gene).attr("fill", ctx.theme.grid).style("pointer-events", "none");
|
|
164
|
+
ctx.set(rows, { d: (track) => track.d });
|
|
165
|
+
const glyphs = genes.flatMap((gene, j) => samples.flatMap((sample, i) => {
|
|
166
|
+
const cell = matrix.cell(gene, sample);
|
|
167
|
+
return cell ? OncoPrintMatrix.layers(cell).map(({ type }) => ({
|
|
168
|
+
key: `${gene}|${sample}|${OncoGlyph.layer(type)}`,
|
|
169
|
+
cell,
|
|
170
|
+
type,
|
|
171
|
+
d: OncoGlyph.path(type, left(i), top(j), width, height)
|
|
172
|
+
})) : [];
|
|
173
|
+
}));
|
|
174
|
+
const marks = ctx.marks("glyphs", "path", glyphs, (glyph) => glyph.key).attr("fill", (glyph) => this.#fill(ctx, glyph.type));
|
|
175
|
+
ctx.set(marks, {
|
|
176
|
+
d: (glyph) => glyph.d,
|
|
177
|
+
"fill-opacity": 1
|
|
178
|
+
}, { "fill-opacity": 0 });
|
|
179
|
+
ctx.hover(marks, ({ cell, type }) => ({
|
|
180
|
+
title: `${cell.gene}, ${cell.sample}`,
|
|
181
|
+
color: this.#fill(ctx, type),
|
|
182
|
+
rows: cell.events.map((event) => [OncoGlyph.label(event.type), event.text])
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
/** Bar length runs from zero, so a stack's far end is the exact total; gaps come out of the inner ends. */
|
|
186
|
+
#segments(subject, cells, draw, unit) {
|
|
187
|
+
const hits = cells.filter((cell) => !!cell), total = hits.length;
|
|
188
|
+
if (!total) return [];
|
|
189
|
+
const counts = this.options.bars === "total" ? [[null, total]] : OncoGlyph.order.map((type) => [type, hits.filter((cell) => cell.primary === type).length]).filter(([, count]) => count);
|
|
190
|
+
let run = 0;
|
|
191
|
+
return counts.map(([type, count], i) => {
|
|
192
|
+
const from = run;
|
|
193
|
+
run += count;
|
|
194
|
+
const lead = i ? Math.min(GAP, (unit(run) - unit(from)) / 3) : 0;
|
|
195
|
+
return {
|
|
196
|
+
key: `${subject}|${type ?? "all"}`,
|
|
197
|
+
subject,
|
|
198
|
+
type,
|
|
199
|
+
count,
|
|
200
|
+
total,
|
|
201
|
+
d: draw(unit(from) + lead, unit(run), i === counts.length - 1)
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
#bars(ctx, name, segments, tip) {
|
|
206
|
+
const fill = (segment) => segment.type ? this.#fill(ctx, segment.type) : ctx.theme.inkMuted;
|
|
207
|
+
const marks = ctx.marks(name, "path", segments, (segment) => segment.key).attr("fill", fill);
|
|
208
|
+
ctx.set(marks, {
|
|
209
|
+
d: (segment) => segment.d,
|
|
210
|
+
"fill-opacity": 1
|
|
211
|
+
}, { "fill-opacity": 0 });
|
|
212
|
+
ctx.hover(marks, (segment) => ({
|
|
213
|
+
title: tip(segment),
|
|
214
|
+
color: fill(segment),
|
|
215
|
+
rows: segment.type ? [[OncoGlyph.label(segment.type), segment.count]] : [["Total", segment.total]]
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
#frequencyBars(ctx, matrix, { genes }, layout) {
|
|
219
|
+
const { barX, barWidth, matrix: box, rowHeight } = layout;
|
|
220
|
+
const peak = Math.max(1, ...genes.map((gene) => matrix.altered(gene)));
|
|
221
|
+
const shown = barWidth ? genes : [];
|
|
222
|
+
const thick = Math.min(14, Math.max(1, rowHeight - OncoPrint.#inset(rowHeight) - 2));
|
|
223
|
+
const bars = shown.flatMap((gene, j) => {
|
|
224
|
+
const y = box.y + (j + .5) * rowHeight - thick / 2, r = Math.min(4, thick / 2);
|
|
225
|
+
const path = (from, to, end) => {
|
|
226
|
+
const a = barX + from, b = barX + to, bend = end ? Math.min(r, b - a) : 0;
|
|
227
|
+
return `M${a.toFixed(2)},${y.toFixed(2)}H${(b - bend).toFixed(2)}Q${b.toFixed(2)},${y.toFixed(2)} ${b.toFixed(2)},${(y + bend).toFixed(2)}V${(y + thick - bend).toFixed(2)}Q${b.toFixed(2)},${(y + thick).toFixed(2)} ${(b - bend).toFixed(2)},${(y + thick).toFixed(2)}H${a.toFixed(2)}Z`;
|
|
228
|
+
};
|
|
229
|
+
return this.#segments(gene, matrix.samples.map((sample) => matrix.cell(gene, sample)), path, (count) => count / peak * barWidth);
|
|
230
|
+
});
|
|
231
|
+
this.#bars(ctx, "gene-bars", bars, (segment) => `${segment.subject}: ${segment.total} of ${plural(matrix.samples.length, "sample")}`);
|
|
232
|
+
}
|
|
233
|
+
#burdenBars(ctx, matrix, { genes, samples }, layout) {
|
|
234
|
+
const { matrix: box, bandHeight, columnWidth } = layout;
|
|
235
|
+
const { tracks } = this.options;
|
|
236
|
+
const shown = tracks === "both-bars" || tracks === "sample-burden" ? samples : [];
|
|
237
|
+
const peak = Math.max(1, ...samples.map((sample) => matrix.burden(sample)));
|
|
238
|
+
const across = OncoPrint.#inset(columnWidth), thick = Math.max(.5, columnWidth - across);
|
|
239
|
+
const bars = shown.flatMap((sample, i) => {
|
|
240
|
+
const x = box.x + i * columnWidth + across / 2, r = Math.min(4, thick / 2), floor = bandHeight;
|
|
241
|
+
const path = (from, to, end) => {
|
|
242
|
+
const a = floor - from, b = floor - to, bend = end ? Math.min(r, a - b) : 0;
|
|
243
|
+
return `M${x.toFixed(2)},${a.toFixed(2)}V${(b + bend).toFixed(2)}Q${x.toFixed(2)},${b.toFixed(2)} ${(x + bend).toFixed(2)},${b.toFixed(2)}H${(x + thick - bend).toFixed(2)}Q${(x + thick).toFixed(2)},${b.toFixed(2)} ${(x + thick).toFixed(2)},${(b + bend).toFixed(2)}V${a.toFixed(2)}Z`;
|
|
244
|
+
};
|
|
245
|
+
return this.#segments(sample, genes.map((gene) => matrix.cell(gene, sample)), path, (count) => count / peak * bandHeight);
|
|
246
|
+
});
|
|
247
|
+
this.#bars(ctx, "burden-bars", bars, (segment) => `${segment.subject}: ${plural(segment.total, "altered gene")}`);
|
|
248
|
+
}
|
|
249
|
+
#captions(ctx, matrix, { genes, samples }, layout) {
|
|
250
|
+
const { matrix: box, rowHeight, labelX, percentX, barX, barWidth, bandHeight } = layout;
|
|
251
|
+
const { tracks } = this.options;
|
|
252
|
+
const stride = Math.ceil(12 / rowHeight);
|
|
253
|
+
const named = genes.flatMap((gene, j) => {
|
|
254
|
+
if (j % stride) return [];
|
|
255
|
+
const y = box.y + (j + .5) * rowHeight + 4;
|
|
256
|
+
return [{
|
|
257
|
+
key: `gene-${gene}`,
|
|
258
|
+
text: clip(gene, labelX),
|
|
259
|
+
x: labelX,
|
|
260
|
+
y,
|
|
261
|
+
anchor: "end",
|
|
262
|
+
tone: "ink"
|
|
263
|
+
}, {
|
|
264
|
+
key: `share-${gene}`,
|
|
265
|
+
text: percent(matrix.frequency(gene)),
|
|
266
|
+
x: percentX,
|
|
267
|
+
y,
|
|
268
|
+
anchor: "end"
|
|
269
|
+
}];
|
|
270
|
+
});
|
|
271
|
+
const burden = tracks === "both-bars" || tracks === "sample-burden";
|
|
272
|
+
const peak = Math.max(1, ...samples.map((sample) => matrix.burden(sample)));
|
|
273
|
+
const scale = burden ? [
|
|
274
|
+
{
|
|
275
|
+
key: "burden-peak",
|
|
276
|
+
text: `${peak}`,
|
|
277
|
+
x: box.x - 6,
|
|
278
|
+
y: 10,
|
|
279
|
+
anchor: "end",
|
|
280
|
+
tone: "muted"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
key: "burden-zero",
|
|
284
|
+
text: "0",
|
|
285
|
+
x: box.x - 6,
|
|
286
|
+
y: bandHeight,
|
|
287
|
+
anchor: "end",
|
|
288
|
+
tone: "muted"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
key: "burden-title",
|
|
292
|
+
text: clip("Burden", box.x - 26),
|
|
293
|
+
x: box.x - 22,
|
|
294
|
+
y: bandHeight / 2 + 4,
|
|
295
|
+
anchor: "end"
|
|
296
|
+
}
|
|
297
|
+
] : [];
|
|
298
|
+
const altered = Math.max(0, ...genes.map((gene) => matrix.altered(gene)));
|
|
299
|
+
const side = barWidth ? [
|
|
300
|
+
{
|
|
301
|
+
key: "gene-peak",
|
|
302
|
+
text: `${altered}`,
|
|
303
|
+
x: barX + barWidth,
|
|
304
|
+
y: bandHeight - 2,
|
|
305
|
+
anchor: "end",
|
|
306
|
+
tone: "muted"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
key: "gene-zero",
|
|
310
|
+
text: "0",
|
|
311
|
+
x: barX,
|
|
312
|
+
y: bandHeight - 2,
|
|
313
|
+
anchor: "start",
|
|
314
|
+
tone: "muted"
|
|
315
|
+
},
|
|
316
|
+
...barWidth >= 84 ? [{
|
|
317
|
+
key: "gene-title",
|
|
318
|
+
text: "Samples",
|
|
319
|
+
x: barX + barWidth / 2,
|
|
320
|
+
y: bandHeight - 2,
|
|
321
|
+
anchor: "middle"
|
|
322
|
+
}] : []
|
|
323
|
+
] : [];
|
|
324
|
+
const texts = ctx.marks("captions", "text", [
|
|
325
|
+
...named,
|
|
326
|
+
...scale,
|
|
327
|
+
...side
|
|
328
|
+
], (caption) => caption.key);
|
|
329
|
+
ctx.text(texts).attr("text-anchor", (d) => d.anchor).attr("x", (d) => d.x).attr("y", (d) => d.y).text((d) => d.text).attr("fill", (d) => ({
|
|
330
|
+
ink: ctx.theme.ink,
|
|
331
|
+
secondary: ctx.theme.inkSecondary,
|
|
332
|
+
muted: ctx.theme.inkMuted
|
|
333
|
+
})[d.tone ?? "secondary"]).style("pointer-events", "none");
|
|
334
|
+
const rail = ctx.marks("rail", "line", barWidth ? [0] : []).attr("stroke", ctx.theme.axis).attr("stroke-width", 1);
|
|
335
|
+
ctx.set(rail, {
|
|
336
|
+
x1: barX,
|
|
337
|
+
x2: barX,
|
|
338
|
+
y1: box.y,
|
|
339
|
+
y2: box.y + box.height
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
#drawLegend(ctx, swatches, { legendY }) {
|
|
343
|
+
const backs = ctx.marks("legend-cells", "rect", swatches, (d) => d.type).attr("fill", ctx.theme.grid);
|
|
344
|
+
ctx.set(backs, {
|
|
345
|
+
x: (d) => d.x,
|
|
346
|
+
y: (d) => legendY + d.y,
|
|
347
|
+
width: SWATCH.width,
|
|
348
|
+
height: SWATCH.height
|
|
349
|
+
});
|
|
350
|
+
const marks = ctx.marks("legend-glyphs", "path", swatches, (d) => d.type).attr("fill", (d) => this.#fill(ctx, d.type));
|
|
351
|
+
ctx.set(marks, { d: (d) => OncoGlyph.path(d.type, d.x, legendY + d.y, SWATCH.width, SWATCH.height) });
|
|
352
|
+
const names = ctx.marks("legend-names", "text", swatches, (d) => d.type);
|
|
353
|
+
ctx.text(names).attr("x", (d) => d.x + SWATCH.width + 5).attr("y", (d) => legendY + d.y + 12).text((d) => d.text);
|
|
354
|
+
}
|
|
355
|
+
draw(ctx) {
|
|
356
|
+
this.#seat(ctx);
|
|
357
|
+
const matrix = this.#matrix(), order = this.#order(matrix);
|
|
358
|
+
if (!order.genes.length || !order.samples.length) {
|
|
359
|
+
ctx.root.selectAll("*").remove();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const present = OncoGlyph.order.filter((type) => order.genes.some((gene) => order.samples.some((sample) => matrix.cell(gene, sample)?.events.some((event) => event.type === type))));
|
|
363
|
+
const swatches = this.#legend(present, ctx.plot.width);
|
|
364
|
+
const legendHeight = swatches.length ? swatches.at(-1).y + LEGEND_ROW + 6 : 0;
|
|
365
|
+
const layout = this.#layout(ctx, order, legendHeight);
|
|
366
|
+
this.#cells(ctx, matrix, order, layout);
|
|
367
|
+
this.#frequencyBars(ctx, matrix, order, layout);
|
|
368
|
+
this.#burdenBars(ctx, matrix, order, layout);
|
|
369
|
+
this.#captions(ctx, matrix, order, layout);
|
|
370
|
+
this.#drawLegend(ctx, swatches, layout);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
export { OncoBars, OncoPrint, OncoTracks, OncoUnaltered };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { OncoGlyph, OncoLayer } from "./OncoGlyph.js";
|
|
2
|
+
// src/charts/lifescience/OncoPrintMatrix.ts
|
|
3
|
+
/** How genes and samples are ordered. */
|
|
4
|
+
var OncoSort = /* @__PURE__ */ function(OncoSort) {
|
|
5
|
+
/** Genes by frequency, then samples lexicographically on those genes, for the mutual-exclusivity staircase. */
|
|
6
|
+
OncoSort["Memo"] = "memo";
|
|
7
|
+
/** Genes by frequency, then samples by how many genes are altered. */
|
|
8
|
+
OncoSort["Burden"] = "burden";
|
|
9
|
+
/** Both as first seen in the rows. */
|
|
10
|
+
OncoSort["Input"] = "input";
|
|
11
|
+
return OncoSort;
|
|
12
|
+
}({});
|
|
13
|
+
var name = (cell) => cell === null || cell === void 0 || cell === "" ? null : String(cell);
|
|
14
|
+
var rank = (type) => OncoGlyph.order.indexOf(type);
|
|
15
|
+
/** Long-form alteration rows as a gene by sample matrix: frequencies, burden, and the orderings. */
|
|
16
|
+
var OncoPrintMatrix = class {
|
|
17
|
+
genes;
|
|
18
|
+
samples;
|
|
19
|
+
#cells = /* @__PURE__ */ new Map();
|
|
20
|
+
/** A row with a blank alteration, or a blank gene, only records that its sample was profiled. */
|
|
21
|
+
constructor(rows, { sample, gene, alteration }) {
|
|
22
|
+
const genes = /* @__PURE__ */ new Set(), samples = /* @__PURE__ */ new Set();
|
|
23
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
24
|
+
if (row === null || typeof row !== "object") continue;
|
|
25
|
+
const s = name(row[sample]), g = name(row[gene]);
|
|
26
|
+
if (s) samples.add(s);
|
|
27
|
+
if (g) genes.add(g);
|
|
28
|
+
const type = OncoGlyph.classify(row[alteration]);
|
|
29
|
+
if (s && g && type) this.#record(g, s, {
|
|
30
|
+
type,
|
|
31
|
+
text: String(row[alteration])
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
this.genes = [...genes];
|
|
35
|
+
this.samples = [...samples];
|
|
36
|
+
}
|
|
37
|
+
#record(gene, sample, event) {
|
|
38
|
+
let row = this.#cells.get(gene);
|
|
39
|
+
if (!row) this.#cells.set(gene, row = /* @__PURE__ */ new Map());
|
|
40
|
+
const cell = row.get(sample);
|
|
41
|
+
if (!cell) {
|
|
42
|
+
row.set(sample, {
|
|
43
|
+
gene,
|
|
44
|
+
sample,
|
|
45
|
+
events: [event],
|
|
46
|
+
primary: event.type
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (cell.events.some((other) => other.type === event.type && other.text === event.text)) return;
|
|
51
|
+
cell.events.push(event);
|
|
52
|
+
if (rank(event.type) < rank(cell.primary)) cell.primary = event.type;
|
|
53
|
+
}
|
|
54
|
+
cell(gene, sample) {
|
|
55
|
+
return this.#cells.get(gene)?.get(sample);
|
|
56
|
+
}
|
|
57
|
+
/** Samples with any alteration in `gene`. */
|
|
58
|
+
altered(gene) {
|
|
59
|
+
return this.#cells.get(gene)?.size ?? 0;
|
|
60
|
+
}
|
|
61
|
+
/** Genes altered in `sample`, among those plotted. */
|
|
62
|
+
burden(sample) {
|
|
63
|
+
return this.genes.reduce((sum, gene) => sum + (this.cell(gene, sample) ? 1 : 0), 0);
|
|
64
|
+
}
|
|
65
|
+
/** Share of the whole cohort, shown or not, with `gene` altered. */
|
|
66
|
+
frequency(gene) {
|
|
67
|
+
return this.samples.length ? this.altered(gene) / this.samples.length : 0;
|
|
68
|
+
}
|
|
69
|
+
/** The strongest event of each layer, bottom layer first: what a cell draws. */
|
|
70
|
+
static layers(cell) {
|
|
71
|
+
return Object.values(OncoLayer).flatMap((layer) => {
|
|
72
|
+
const inLayer = cell.events.filter((event) => OncoGlyph.layer(event.type) === layer);
|
|
73
|
+
return inLayer.length ? [inLayer.reduce((best, event) => rank(event.type) < rank(best.type) ? event : best)] : [];
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Samples are ordered, and may be hidden, but `frequency` always counts the whole cohort. */
|
|
77
|
+
arrange(sort, hideUnaltered = false) {
|
|
78
|
+
const byFrequency = [...this.genes].sort((a, b) => this.altered(b) - this.altered(a));
|
|
79
|
+
const shown = hideUnaltered ? this.samples.filter((sample) => this.burden(sample) > 0) : [...this.samples];
|
|
80
|
+
if (sort === "input") return {
|
|
81
|
+
genes: [...this.genes],
|
|
82
|
+
samples: shown
|
|
83
|
+
};
|
|
84
|
+
if (sort === "burden") return {
|
|
85
|
+
genes: byFrequency,
|
|
86
|
+
samples: shown.sort((a, b) => this.burden(b) - this.burden(a))
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
genes: byFrequency,
|
|
90
|
+
samples: this.#memo(byFrequency, shown)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* MEMo-style sort: the binary score Σ 2^(n-i) over genes in frequency order, decreasing. Comparing
|
|
95
|
+
* gene by gene gives the same order and, unlike the score, stays exact past 53 genes.
|
|
96
|
+
*/
|
|
97
|
+
#memo(genes, samples) {
|
|
98
|
+
return samples.sort((a, b) => {
|
|
99
|
+
for (const gene of genes) {
|
|
100
|
+
const left = this.cell(gene, a) ? 1 : 0, right = this.cell(gene, b) ? 1 : 0;
|
|
101
|
+
if (left !== right) return right - left;
|
|
102
|
+
}
|
|
103
|
+
return 0;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
export { OncoPrintMatrix, OncoSort };
|