@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
@@ -4,7 +4,7 @@ import { HierarchyMeasure, HierarchySort, PolarSweep, SunburstRings, countsKeepD
4
4
  import { polarFrame } from "./PolarFrame.js";
5
5
  import { TreeSamples } from "./TreeSamples.js";
6
6
  import { TreeStyle } from "./TreeStyle.js";
7
- import { arc, partition } from "d3";
7
+ import { arc, easeCubicOut, interpolateNumber, partition } from "d3";
8
8
  // src/charts/hierarchy/SunburstChart.ts
9
9
  var degrees = (radians) => radians * 180 / Math.PI;
10
10
  /** Concentric rings of arcs: for following shares outward through a few levels of a hierarchy. */
@@ -59,17 +59,10 @@ var SunburstChart = class extends HierarchyChart {
59
59
  const outer = (node) => this.ring(node.depth, levels, frame.radius) - 1;
60
60
  const start = (node) => node.x0 + frame.start;
61
61
  const end = (node) => node.x1 + frame.start;
62
- const shape = arc().startAngle(start).endAngle(end).innerRadius(inner).outerRadius(outer).padAngle(4 / frame.radius).padRadius(frame.radius / 2).cornerRadius(3);
63
- const closed = arc().startAngle(start).endAngle((node) => start(node) + .001).innerRadius(inner).outerRadius(outer);
62
+ const shape = (node, from, to) => arc().startAngle(from).endAngle(to).innerRadius(inner).outerRadius(outer).padAngle(4 / frame.radius).padRadius(frame.radius / 2).cornerRadius(3)(node) ?? "";
64
63
  const nodes = root.descendants().filter((node) => node.depth > 0 && node.x1 > node.x0);
65
64
  const arcs = stage.marks("arcs", "path", nodes, (node) => node.data.id).attr("fill", (node) => this.style.fill(node));
66
- stage.set(arcs, {
67
- d: shape,
68
- opacity: 1
69
- }, {
70
- d: closed,
71
- opacity: 0
72
- }, this.stagger(arcs, 5));
65
+ this.sweep(arcs, shape, start, end, frame.start);
73
66
  this.interact(stage, arcs, (node) => node);
74
67
  const notes = nodes.flatMap((node) => this.arcLabel(node, start(node), end(node), inner(node), outer(node)));
75
68
  if (this.options.hole >= .3 && frame.radius * this.options.hole > 24 && this.options.sweep !== PolarSweep.Half) notes.push({
@@ -85,6 +78,20 @@ var SunburstChart = class extends HierarchyChart {
85
78
  });
86
79
  this.labels(stage, "names", notes);
87
80
  }
81
+ /** Path text cannot tween through arc flags, so every arc opens by angle from `rest`, the whole ring as one fan; later paints move on from the angles held. */
82
+ sweep(arcs, shape, start, end, rest) {
83
+ if (!this.animated) {
84
+ arcs.interrupt().attr("d", (node) => shape(node, start(node), end(node))).property("__angles", (node) => [start(node), end(node)]);
85
+ return;
86
+ }
87
+ arcs.transition().duration(650).ease(easeCubicOut).attrTween("d", function(node) {
88
+ const held = this;
89
+ const [from, to] = held.__angles ?? [rest, rest];
90
+ const a0 = interpolateNumber(from, start(node)), a1 = interpolateNumber(to, end(node));
91
+ held.__angles = [start(node), end(node)];
92
+ return (t) => shape(node, a0(t), a1(t));
93
+ });
94
+ }
88
95
  arcLabel(node, start, end, inner, outer) {
89
96
  const middle = (start + end) / 2, radius = (inner + outer) / 2;
90
97
  const chord = (end - start) * radius, thick = outer - inner;
@@ -19,11 +19,6 @@ import { PhylogeneticTree } from "./PhylogeneticTree.js";
19
19
  import { DecompositionTree } from "./DecompositionTree.js";
20
20
  import { MindMap } from "./MindMap.js";
21
21
  import { FishboneDiagram } from "./FishboneDiagram.js";
22
- import { Hierarchy3DChart } from "./Hierarchy3DChart.js";
23
- import { TreemapSkyline3D } from "./TreemapSkyline3D.js";
24
- import { SunburstTower3D } from "./SunburstTower3D.js";
25
- import { ConeTree3D } from "./ConeTree3D.js";
26
- import { SpherePack3D } from "./SpherePack3D.js";
27
22
  import { CirclePackChart } from "./CirclePackChart.js";
28
23
  import { ZoomAdjacencyChart } from "./ZoomAdjacencyChart.js";
29
24
  import { FlameGraph } from "./FlameGraph.js";
@@ -53,10 +48,6 @@ var hierarchyCatalog = [
53
48
  PhylogeneticTree,
54
49
  DecompositionTree,
55
50
  MindMap,
56
- FishboneDiagram,
57
- TreemapSkyline3D,
58
- SunburstTower3D,
59
- ConeTree3D,
60
- SpherePack3D
51
+ FishboneDiagram
61
52
  ];
62
- export { AdjacencyChart, BalloonAngles, BalloonSpread, BalloonTree, CartesianTreeChart, CirclePackChart, CirclePackLayout, CollapsibleTree, ConeAxis, ConeTree3D, DecompositionDrill, DecompositionScale, DecompositionTree, DendrogramChart, DendrogramTip, FishboneDiagram, FishboneHead, FishboneRibs, FlameFlow, FlameGraph, Hierarchy3DChart, HierarchyChart, HierarchyColor, HierarchyMeasure, HierarchySort, IcicleBands, IcicleChart, IcicleZoom, IndentedBars, IndentedTreeChart, MindMap, MindMapNode, MindMapSides, NestedTreemap, OrgCard, OrgChart, PartitionAlign, PartitionChart, PartitionRibbon, PhyloBranches, PhyloLadder, PhyloLayout, PhylogeneticTree, PolarSweep, RadialDendrogram, RadialTreeBase, RadialTreeChart, SkylineHeight, SpherePack3D, SpherePackShell, SpherePackStack, SunburstChart, SunburstRings, SunburstTower3D, TidyTreeChart, TowerProfile, TreeDirection, TreeLinkShape, TreeModel, TreeNodeLinkChart, TreeNodeMark, TreeSamples, TreeSpacing, TreemapChart, TreemapGrouping, TreemapHeader, TreemapSkyline3D, TreemapTile, ZoomAdjacencyChart, ZoomStart, countsKeepDefaultSort, hierarchyCatalog, treeFieldDefaults };
53
+ export { AdjacencyChart, BalloonAngles, BalloonSpread, BalloonTree, CartesianTreeChart, CirclePackChart, CirclePackLayout, CollapsibleTree, ConeAxis, DecompositionDrill, DecompositionScale, DecompositionTree, DendrogramChart, DendrogramTip, FishboneDiagram, FishboneHead, FishboneRibs, FlameFlow, FlameGraph, HierarchyChart, HierarchyColor, HierarchyMeasure, HierarchySort, IcicleBands, IcicleChart, IcicleZoom, IndentedBars, IndentedTreeChart, MindMap, MindMapNode, MindMapSides, NestedTreemap, OrgCard, OrgChart, PartitionAlign, PartitionChart, PartitionRibbon, PhyloBranches, PhyloLadder, PhyloLayout, PhylogeneticTree, PolarSweep, RadialDendrogram, RadialTreeBase, RadialTreeChart, SkylineHeight, SpherePackShell, SpherePackStack, SunburstChart, SunburstRings, TidyTreeChart, TowerProfile, TreeDirection, TreeLinkShape, TreeModel, TreeNodeLinkChart, TreeNodeMark, TreeSamples, TreeSpacing, TreemapChart, TreemapGrouping, TreemapHeader, TreemapTile, ZoomAdjacencyChart, ZoomStart, countsKeepDefaultSort, hierarchyCatalog, treeFieldDefaults };
@@ -61,7 +61,12 @@ var BulletChart = class extends IndicatorChart {
61
61
  const rows = readings.slice(0, Math.max(1, Math.floor(room / (vertical ? 40 : 28))));
62
62
  const [low, top] = this.domain();
63
63
  const baseOf = (reading) => reading.target || reading.value || 1;
64
- const ceiling = relative ? Math.max(1.3, ...rows.flatMap((r) => [r.value, r.previous ?? 0].map((v) => v / baseOf(r)))) : top, floor = relative ? 0 : low;
64
+ const edgesOf = (reading) => (this.options.ranges ?? []).map((column) => +(reading.row[column] ?? NaN)).filter(Number.isFinite);
65
+ const ceiling = relative ? Math.max(1.3, ...rows.flatMap((r) => [
66
+ r.value,
67
+ r.previous ?? 0,
68
+ ...edgesOf(r)
69
+ ].map((v) => v / baseOf(r)))) : this.options.max ?? Math.max(top, ...rows.flatMap(edgesOf)), floor = relative ? 0 : low;
65
70
  const gutter = vertical ? 28 : Math.min(width * .26, Math.max(30, ...rows.map((r) => this.textWidth(r.label))) + 10);
66
71
  const length = vertical ? height - 40 : width - gutter - 8;
67
72
  const slot = room / Math.max(1, rows.length);
@@ -97,9 +102,10 @@ var BulletChart = class extends IndicatorChart {
97
102
  };
98
103
  const bands = lanes.flatMap((lane) => {
99
104
  const goal = lane.reading.target ?? lane.ceiling * lane.unit * .8;
105
+ const own = edgesOf(lane.reading);
100
106
  const stops = [.../* @__PURE__ */ new Set([
101
107
  0,
102
- ...this.edges().map((edge) => lane.at(edge * goal)),
108
+ ...own.length ? own.map(lane.at) : this.edges().map((edge) => lane.at(edge * goal)),
103
109
  length
104
110
  ])].sort((a, b) => a - b);
105
111
  return stops.slice(0, -1).map((from, i) => ({
@@ -77,7 +77,7 @@ var SpeedometerChart = class extends DialChart {
77
77
  const band = ctx.marks("trail", "path", trail ? [reading] : [], (d) => d.id, dial.root).attr("fill", this.accent(ctx));
78
78
  this.sweep(band, (d) => d.fraction, (_, f) => this.ring(radius - (outside ? 24 : 44), radius - (outside ? 18 : 38), frame.start, frame.angle(f), 3));
79
79
  ctx.hover(band, (d) => this.tooltip(d));
80
- const lines = ctx.marks("graduations", "line", ticks, (d) => d.fraction).attr("stroke", (d) => d.major ? ctx.theme.ink : ctx.theme.inkMuted).attr("stroke-width", (d) => d.major ? 2 : 1);
80
+ const lines = ctx.marks("axis-ticks", "line", ticks, (d) => d.fraction).attr("stroke", (d) => d.major ? ctx.theme.ink : ctx.theme.inkMuted).attr("stroke-width", (d) => d.major ? 2 : 1);
81
81
  const reach = (d) => radius - (d.major ? 15 : 10);
82
82
  lines.attr("x1", (d) => frame.point(d.fraction, reach(d))[0]).attr("y1", (d) => frame.point(d.fraction, reach(d))[1]).attr("x2", (d) => frame.point(d.fraction, radius - 3)[0]).attr("y2", (d) => frame.point(d.fraction, radius - 3)[1]);
83
83
  const ring = outside ? radius + 12 : radius - 25;
@@ -90,7 +90,7 @@ var ThermometerChart = class extends IndicatorChart {
90
90
  major: major.has(fraction.toFixed(4)),
91
91
  sign
92
92
  }))) : [];
93
- const marks = ctx.marks("ticks", "line", ticks, (d) => d.key).attr("stroke", (d) => d.major ? ctx.theme.inkSecondary : ctx.theme.axis).attr("stroke-width", 1.5);
93
+ const marks = ctx.marks("axis-ticks", "line", ticks, (d) => d.key).attr("stroke", (d) => d.major ? ctx.theme.inkSecondary : ctx.theme.axis).attr("stroke-width", 1.5);
94
94
  const end = (d, off) => overlay(at(d.fraction), d.sign * off);
95
95
  marks.attr("x1", (d) => end(d, half + 3)[0]).attr("y1", (d) => end(d, half + 3)[1]).attr("x2", (d) => end(d, half + (d.major ? 9 : 6))[0]).attr("y2", (d) => end(d, half + (d.major ? 9 : 6))[1]);
96
96
  const [low, high] = this.domain();
@@ -0,0 +1,113 @@
1
+ // src/charts/lifescience/LoessFit.ts
2
+ var tricube = (r) => (1 - r ** 3) ** 3;
3
+ var median = (values) => {
4
+ const sorted = [...values].sort((a, b) => a - b), middle = sorted.length / 2;
5
+ return sorted.length % 2 ? sorted[Math.floor(middle)] : (sorted[middle - 1] + sorted[middle]) / 2;
6
+ };
7
+ /**
8
+ * Robust locally weighted regression: tricube weights over the nearest `span` of the points, then
9
+ * bisquare reweighting of large residuals (Cleveland 1979, JASA 74:829; Cleveland and Devlin 1988,
10
+ * JASA 83:596). Windows and cut-offs follow R's `clowess`, so degree 1 reproduces `stats::lowess`.
11
+ */
12
+ var LoessFit = class LoessFit {
13
+ curve;
14
+ #xs;
15
+ #ys;
16
+ #degree;
17
+ #reach;
18
+ constructor(points, { span = .3, degree = 1, iterations = 3, knots = 60 } = {}) {
19
+ const sorted = points.filter(([x, y]) => Number.isFinite(x) && Number.isFinite(y)).sort((a, b) => a[0] - b[0]);
20
+ this.#xs = sorted.map((point) => point[0]);
21
+ this.#ys = sorted.map((point) => point[1]);
22
+ this.#degree = degree;
23
+ const n = sorted.length;
24
+ this.#reach = Math.max(degree + 1, Math.min(n, Math.floor(span * n + 1e-7)));
25
+ const at = this.#knots(knots);
26
+ const robust = new Array(n).fill(1);
27
+ let curve = this.#pass(at, robust);
28
+ for (let pass = 0; pass < iterations && n > 2; pass++) {
29
+ const residuals = this.#xs.map((x, i) => Math.abs(this.#ys[i] - LoessFit.#read(curve, x)));
30
+ const scale = 6 * median(residuals);
31
+ if (scale < 1e-7 * (residuals.reduce((sum, value) => sum + value, 0) / n) || scale === 0) break;
32
+ residuals.forEach((value, i) => {
33
+ robust[i] = value <= .001 * scale ? 1 : value <= .999 * scale ? (1 - (value / scale) ** 2) ** 2 : 0;
34
+ });
35
+ curve = this.#pass(at, robust);
36
+ }
37
+ this.curve = curve;
38
+ }
39
+ /** The fit at `x`, held flat past the ends of the data. */
40
+ at(x) {
41
+ return LoessFit.#read(this.curve, x);
42
+ }
43
+ static #read(curve, x) {
44
+ if (!curve.length) return 0;
45
+ if (x <= curve[0][0]) return curve[0][1];
46
+ if (x >= curve.at(-1)[0]) return curve.at(-1)[1];
47
+ let low = 0, high = curve.length - 1;
48
+ while (high - low > 1) {
49
+ const middle = low + high >> 1;
50
+ if (curve[middle][0] <= x) low = middle;
51
+ else high = middle;
52
+ }
53
+ const [x0, y0] = curve[low], [x1, y1] = curve[high];
54
+ return y0 + (x - x0) / (x1 - x0) * (y1 - y0);
55
+ }
56
+ #knots(limit) {
57
+ const distinct = this.#xs.filter((x, i, all) => i === 0 || x !== all[i - 1]);
58
+ if (distinct.length <= limit) return distinct;
59
+ return Array.from({ length: limit }, (_, i) => distinct[Math.round(i * (distinct.length - 1) / (limit - 1))]);
60
+ }
61
+ #pass(knots, robust) {
62
+ let left = 0;
63
+ return knots.map((at) => {
64
+ while (left + this.#reach < this.#xs.length && at - this.#xs[left] > this.#xs[left + this.#reach] - at) left++;
65
+ return [at, this.#local(at, left, robust)];
66
+ });
67
+ }
68
+ /** Weighted least squares around `at`, on distances in bandwidths so the system stays well scaled. */
69
+ #local(at, left, robust) {
70
+ const xs = this.#xs, ys = this.#ys, last = Math.min(xs.length, left + this.#reach) - 1;
71
+ const h = Math.max(at - xs[left], xs[last] - at), unit = h || 1;
72
+ const s = [
73
+ 0,
74
+ 0,
75
+ 0,
76
+ 0,
77
+ 0
78
+ ], t = [
79
+ 0,
80
+ 0,
81
+ 0
82
+ ];
83
+ for (let j = left; j < xs.length; j++) {
84
+ const distance = Math.abs(xs[j] - at);
85
+ if (distance > .999 * h && h > 0) {
86
+ if (j > last) break;
87
+ continue;
88
+ }
89
+ if (h === 0 && xs[j] !== at) break;
90
+ const weight = (distance <= .001 * h ? 1 : tricube(distance / h)) * robust[j];
91
+ const d = (xs[j] - at) / unit;
92
+ let power = weight;
93
+ for (let k = 0; k < 5; k++) {
94
+ s[k] += power;
95
+ if (k < 3) t[k] += power * ys[j];
96
+ power *= d;
97
+ }
98
+ }
99
+ if (s[0] <= 0) return ys[Math.min(last, Math.max(left, xs.findIndex((x) => x >= at)))];
100
+ return LoessFit.#solve(s, t, this.#degree);
101
+ }
102
+ /** Intercept of the local polynomial, dropping to a lower degree when the window cannot support one. */
103
+ static #solve([s0, s1, s2, s3, s4], [t0, t1, t2], degree) {
104
+ if (degree === 2) {
105
+ const a = s2 * s4 - s3 * s3, b = s1 * s4 - s3 * s2, c = s1 * s3 - s2 * s2;
106
+ const det = s0 * a - s1 * b + s2 * c;
107
+ if (Math.abs(det) > 1e-10 * s0 * s2 * s4) return (t0 * a - t1 * b + t2 * c) / det;
108
+ }
109
+ const det = s0 * s2 - s1 * s1;
110
+ return det > 1e-10 * s0 * s2 ? (t0 * s2 - t1 * s1) / det : t0 / s0;
111
+ }
112
+ };
113
+ export { LoessFit };
@@ -0,0 +1,337 @@
1
+ import { ChartFamily, DataShape, Dimension } from "../../core/enums.js";
2
+ import { StatisticalChart } from "../statistics/StatisticalChart.js";
3
+ import { StatMath } from "../statistics/StatMath.js";
4
+ import { PValueAdjustment } from "../statistics/VolcanoPlot.js";
5
+ import { FrameworkText } from "../frameworks/FrameworkText.js";
6
+ import { LoessFit } from "./LoessFit.js";
7
+ import { MaPlotSamples } from "./MaPlotSamples.js";
8
+ import { extent, max, min } from "d3";
9
+ // src/charts/lifescience/MaPlot.ts
10
+ /** Which columns hold the expression values. */
11
+ var MaSource = /* @__PURE__ */ function(MaSource) {
12
+ /** `red` and `green` intensities: M = log2(R/G), A = ½ log2(R·G). */
13
+ MaSource["TwoChannel"] = "two-channel";
14
+ /** `m` and `a` as already computed. */
15
+ MaSource["Values"] = "m-a-values";
16
+ /** `mean` normalized counts on a log axis against `m`, as DESeq2's `plotMA`. */
17
+ MaSource["MeanCounts"] = "mean-counts";
18
+ return MaSource;
19
+ }({});
20
+ /** Correction of M for dye or composition bias before plotting. */
21
+ var MaNormalization = /* @__PURE__ */ function(MaNormalization) {
22
+ MaNormalization["None"] = "unnormalized";
23
+ MaNormalization["Median"] = "median";
24
+ MaNormalization["Loess"] = "loess";
25
+ return MaNormalization;
26
+ }({});
27
+ /** How the genes that are not significant are drawn. */
28
+ var MaMarks = /* @__PURE__ */ function(MaMarks) {
29
+ MaMarks["Points"] = "points";
30
+ MaMarks["Density"] = "hexbin-density";
31
+ return MaMarks;
32
+ }({});
33
+ var HEX = 9;
34
+ var LABEL_GAP = 6;
35
+ var toPlot = (a, source) => source === "mean-counts" ? 2 ** a : a;
36
+ var median$1 = (values) => {
37
+ const sorted = [...values].sort((a, b) => a - b), middle = sorted.length / 2;
38
+ return sorted.length % 2 ? sorted[Math.floor(middle)] ?? 0 : ((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2;
39
+ };
40
+ /**
41
+ * Differential expression as log ratio M against mean average A, which shows intensity-dependent bias
42
+ * that a volcano plot hides. Follows Dudoit et al. (2002, Statistica Sinica 12:111), with the loess
43
+ * normalization of Yang et al. (2002, NAR 30:e15) and the count form of DESeq2's `plotMA`.
44
+ */
45
+ var MaPlot = class extends StatisticalChart {
46
+ static meta = {
47
+ id: "ma-plot",
48
+ name: "MA plot",
49
+ family: ChartFamily.Scientific,
50
+ dimension: Dimension.Two,
51
+ shape: DataShape.Table,
52
+ description: "Plots log ratio against mean expression with a loess trend, to expose intensity bias and the genes that change. The sample genes are synthetic.",
53
+ tags: [
54
+ "genomics",
55
+ "differential expression",
56
+ "microarray",
57
+ "rna-seq",
58
+ "loess"
59
+ ],
60
+ sample: (seed) => MaPlotSamples.expression(seed),
61
+ variants: {
62
+ normalization: Object.values(MaNormalization),
63
+ marks: Object.values(MaMarks),
64
+ trend: [true, false],
65
+ labels: [true, false]
66
+ }
67
+ };
68
+ #memo = null;
69
+ defaults() {
70
+ return {
71
+ gene: "gene",
72
+ red: "red",
73
+ green: "green",
74
+ m: "m",
75
+ a: "a",
76
+ mean: "baseMean",
77
+ pvalue: "pvalue",
78
+ source: "two-channel",
79
+ normalization: "unnormalized",
80
+ adjustment: PValueAdjustment.BenjaminiHochberg,
81
+ marks: "points",
82
+ trend: true,
83
+ labels: true,
84
+ top: 8,
85
+ alpha: .05,
86
+ threshold: 1,
87
+ span: .3,
88
+ degree: 1
89
+ };
90
+ }
91
+ axisTitles() {
92
+ const { source, normalization } = this.options;
93
+ const counts = source === "mean-counts";
94
+ const m = counts ? "log2 fold change" : "M";
95
+ return {
96
+ x: counts ? "Mean of normalized counts" : "A = ½ log2(R·G)",
97
+ y: normalization === "unnormalized" ? counts ? m : "M = log2(R/G)" : `${m}, ${normalization} normalized`
98
+ };
99
+ }
100
+ /** `[A, M]` for a row, on the log2 scale; `null` when a log is undefined or a cell is missing. */
101
+ #measure(row) {
102
+ const { source, red, green, m, a, mean } = this.options;
103
+ if (source === "two-channel") {
104
+ const r = this.number(row[red]), g = this.number(row[green]);
105
+ return r !== null && g !== null && r > 0 && g > 0 ? [(Math.log2(r) + Math.log2(g)) / 2, Math.log2(r) - Math.log2(g)] : null;
106
+ }
107
+ const change = this.number(row[m]);
108
+ if (change === null) return null;
109
+ if (source === "m-a-values") {
110
+ const level = this.number(row[a]);
111
+ return level === null ? null : [level, change];
112
+ }
113
+ const level = this.number(row[mean]);
114
+ return level !== null && level > 0 ? [Math.log2(level), change] : null;
115
+ }
116
+ /** Adjusted p-values by row. Every tested gene is in the family, plotted or not. A missing p stays missing, so that gene is shown but never called. */
117
+ #adjusted(rows) {
118
+ const { pvalue, adjustment } = this.options;
119
+ const raw = rows.map((row) => {
120
+ const p = this.number(row[pvalue]);
121
+ return p !== null && p >= 0 && p <= 1 ? p : null;
122
+ });
123
+ const tested = raw.filter((p) => p !== null);
124
+ const corrected = adjustment === PValueAdjustment.BenjaminiHochberg ? StatMath.benjaminiHochberg(tested) : adjustment === PValueAdjustment.Bonferroni ? tested.map((p) => Math.min(1, p * tested.length)) : tested;
125
+ let next = 0;
126
+ return raw.map((p) => p === null ? null : corrected[next++]);
127
+ }
128
+ #build() {
129
+ const { gene, normalization, span, degree, trend, alpha, threshold, source } = this.options;
130
+ const rows = (Array.isArray(this.options.data) ? this.options.data : []).filter((row) => row !== null && typeof row === "object");
131
+ const family = this.#adjusted(rows);
132
+ const usable = rows.flatMap((row, i) => {
133
+ const measured = this.#measure(row);
134
+ return measured ? [{
135
+ row,
136
+ measured,
137
+ q: family[i]
138
+ }] : [];
139
+ });
140
+ const pairs = usable.map((item) => item.measured);
141
+ const fit = (points) => new LoessFit(points, {
142
+ span,
143
+ degree
144
+ });
145
+ const shift = normalization === "loess" ? ((curve) => (a) => curve.at(a))(fit(pairs)) : normalization === "median" ? ((level) => () => level)(median$1(pairs.map((pair) => pair[1]))) : () => 0;
146
+ const genes = usable.map(({ row, measured: [a, raw] }, index) => {
147
+ const m = raw - shift(a), q = usable[index].q;
148
+ const called = q !== null && q < alpha && Math.abs(m) >= threshold && m !== 0;
149
+ return {
150
+ index,
151
+ name: String(row[gene] ?? index + 1),
152
+ a,
153
+ m,
154
+ q,
155
+ hit: called ? m > 0 ? 1 : -1 : 0
156
+ };
157
+ });
158
+ return {
159
+ genes,
160
+ trend: (trend && genes.length > 2 ? fit(genes.map((d) => [d.a, d.m])).curve : []).map(([a, m]) => [toPlot(a, source), m])
161
+ };
162
+ }
163
+ /** Loess is the slow step and pan and zoom repaint constantly, so a frame lasts until its inputs change. */
164
+ #frame() {
165
+ const { data, gene, red, green, m, a, mean, pvalue, source, normalization, adjustment, trend, alpha, threshold, span, degree } = this.options;
166
+ const key = JSON.stringify([
167
+ data?.length,
168
+ gene,
169
+ red,
170
+ green,
171
+ m,
172
+ a,
173
+ mean,
174
+ pvalue,
175
+ source,
176
+ normalization,
177
+ adjustment,
178
+ trend,
179
+ alpha,
180
+ threshold,
181
+ span,
182
+ degree
183
+ ]);
184
+ if (this.#memo?.data !== data || this.#memo.key !== key) this.#memo = {
185
+ data,
186
+ key,
187
+ frame: this.#build()
188
+ };
189
+ return this.#memo.frame;
190
+ }
191
+ /** The computed genes and trend, in log2 A, for reading results in code. */
192
+ get frame() {
193
+ return this.#frame();
194
+ }
195
+ scales(ctx) {
196
+ const { source, threshold } = this.options;
197
+ const { genes, trend } = this.#frame();
198
+ const reach = Math.max(threshold * 1.25, 1, max(genes, (d) => Math.abs(d.m)) ?? 0, max(trend, (d) => Math.abs(d[1])) ?? 0) * 1.06;
199
+ const y = this.linear([-reach, reach], [ctx.plot.height, 0]);
200
+ if (source !== "mean-counts") return {
201
+ x: this.linear(this.span(extent(genes, (d) => d.a).filter((a) => a !== void 0)), [0, ctx.plot.width]),
202
+ y
203
+ };
204
+ const low = min(genes, (d) => 2 ** d.a), high = max(genes, (d) => 2 ** d.a);
205
+ return {
206
+ x: this.logarithmic(low !== void 0 && high !== void 0 ? [low / 1.4, high * 1.4] : [1, 10], [0, ctx.plot.width]),
207
+ y
208
+ };
209
+ }
210
+ legendItems() {
211
+ const { data, trend, marks } = this.options;
212
+ if (!data?.length) return [];
213
+ return [
214
+ {
215
+ key: "Up",
216
+ color: this.palette.diverging(1)
217
+ },
218
+ {
219
+ key: "Down",
220
+ color: this.palette.diverging(-1)
221
+ },
222
+ {
223
+ key: marks === "hexbin-density" ? "Not significant, density" : "Not significant",
224
+ color: this.theme.inkMuted
225
+ },
226
+ ...trend ? [{
227
+ key: "Loess trend",
228
+ color: this.theme.ink
229
+ }] : []
230
+ ];
231
+ }
232
+ /** Pointy-top hexagonal bins in pixels: each gene joins the nearer of the two candidate rows. */
233
+ #bin(genes, at, { width, height }) {
234
+ const dx = Math.sqrt(3) * HEX, dy = 1.5 * HEX, bins = /* @__PURE__ */ new Map();
235
+ for (const gene of genes) {
236
+ const [x, y] = at(gene);
237
+ if (x < 0 || x > width || y < 0 || y > height) continue;
238
+ const row = Math.floor(y / dy);
239
+ const nearest = [row, row + 1].map((j) => {
240
+ const i = Math.round(x / dx - (j & 1) / 2), cx = (i + (j & 1) / 2) * dx, cy = j * dy;
241
+ return {
242
+ key: `${i},${j}`,
243
+ x: cx,
244
+ y: cy,
245
+ distance: (cx - x) ** 2 + (cy - y) ** 2
246
+ };
247
+ }).sort((a, b) => a.distance - b.distance)[0];
248
+ const bin = bins.get(nearest.key);
249
+ if (bin) bin.genes.push(gene);
250
+ else bins.set(nearest.key, {
251
+ key: nearest.key,
252
+ x: nearest.x,
253
+ y: nearest.y,
254
+ genes: [gene]
255
+ });
256
+ }
257
+ return [...bins.values()];
258
+ }
259
+ #density(ctx, bins) {
260
+ const peak = Math.max(1, max(bins, (bin) => bin.genes.length) ?? 0);
261
+ const opacity = (bin) => .2 + .8 * Math.sqrt(bin.genes.length / peak);
262
+ const r = 8, c = r * Math.sqrt(3) / 2;
263
+ const shape = (bin) => `M${bin.x},${bin.y - r}l${c},${r / 2}v${r}l${-c},${r / 2}l${-c},-4v-8z`;
264
+ const cells = ctx.marks("density", "path", bins, (bin) => bin.key).attr("fill", ctx.theme.inkMuted);
265
+ ctx.set(cells, {
266
+ d: shape,
267
+ "fill-opacity": opacity
268
+ }, { "fill-opacity": 0 });
269
+ ctx.hover(cells, (bin) => ({
270
+ title: `${bin.genes.length} ${bin.genes.length === 1 ? "gene" : "genes"}`,
271
+ color: ctx.theme.inkMuted,
272
+ rows: [["Not significant", bin.genes.length]]
273
+ }));
274
+ }
275
+ plotMarks(ctx, axes) {
276
+ const { marks, labels, top, threshold, source } = this.options;
277
+ const { genes, trend } = this.#frame();
278
+ const [left, right] = axes.x.domain();
279
+ const dash = "5 4";
280
+ this.rules(ctx, "zero", [{
281
+ key: "zero",
282
+ from: [left, 0],
283
+ to: [right, 0],
284
+ strong: true
285
+ }], axes);
286
+ this.rules(ctx, "fold", threshold > 0 ? [{
287
+ key: "up",
288
+ from: [left, threshold],
289
+ to: [right, threshold],
290
+ dash
291
+ }, {
292
+ key: "down",
293
+ from: [left, -threshold],
294
+ to: [right, -threshold],
295
+ dash
296
+ }] : [], axes);
297
+ const fx = this.scaleX(axes), fy = this.scaleY(axes);
298
+ const at = (d) => [fx(toPlot(d.a, source)), fy(d.m)];
299
+ const binned = marks === "hexbin-density";
300
+ this.#density(ctx, binned ? this.#bin(genes.filter((d) => !d.hit), at, ctx.plot) : []);
301
+ const color = (d) => d.hit ? ctx.palette.diverging(d.hit) : ctx.theme.inkMuted;
302
+ const shown = genes.filter((d) => d.hit || !binned).sort((a, b) => Math.abs(a.hit) - Math.abs(b.hit));
303
+ this.dots(ctx, "genes", shown, at, color, (d) => ({
304
+ title: d.name,
305
+ color: color(d),
306
+ rows: [
307
+ ["A", +d.a.toFixed(3)],
308
+ ["M", +d.m.toFixed(3)],
309
+ ["Adjusted p", d.q === null ? "not tested" : +d.q.toPrecision(3)]
310
+ ]
311
+ }), {
312
+ r: (d) => d.hit ? 4 : 2.5,
313
+ key: (d) => d.index,
314
+ ring: false,
315
+ opacity: .8
316
+ });
317
+ this.traces(ctx, "trend", trend.length > 1 ? [{
318
+ key: "Loess trend",
319
+ color: ctx.theme.ink,
320
+ points: trend
321
+ }] : [], axes);
322
+ const best = labels ? genes.filter((d) => d.hit).sort((a, b) => a.q - b.q || Math.abs(b.m) - Math.abs(a.m)).slice(0, top) : [];
323
+ this.notes(ctx, "labels", best.map((d) => {
324
+ const [x, y] = at(d), right = ctx.plot.width - x - LABEL_GAP, left = x - LABEL_GAP;
325
+ const flip = FrameworkText.width(d.name, 11) > right && left > right;
326
+ return {
327
+ key: `${d.index}`,
328
+ text: FrameworkText.fit(d.name, flip ? left : right, 11),
329
+ x: x + (flip ? -6 : LABEL_GAP),
330
+ y: y - 5,
331
+ anchor: flip ? "end" : "start",
332
+ tone: "ink"
333
+ };
334
+ }));
335
+ }
336
+ };
337
+ export { MaMarks, MaNormalization, MaPlot, MaSource };
@@ -0,0 +1,35 @@
1
+ import { SampleData } from "../../data/SampleData.js";
2
+ import { StatMath } from "../statistics/StatMath.js";
3
+ // src/charts/lifescience/MaPlotSamples.ts
4
+ var DRAWS = 4;
5
+ /** Invented two-channel expression data, not a measurement: `SYN-` ids, a designed dye bias and designed differential genes. */
6
+ var MaPlotSamples = class MaPlotSamples {
7
+ /** Spread of M at intensity `a`, which fans out at low signal. */
8
+ static #noise(a) {
9
+ return .16 + 1.1 * Math.exp(-(a - 3) / 1.8);
10
+ }
11
+ /** The banana shape of an unnormalized dye effect. */
12
+ static #bias(a) {
13
+ return .75 * Math.cos(Math.PI * (a - 3) / 12);
14
+ }
15
+ /** `{ gene, red, green, baseMean, a, m, pvalue }`, with no p-value for the faintest genes, as after independent filtering. */
16
+ static expression(seed, count = 1200) {
17
+ const z = SampleData.distribution(seed, count * DRAWS, 1).map((row) => (row.value - 40) / 8);
18
+ return Array.from({ length: count }, (_, i) => {
19
+ const [level, noise, pick, size] = z.slice(i * DRAWS, (i + 1) * DRAWS);
20
+ const a = Math.max(3, Math.min(15, 8.5 + 2.2 * level));
21
+ const effect = StatMath.normalCdf(pick) < .08 ? (size < 0 ? -1 : 1) * (1.1 + Math.abs(size) * .8) : 0;
22
+ const spread = MaPlotSamples.#noise(a), m = MaPlotSamples.#bias(a) + effect + spread * noise;
23
+ return {
24
+ gene: `SYN-${String(i + 1).padStart(4, "0")}`,
25
+ red: +(2 ** (a + m / 2)).toFixed(2),
26
+ green: +(2 ** (a - m / 2)).toFixed(2),
27
+ baseMean: +(2 ** a).toFixed(2),
28
+ a: +a.toFixed(3),
29
+ m: +m.toFixed(3),
30
+ pvalue: a < 4 ? null : +StatMath.twoSided((effect + spread * noise) / (spread / 3)).toPrecision(3)
31
+ };
32
+ });
33
+ }
34
+ };
35
+ export { MaPlotSamples };