@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.
Files changed (238) hide show
  1. package/README.md +111 -14
  2. package/dist/ExcelWriter-3ZSYUH4T.cjs +154 -0
  3. package/dist/GraphType.js +24 -38
  4. package/dist/PdfWriter-6HJFYMCO.cjs +205 -0
  5. package/dist/catalog-YWX6FHRM.cjs +8 -0
  6. package/dist/catalog.js +5 -1
  7. package/dist/charts/bars/index.js +2 -12
  8. package/dist/charts/comparison/EffectForestPlot.js +1 -1
  9. package/dist/charts/comparison/index.js +1 -8
  10. package/dist/charts/demography/Nomogram.js +3 -3
  11. package/dist/charts/density/HexbinChart.js +6 -4
  12. package/dist/charts/diagrams/ArchitectureCanvas.js +68 -0
  13. package/dist/charts/diagrams/ArchitectureDiagram.js +331 -0
  14. package/dist/charts/diagrams/ArchitectureLayout.js +106 -0
  15. package/dist/charts/diagrams/ArchitectureLinkLabels.js +78 -0
  16. package/dist/charts/diagrams/ArchitectureModel.js +108 -0
  17. package/dist/charts/diagrams/ArchitectureRouter.js +348 -0
  18. package/dist/charts/diagrams/ArchitectureSamples.js +312 -0
  19. package/dist/charts/diagrams/ArchitectureShapes.js +130 -0
  20. package/dist/charts/diagrams/ArchitectureText.js +25 -0
  21. package/dist/charts/diagrams/DiagramText.js +38 -0
  22. package/dist/charts/diagrams/EntityRelationshipDiagram.js +465 -0
  23. package/dist/charts/diagrams/ErCrowsFoot.js +33 -0
  24. package/dist/charts/diagrams/ErSamples.js +205 -0
  25. package/dist/charts/diagrams/ErSchema.js +122 -0
  26. package/dist/charts/diagrams/LogicCircuit.js +154 -0
  27. package/dist/charts/diagrams/LogicCircuitLayout.js +600 -0
  28. package/dist/charts/diagrams/LogicCircuitSamples.js +66 -0
  29. package/dist/charts/diagrams/LogicGateDiagram.js +216 -0
  30. package/dist/charts/diagrams/LogicGateSymbol.js +113 -0
  31. package/dist/charts/diagrams/NetworkTopologyDiagram.js +247 -0
  32. package/dist/charts/diagrams/SequenceDiagram.js +351 -0
  33. package/dist/charts/diagrams/SequenceLayout.js +422 -0
  34. package/dist/charts/diagrams/SequenceModel.js +221 -0
  35. package/dist/charts/diagrams/TopologyLayout.js +143 -0
  36. package/dist/charts/diagrams/architectureEnums.js +50 -0
  37. package/dist/charts/diagrams/index.js +26 -0
  38. package/dist/charts/diagrams/logicEnums.js +42 -0
  39. package/dist/charts/diagrams/sequenceEnums.js +40 -0
  40. package/dist/charts/distribution/HistogramChart.js +25 -3
  41. package/dist/charts/distribution/index.js +1 -3
  42. package/dist/charts/earthscience/HovmollerDiagram.js +477 -0
  43. package/dist/charts/earthscience/HovmollerEnums.js +42 -0
  44. package/dist/charts/earthscience/HovmollerField.js +180 -0
  45. package/dist/charts/earthscience/HovmollerSample.js +40 -0
  46. package/dist/charts/earthscience/HovmollerSpeed.js +71 -0
  47. package/dist/charts/earthscience/index.js +10 -2
  48. package/dist/charts/engineering/ConstellationDiagram.js +26 -6
  49. package/dist/charts/engineering/EyeDiagram.js +2 -1
  50. package/dist/charts/engineering/PoleZeroPlot.js +32 -8
  51. package/dist/charts/financial/RelativeStrengthRotation.js +434 -0
  52. package/dist/charts/financial/RelativeStrengthRotationMath.js +71 -0
  53. package/dist/charts/financial/RelativeStrengthRotationSamples.js +27 -0
  54. package/dist/charts/financial/index.js +5 -3
  55. package/dist/charts/flow/index.js +1 -5
  56. package/dist/charts/frameworks/AnsoffMatrix.js +523 -0
  57. package/dist/charts/frameworks/FrameworkText.js +98 -0
  58. package/dist/charts/frameworks/NpsGauge.js +279 -0
  59. package/dist/charts/frameworks/NpsSurvey.js +58 -0
  60. package/dist/charts/frameworks/PerceptualMap.js +419 -0
  61. package/dist/charts/frameworks/PerceptualProjection.js +142 -0
  62. package/dist/charts/frameworks/PositioningSamples.js +191 -0
  63. package/dist/charts/frameworks/RaciMatrix.js +458 -0
  64. package/dist/charts/frameworks/RaciPlan.js +81 -0
  65. package/dist/charts/frameworks/RaciSamples.js +76 -0
  66. package/dist/charts/frameworks/SwotMatrix.js +358 -0
  67. package/dist/charts/frameworks/SwotSamples.js +85 -0
  68. package/dist/charts/frameworks/VanWestendorpChart.js +187 -0
  69. package/dist/charts/frameworks/VanWestendorpSurvey.js +197 -0
  70. package/dist/charts/frameworks/index.js +26 -0
  71. package/dist/charts/frameworks/raciEnums.js +30 -0
  72. package/dist/charts/frameworks/swotEnums.js +34 -0
  73. package/dist/charts/genomics/CircosPlot.js +1 -1
  74. package/dist/charts/geoflows/GeoFlowChart.js +2 -2
  75. package/dist/charts/geoflows/GeoFlowSample.js +2 -1
  76. package/dist/charts/geomaps/FloorPlanMap.js +324 -0
  77. package/dist/charts/geomaps/FloorPlanModel.js +179 -0
  78. package/dist/charts/geomaps/FloorPlanSamples.js +416 -0
  79. package/dist/charts/geomaps/GeoLand.js +2 -2
  80. package/dist/charts/geomaps/GeoSamples.js +2 -1
  81. package/dist/charts/geomaps/GraticuleMap.js +2 -2
  82. package/dist/charts/geomaps/RoomLabelFit.js +200 -0
  83. package/dist/charts/geomaps/TissotMap.js +2 -2
  84. package/dist/charts/geomaps/index.js +8 -4
  85. package/dist/charts/hierarchy/SunburstChart.js +17 -10
  86. package/dist/charts/hierarchy/index.js +2 -11
  87. package/dist/charts/indicators/BulletChart.js +8 -2
  88. package/dist/charts/indicators/SpeedometerChart.js +1 -1
  89. package/dist/charts/indicators/ThermometerChart.js +1 -1
  90. package/dist/charts/lifescience/LoessFit.js +113 -0
  91. package/dist/charts/lifescience/MaPlot.js +337 -0
  92. package/dist/charts/lifescience/MaPlotSamples.js +35 -0
  93. package/dist/charts/lifescience/OncoGlyph.js +137 -0
  94. package/dist/charts/lifescience/OncoPrint.js +373 -0
  95. package/dist/charts/lifescience/OncoPrintMatrix.js +107 -0
  96. package/dist/charts/lifescience/OncoPrintSamples.js +142 -0
  97. package/dist/charts/lifescience/PedigreeChart.js +306 -0
  98. package/dist/charts/lifescience/PedigreeLayout.js +261 -0
  99. package/dist/charts/lifescience/PedigreeRows.js +70 -0
  100. package/dist/charts/lifescience/PedigreeSamples.js +161 -0
  101. package/dist/charts/lifescience/PedigreeSymbol.js +47 -0
  102. package/dist/charts/lifescience/RamachandranPlot.js +364 -0
  103. package/dist/charts/lifescience/RamachandranRegions.js +2712 -0
  104. package/dist/charts/lifescience/RamachandranSamples.js +235 -0
  105. package/dist/charts/lifescience/index.js +25 -0
  106. package/dist/charts/lifescience/pedigreeEnums.js +21 -0
  107. package/dist/charts/lifescience/ramachandranEnums.js +34 -0
  108. package/dist/charts/linesareas/DualAxisChart.js +1 -1
  109. package/dist/charts/linesareas/LineEnums.js +1 -15
  110. package/dist/charts/linesareas/MultiLineChart.js +1 -1
  111. package/dist/charts/linesareas/TrendChart.js +15 -5
  112. package/dist/charts/linesareas/index.js +2 -6
  113. package/dist/charts/massive/MassiveChart.js +3 -2
  114. package/dist/charts/mathematical/FourierSeriesChart.js +7 -1
  115. package/dist/charts/mathematical/index.js +2 -6
  116. package/dist/charts/matrix/GridPlot.js +27 -2
  117. package/dist/charts/matrix/TilegramChart.js +2 -2
  118. package/dist/charts/mlcharts/AttentionGrid.js +79 -0
  119. package/dist/charts/mlcharts/AttentionHeatmap.js +329 -0
  120. package/dist/charts/mlcharts/AttentionModel.js +132 -0
  121. package/dist/charts/mlcharts/AttentionSamples.js +82 -0
  122. package/dist/charts/mlcharts/CellNumber.js +8 -0
  123. package/dist/charts/mlcharts/CurveDiagnosticSamples.js +95 -0
  124. package/dist/charts/mlcharts/CurvePickMark.js +31 -0
  125. package/dist/charts/mlcharts/DecisionBoundaryChart.js +316 -0
  126. package/dist/charts/mlcharts/DecisionSamples.js +60 -0
  127. package/dist/charts/mlcharts/DecisionSurface.js +130 -0
  128. package/dist/charts/mlcharts/DistinctTicks.js +13 -0
  129. package/dist/charts/mlcharts/ElbowMath.js +153 -0
  130. package/dist/charts/mlcharts/ElbowPlot.js +273 -0
  131. package/dist/charts/mlcharts/ExplainabilitySamples.js +70 -0
  132. package/dist/charts/mlcharts/FeatureImportanceChart.js +150 -0
  133. package/dist/charts/mlcharts/ImportanceRanking.js +98 -0
  134. package/dist/charts/mlcharts/LearningCurveChart.js +276 -0
  135. package/dist/charts/mlcharts/LearningCurveMath.js +92 -0
  136. package/dist/charts/mlcharts/PartialDependence.js +92 -0
  137. package/dist/charts/mlcharts/PartialDependencePlot.js +225 -0
  138. package/dist/charts/mlcharts/WideLabel.js +28 -0
  139. package/dist/charts/mlcharts/curveDiagnosticEnums.js +37 -0
  140. package/dist/charts/mlcharts/index.js +32 -0
  141. package/dist/charts/proportion/index.js +5 -9
  142. package/dist/charts/scatter/CategoryScatterBase.js +1 -4
  143. package/dist/charts/scatter/ConnectedScatter.js +7 -4
  144. package/dist/charts/scatter/MarginalScatter.js +13 -8
  145. package/dist/charts/scatter/index.js +4 -17
  146. package/dist/charts/scientific/SpectrogramChart.js +9 -3
  147. package/dist/charts/scientific/index.js +2 -2
  148. package/dist/charts/signals/index.js +3 -13
  149. package/dist/charts/statistics/LorenzCurve.js +7 -4
  150. package/dist/charts/threebars/Candlestick3DChart.js +18 -2
  151. package/dist/charts/threeglobe/GlobeChart.js +2 -2
  152. package/dist/charts/threeglobe/GlobeSamples.js +2 -1
  153. package/dist/charts/threeglobe/RingGlobe.js +1 -1
  154. package/dist/charts/threenetwork/ConeTreeChart.js +2 -1
  155. package/dist/charts/threenetwork/Gantt3DChart.js +3 -2
  156. package/dist/charts/threesurfaces/Ridgeline3DChart.js +22 -4
  157. package/dist/charts/threevolume/LorenzAttractor.js +19 -0
  158. package/dist/charts/threevolume/VolumeRaycastChart.js +4 -3
  159. package/dist/charts/threevolume/volumeEnums.js +1 -0
  160. package/dist/charts/timeline/index.js +2 -10
  161. package/dist/chunk-753UBLV2.cjs +33 -0
  162. package/dist/chunk-VX54A7KA.cjs +17 -0
  163. package/dist/{chunk-W5GAAAZY.cjs → chunk-WAODDPYX.cjs} +24874 -17620
  164. package/dist/chunk-Z3JPIJSC.cjs +338 -0
  165. package/dist/core/CartesianChart.js +87 -11
  166. package/dist/core/Chart.js +63 -15
  167. package/dist/core/Chart2D.js +54 -17
  168. package/dist/core/Chart3D.js +56 -20
  169. package/dist/core/ChartExportError.js +2 -0
  170. package/dist/core/ChartExporter.js +50 -8
  171. package/dist/core/ColumnCheck.js +53 -0
  172. package/dist/core/Context2D.js +29 -15
  173. package/dist/core/Context3D.js +7 -2
  174. package/dist/core/ExcelWriter.js +152 -0
  175. package/dist/core/ExportText.js +28 -0
  176. package/dist/core/MarkOrigin.js +88 -0
  177. package/dist/core/MarkStyle.js +294 -0
  178. package/dist/core/Palette.js +27 -7
  179. package/dist/core/PdfPage.js +13 -0
  180. package/dist/core/PdfWriter.js +245 -0
  181. package/dist/core/Scales.js +52 -2
  182. package/dist/core/SceneOverlay.js +4 -4
  183. package/dist/core/TextFit.js +37 -0
  184. package/dist/core/Theme.js +28 -1
  185. package/dist/core/enums.js +33 -1
  186. package/dist/core/pathMorph.js +15 -0
  187. package/dist/data/SampleData.js +1 -17
  188. package/dist/data/WorldSample.js +23 -0
  189. package/dist/index.cjs +810 -691
  190. package/dist/index.d.cts +8390 -2925
  191. package/dist/index.d.ts +8392 -2927
  192. package/dist/index.js +106 -56
  193. package/dist/react/ChartView.js +18 -8
  194. package/dist/react/GraphHandle.js +2 -0
  195. package/dist/react/components.js +74 -116
  196. package/package.json +13 -3
  197. package/dist/catalog-4UP2YFNR.cjs +0 -7
  198. package/dist/charts/bars/BulletBar.js +0 -184
  199. package/dist/charts/bars/ErrorBarColumn.js +0 -147
  200. package/dist/charts/bars/HistogramBar.js +0 -217
  201. package/dist/charts/bars/MarimekkoBar.js +0 -191
  202. package/dist/charts/bars/ParetoBar.js +0 -143
  203. package/dist/charts/comparison/PanelChart.js +0 -70
  204. package/dist/charts/comparison/SmallMultipleBars.js +0 -134
  205. package/dist/charts/comparison/SpanChart.js +0 -118
  206. package/dist/charts/comparison/TrellisChart.js +0 -158
  207. package/dist/charts/distribution/RidgeSlabs3DChart.js +0 -169
  208. package/dist/charts/financial/Candlestick3D.js +0 -157
  209. package/dist/charts/flow/BumpFlow.js +0 -212
  210. package/dist/charts/flow/StreamFlow.js +0 -174
  211. package/dist/charts/geomaps/ThiessenMap.js +0 -106
  212. package/dist/charts/hierarchy/ConeTree3D.js +0 -135
  213. package/dist/charts/hierarchy/Hierarchy3DChart.js +0 -27
  214. package/dist/charts/hierarchy/SpherePack3D.js +0 -81
  215. package/dist/charts/hierarchy/SunburstTower3D.js +0 -93
  216. package/dist/charts/hierarchy/TreemapSkyline3D.js +0 -98
  217. package/dist/charts/linesareas/ConnectedScatterTime.js +0 -206
  218. package/dist/charts/linesareas/SmallMultipleLines.js +0 -153
  219. package/dist/charts/mathematical/ParametricSurface3D.js +0 -234
  220. package/dist/charts/mathematical/StrangeAttractor3D.js +0 -251
  221. package/dist/charts/proportion/ConeFunnel.js +0 -126
  222. package/dist/charts/proportion/ExtrudedPieChart.js +0 -123
  223. package/dist/charts/scatter/BeeswarmScatter.js +0 -151
  224. package/dist/charts/scatter/BinnedScatterBase.js +0 -51
  225. package/dist/charts/scatter/ContourScatter.js +0 -116
  226. package/dist/charts/scatter/GridBinScatter.js +0 -98
  227. package/dist/charts/scatter/HexbinScatter.js +0 -74
  228. package/dist/charts/scatter/StripScatter.js +0 -107
  229. package/dist/charts/scatter/SunflowerScatter.js +0 -109
  230. package/dist/charts/signals/ConstellationChart.js +0 -153
  231. package/dist/charts/signals/CorrelogramChart.js +0 -171
  232. package/dist/charts/signals/EyeDiagramChart.js +0 -155
  233. package/dist/charts/signals/PoleZeroChart.js +0 -164
  234. package/dist/charts/signals/SpectrogramChart.js +0 -79
  235. package/dist/charts/timeline/CalendarSkylineChart.js +0 -128
  236. package/dist/charts/timeline/ScheduleBlocksChart.js +0 -119
  237. package/dist/charts/timeline/SpiralTimeline.js +0 -154
  238. 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 };