@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,364 @@
1
+ import { CartesianChart } from "../../core/CartesianChart.js";
2
+ import { ChartFamily, DataShape, Dimension, LegendPosition, SymbolKind } from "../../core/enums.js";
3
+ import { Scales } from "../../core/Scales.js";
4
+ import { RamachandranClass, RamachandranPanel, RamachandranRegionStyle, RamachandranVerdict } from "./ramachandranEnums.js";
5
+ import { RamachandranRegions } from "./RamachandranRegions.js";
6
+ import { RamachandranSamples } from "./RamachandranSamples.js";
7
+ import { scaleLinear, symbol } from "d3";
8
+ // src/charts/lifescience/RamachandranPlot.ts
9
+ var FULL = 360;
10
+ var HALF = 180;
11
+ var MARK_AREA = 64;
12
+ var MARK_REACH = 7;
13
+ var LABEL_MOST = 8;
14
+ var LEGEND_ROW = 22;
15
+ var LEGEND_RAIL = 120;
16
+ var LEGEND_ITEM = 32;
17
+ var LEGEND_CHAR = 6.4;
18
+ var LEGEND_INSET = 24;
19
+ var CHAR = 6.2;
20
+ var THREE_LETTER = {
21
+ G: "GLY",
22
+ P: "PRO",
23
+ I: "ILE",
24
+ V: "VAL"
25
+ };
26
+ var GROUPS = {
27
+ [RamachandranClass.Glycine]: ["Glycine", SymbolKind.Triangle],
28
+ [RamachandranClass.TransProline]: ["Proline", SymbolKind.Square],
29
+ [RamachandranClass.CisProline]: ["Proline", SymbolKind.Square]
30
+ };
31
+ var CLASS_NAMES = {
32
+ [RamachandranClass.General]: "General",
33
+ [RamachandranClass.Glycine]: "Glycine",
34
+ [RamachandranClass.TransProline]: "trans-Proline",
35
+ [RamachandranClass.CisProline]: "cis-Proline",
36
+ [RamachandranClass.PreProline]: "Pre-proline",
37
+ [RamachandranClass.IleVal]: "Ile/Val"
38
+ };
39
+ var PANEL_CLASS = {
40
+ [RamachandranPanel.All]: RamachandranClass.General,
41
+ [RamachandranPanel.General]: RamachandranClass.General,
42
+ [RamachandranPanel.Glycine]: RamachandranClass.Glycine,
43
+ [RamachandranPanel.TransProline]: RamachandranClass.TransProline,
44
+ [RamachandranPanel.CisProline]: RamachandranClass.CisProline,
45
+ [RamachandranPanel.PreProline]: RamachandranClass.PreProline,
46
+ [RamachandranPanel.IleVal]: RamachandranClass.IleVal
47
+ };
48
+ var clips = 0;
49
+ /**
50
+ * Check a protein's backbone: phi against psi per residue, after Ramachandran, Ramakrishnan and
51
+ * Sasisekharan (1963), on the reference regions MolProbity validates with. Residues are classed and
52
+ * judged as MolProbity's `ramalyze` does: Gly, then cis or trans Pro by omega, then pre-Pro, then
53
+ * Ile/Val, then general. Both angles are periodic, so input wraps and a mark on an edge carries over
54
+ * to the opposite one. Regions are the Top8000 contours; see `RamachandranRegions` for provenance.
55
+ */
56
+ var RamachandranPlot = class RamachandranPlot extends CartesianChart {
57
+ static meta = {
58
+ id: "ramachandran-plot",
59
+ name: "Ramachandran plot",
60
+ family: ChartFamily.Scientific,
61
+ dimension: Dimension.Two,
62
+ shape: DataShape.Table,
63
+ description: "Plots backbone phi against psi per residue over the Top8000 favoured and allowed regions, marking glycine, proline and outliers.",
64
+ tags: [
65
+ "protein",
66
+ "structure",
67
+ "backbone",
68
+ "torsion",
69
+ "validation",
70
+ "molprobity"
71
+ ],
72
+ sample: (seed) => RamachandranSamples.protein(seed),
73
+ variants: {
74
+ panel: Object.values(RamachandranPanel),
75
+ regions: Object.values(RamachandranRegionStyle),
76
+ labelOutliers: [true, false]
77
+ }
78
+ };
79
+ #clipId = `ramachandran-clip-${++clips}`;
80
+ #outliers = false;
81
+ defaults() {
82
+ return {
83
+ phi: "phi",
84
+ psi: "psi",
85
+ residue: "residue",
86
+ nextResidue: "nextResidue",
87
+ omega: "omega",
88
+ label: "label",
89
+ xLabel: "φ (degrees)",
90
+ yLabel: "ψ (degrees)",
91
+ subtitle: RamachandranRegions.source,
92
+ panel: RamachandranPanel.All,
93
+ regions: RamachandranRegionStyle.Filled,
94
+ labelOutliers: true
95
+ };
96
+ }
97
+ /** Both axes span the same 360 degrees, so the plot stays square and one degree is one length. */
98
+ plotMargin() {
99
+ const base = super.plotMargin(), { width, height } = this.size, { title, subtitle } = this.options, side = this.legendPosition;
100
+ const wide = width - base.left - base.right - (side === LegendPosition.Right ? LEGEND_RAIL : 0);
101
+ const legend = side === LegendPosition.Top || side === LegendPosition.Bottom ? LEGEND_ROW * this.#legendRows(width) : 0;
102
+ const spare = wide - (height - base.top - base.bottom - (title ? 24 : 0) - (subtitle ? 18 : 0) - legend);
103
+ return spare >= 0 ? {
104
+ ...base,
105
+ left: base.left + spare / 2,
106
+ right: base.right + spare / 2
107
+ } : {
108
+ ...base,
109
+ bottom: base.bottom - spare
110
+ };
111
+ }
112
+ /** The same greedy fill the legend uses, so a wrapped legend does not cost the plot its shape. */
113
+ #legendRows(width) {
114
+ let rows = 1, at = 0;
115
+ for (const { key, label } of this.legendItems()) {
116
+ const cost = LEGEND_ITEM + (label ?? key).length * LEGEND_CHAR;
117
+ if (at && at + cost > width - LEGEND_INSET) {
118
+ rows++;
119
+ at = 0;
120
+ }
121
+ at += cost;
122
+ }
123
+ return rows;
124
+ }
125
+ /** Wraps to -180..180. An exact -180 stays put, since it is the same angle as 180. */
126
+ static wrap(angle) {
127
+ return angle >= -180 && angle <= HALF ? angle : angle - FULL * Math.ceil((angle - HALF) / FULL);
128
+ }
129
+ /** `ramalyze`'s precedence. `next` matters only for residues that are neither glycine nor proline. */
130
+ static classOf(residue, next, omega) {
131
+ const name = RamachandranPlot.#code(residue);
132
+ if (name === "GLY") return RamachandranClass.Glycine;
133
+ if (name === "PRO") return omega !== null && Math.abs(RamachandranPlot.wrap(omega)) < 90 ? RamachandranClass.CisProline : RamachandranClass.TransProline;
134
+ if (RamachandranPlot.#code(next) === "PRO") return RamachandranClass.PreProline;
135
+ return name === "ILE" || name === "VAL" ? RamachandranClass.IleVal : RamachandranClass.General;
136
+ }
137
+ static #code(name) {
138
+ const code = name.trim().toUpperCase();
139
+ return THREE_LETTER[code] ?? code.slice(0, 3);
140
+ }
141
+ static #number(cell) {
142
+ if (cell === "" || cell === null || cell === void 0 || cell instanceof Date) return null;
143
+ const value = Number(cell);
144
+ return Number.isFinite(value) ? value : null;
145
+ }
146
+ #residues() {
147
+ const { data, phi: phiKey, psi: psiKey, residue, nextResidue, omega, label, panel } = this.options;
148
+ const shown = panel === RamachandranPanel.All ? null : PANEL_CLASS[panel];
149
+ return (Array.isArray(data) ? data : []).flatMap((row, i) => {
150
+ const phiRaw = RamachandranPlot.#number(row?.[phiKey]), psiRaw = RamachandranPlot.#number(row?.[psiKey]);
151
+ if (phiRaw === null || psiRaw === null) return [];
152
+ const phi = RamachandranPlot.wrap(phiRaw), psi = RamachandranPlot.wrap(psiRaw);
153
+ const name = String(row[residue] ?? "").trim();
154
+ const kind = RamachandranPlot.classOf(name, String(row[nextResidue] ?? ""), RamachandranPlot.#number(row[omega]));
155
+ if (shown && kind !== shown) return [];
156
+ const [group, glyph] = GROUPS[kind] ?? ["General", SymbolKind.Circle];
157
+ return [{
158
+ key: `${i}`,
159
+ row,
160
+ name,
161
+ label: String(row[label] ?? name),
162
+ phi,
163
+ psi,
164
+ kind,
165
+ group,
166
+ glyph,
167
+ verdict: RamachandranRegions.classify(kind, phi, psi)
168
+ }];
169
+ });
170
+ }
171
+ scales(ctx) {
172
+ const x = scaleLinear([-180, HALF], [0, ctx.plot.width]), y = scaleLinear([-180, HALF], [ctx.plot.height, 0]);
173
+ const step = ctx.plot.width >= 196 ? 60 : 90;
174
+ const ticks = Array.from({ length: FULL / step + 1 }, (_, i) => -180 + i * step);
175
+ Object.assign(x, { ticks: () => ticks });
176
+ Object.assign(y, { ticks: () => ticks });
177
+ return {
178
+ x,
179
+ y
180
+ };
181
+ }
182
+ legendItems() {
183
+ const { panel } = this.options, kind = PANEL_CLASS[panel];
184
+ return [
185
+ {
186
+ key: "favoured",
187
+ label: RamachandranRegions.favouredLabel(),
188
+ color: this.#tone(RamachandranVerdict.Favoured)
189
+ },
190
+ {
191
+ key: "allowed",
192
+ label: RamachandranRegions.allowedLabel(kind),
193
+ color: this.#tone(RamachandranVerdict.Allowed)
194
+ },
195
+ ...panel === RamachandranPanel.All ? this.palette.items() : [],
196
+ ...this.#outliers ? [{
197
+ key: "outlier",
198
+ label: "Outlier",
199
+ color: this.theme.critical
200
+ }] : []
201
+ ];
202
+ }
203
+ #tone(verdict) {
204
+ return this.palette.sequential(verdict === RamachandranVerdict.Favoured ? .3 : .03);
205
+ }
206
+ /** Filled regions close along the plot's edge, but that is where the map wraps, so a contour leaves those runs out. */
207
+ #regionPath(rings, x, y) {
208
+ const at = ([phi, psi]) => `${(x(phi) ?? 0).toFixed(1)},${(y(psi) ?? 0).toFixed(1)}`;
209
+ if (this.options.regions === RamachandranRegionStyle.Filled) return rings.map((ring) => `M${ring.map(at).join("L")}Z`).join("");
210
+ const edge = (a, b) => Math.abs(a[0]) === HALF && a[0] === b[0] || Math.abs(a[1]) === HALF && a[1] === b[1];
211
+ return rings.map((ring) => ring.map((point, i) => {
212
+ const before = ring[(i + ring.length - 1) % ring.length];
213
+ return `${i && !edge(before, point) ? "L" : "M"}${at(point)}`;
214
+ }).join("")).join("");
215
+ }
216
+ #drawRegions(ctx, { x, y }) {
217
+ const kind = PANEL_CLASS[this.options.panel], filled = this.options.regions === RamachandranRegionStyle.Filled;
218
+ const levels = [RamachandranVerdict.Allowed, RamachandranVerdict.Favoured].map((verdict) => ({
219
+ verdict,
220
+ d: this.#regionPath(RamachandranRegions.rings(kind, verdict === RamachandranVerdict.Favoured ? "favoured" : "allowed"), x, y)
221
+ }));
222
+ const shapes = ctx.marks("regions", "path", levels, (d) => d.verdict).attr("pointer-events", "none").attr("fill-rule", "evenodd").attr("fill", (d) => filled ? this.#tone(d.verdict) : "none").attr("stroke", (d) => filled ? "none" : ctx.palette.sequential(d.verdict === RamachandranVerdict.Favoured ? .75 : .4)).attr("stroke-width", filled ? null : 2);
223
+ ctx.set(shapes, {
224
+ d: (d) => d.d,
225
+ "fill-opacity": 1
226
+ }, { "fill-opacity": 0 });
227
+ }
228
+ #drawRules(ctx, { x, y }) {
229
+ const { width, height } = ctx.plot;
230
+ const verticals = x.ticks().filter((tick) => tick !== 0);
231
+ const lines = ctx.marks("phi-grid", "line", this.options.grid === false ? [] : verticals, (tick) => tick).attr("stroke", ctx.theme.grid).attr("stroke-width", 1);
232
+ ctx.set(lines, {
233
+ x1: (tick) => x(tick) ?? 0,
234
+ x2: (tick) => x(tick) ?? 0,
235
+ y1: 0,
236
+ y2: height
237
+ });
238
+ const zero = [{
239
+ key: "phi",
240
+ x1: x(0) ?? 0,
241
+ x2: x(0) ?? 0,
242
+ y1: 0,
243
+ y2: height
244
+ }, {
245
+ key: "psi",
246
+ x1: 0,
247
+ x2: width,
248
+ y1: y(0) ?? 0,
249
+ y2: y(0) ?? 0
250
+ }];
251
+ const axes = ctx.marks("zero-lines", "line", zero, (d) => d.key).attr("stroke", ctx.theme.axis).attr("stroke-width", 1).attr("pointer-events", "none");
252
+ ctx.set(axes, {
253
+ x1: (d) => d.x1,
254
+ x2: (d) => d.x2,
255
+ y1: (d) => d.y1,
256
+ y2: (d) => d.y2
257
+ });
258
+ }
259
+ /** The torus repeats at the edges: a mark that crosses one is drawn again, unhovered, where it comes back in. */
260
+ #copies(marks, { x, y }) {
261
+ const across = (at, low, high) => at < low + MARK_REACH ? high - low : at > high - MARK_REACH ? low - high : 0;
262
+ const left = x(-180) ?? 0, right = x(HALF) ?? 0, top = y(HALF) ?? 0, bottom = y(-180) ?? 0;
263
+ return marks.flatMap((mark) => {
264
+ const dx = across(mark.px, left, right), dy = across(mark.py, top, bottom);
265
+ return (dx && dy ? [
266
+ [dx, 0],
267
+ [0, dy],
268
+ [dx, dy]
269
+ ] : dx || dy ? [[dx, dy]] : []).map(([sideways, upward]) => ({
270
+ ...mark,
271
+ key: `${mark.key}:${sideways}:${upward}`,
272
+ px: mark.px + sideways,
273
+ py: mark.py + upward
274
+ }));
275
+ });
276
+ }
277
+ #drawMarks(ctx, marks, axes) {
278
+ const { width, height } = ctx.plot;
279
+ const holder = ctx.group("residues");
280
+ ctx.marks("residue-clip", "clipPath", [0], void 0, holder).attr("id", this.#clipId).selectAll("rect").data([0]).join("rect").attr("width", width).attr("height", height);
281
+ const inner = ctx.group("residue-marks", holder).attr("clip-path", `url(#${this.#clipId})`);
282
+ const paint = (d) => d.verdict === RamachandranVerdict.Outlier ? ctx.theme.critical : ctx.palette.color(d.group);
283
+ const glyph = (d) => symbol(Scales.symbol(d.glyph), MARK_AREA)() ?? "";
284
+ const place = (d) => `translate(${d.px.toFixed(1)},${d.py.toFixed(1)})`;
285
+ for (const mark of marks) if (mark.verdict !== RamachandranVerdict.Outlier) paint(mark);
286
+ const draw = (name, list) => ctx.marks(name, "path", list, (d) => d.key, inner).attr("fill", paint).attr("stroke", ctx.theme.surface).attr("stroke-width", 2);
287
+ const copies = draw("residue-copies", this.#copies(marks, axes)).attr("pointer-events", "none");
288
+ ctx.set(copies, {
289
+ d: glyph,
290
+ transform: place
291
+ });
292
+ const shown = draw("residue-points", [...marks].sort((a, b) => +(a.verdict === RamachandranVerdict.Outlier) - +(b.verdict === RamachandranVerdict.Outlier)));
293
+ ctx.set(shown, {
294
+ d: glyph,
295
+ transform: place,
296
+ "fill-opacity": 1
297
+ }, { "fill-opacity": 0 }, Math.min(2, 300 / (marks.length || 1)));
298
+ ctx.hover(shown, (d) => ({
299
+ title: d.label || CLASS_NAMES[d.kind],
300
+ color: paint(d),
301
+ rows: [
302
+ ["φ", `${d.phi}°`],
303
+ ["ψ", `${d.psi}°`],
304
+ ["Reference map", CLASS_NAMES[d.kind]],
305
+ ["Region", d.verdict]
306
+ ]
307
+ }));
308
+ }
309
+ /** Greedy, on whichever side has room. A label that would touch another label or an outlier is dropped. */
310
+ #drawLabels(ctx, marks) {
311
+ const { width } = ctx.plot;
312
+ const boxes = marks.filter((d) => d.verdict === RamachandranVerdict.Outlier).map((d) => [
313
+ d.px - 5,
314
+ d.py - 5,
315
+ 10,
316
+ 10
317
+ ]);
318
+ const free = (box) => box[0] >= 0 && box[0] + box[2] <= width && !boxes.some((b) => b[0] < box[0] + box[2] && box[0] < b[0] + b[2] && b[1] < box[1] + box[3] && box[1] < b[1] + b[3]);
319
+ let placed = 0;
320
+ const inPlot = (d) => d.px >= 0 && d.px <= width && d.py >= 0 && d.py <= ctx.plot.height;
321
+ const kept = (this.options.labelOutliers ? marks.filter((d) => d.verdict === RamachandranVerdict.Outlier && d.label && inPlot(d)) : []).flatMap((d) => {
322
+ const room = Math.floor(width / 3 / CHAR);
323
+ const text = d.label.length > room ? `${d.label.slice(0, Math.max(1, room - 1))}…` : d.label;
324
+ const wide = text.length * CHAR, base = Math.max(11, d.py + 4);
325
+ const right = [
326
+ d.px + 7,
327
+ base - 10,
328
+ wide,
329
+ 12
330
+ ], left = [
331
+ d.px - 7 - wide,
332
+ base - 10,
333
+ wide,
334
+ 12
335
+ ];
336
+ const box = placed >= LABEL_MOST ? null : free(right) ? right : free(left) ? left : null;
337
+ if (!box) return [];
338
+ boxes.push(box);
339
+ placed++;
340
+ return [{
341
+ key: d.key,
342
+ text,
343
+ x: box === right ? d.px + 7 : d.px - 7,
344
+ y: base,
345
+ anchor: box === right ? "start" : "end"
346
+ }];
347
+ });
348
+ ctx.text(ctx.marks("outlier-labels", "text", kept, (d) => d.key), 10, "ink").attr("pointer-events", "none").attr("text-anchor", (d) => d.anchor).attr("x", (d) => d.x).attr("y", (d) => d.y).text((d) => d.text);
349
+ }
350
+ plotMarks(ctx, axes) {
351
+ const { x, y } = axes;
352
+ const marks = this.#residues().map((residue) => ({
353
+ ...residue,
354
+ px: x(residue.phi) ?? 0,
355
+ py: y(residue.psi) ?? 0
356
+ }));
357
+ this.#outliers = marks.some((mark) => mark.verdict === RamachandranVerdict.Outlier);
358
+ this.#drawRegions(ctx, axes);
359
+ this.#drawRules(ctx, axes);
360
+ this.#drawMarks(ctx, marks, axes);
361
+ this.#drawLabels(ctx, marks);
362
+ }
363
+ };
364
+ export { RamachandranPlot };