@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,191 @@
1
+ import { randomLcg, randomNormal } from "d3";
2
+ // src/charts/frameworks/PositioningSamples.ts
3
+ /** Latent premium and practicality of each fictional brand, with its share. */
4
+ var BRANDS = [
5
+ [
6
+ "Alder",
7
+ 1.3,
8
+ .5,
9
+ 9
10
+ ],
11
+ [
12
+ "Brio",
13
+ -1.2,
14
+ .9,
15
+ 24
16
+ ],
17
+ [
18
+ "Cinder",
19
+ .4,
20
+ -1.3,
21
+ 7
22
+ ],
23
+ [
24
+ "Dune",
25
+ -.6,
26
+ -.8,
27
+ 18
28
+ ],
29
+ [
30
+ "Ember",
31
+ 1,
32
+ -.5,
33
+ 6
34
+ ],
35
+ [
36
+ "Fable",
37
+ -1.4,
38
+ -.4,
39
+ 21
40
+ ],
41
+ [
42
+ "Grove",
43
+ .1,
44
+ 1.2,
45
+ 11
46
+ ],
47
+ [
48
+ "Halo",
49
+ .7,
50
+ 1.4,
51
+ 4
52
+ ]
53
+ ];
54
+ /** How strongly each attribute reads the two latent dimensions. */
55
+ var ATTRIBUTES = [
56
+ [
57
+ "Price",
58
+ 1.3,
59
+ -.1
60
+ ],
61
+ [
62
+ "Quality",
63
+ .8,
64
+ .9
65
+ ],
66
+ [
67
+ "Style",
68
+ 1.1,
69
+ -.5
70
+ ],
71
+ [
72
+ "Reliability",
73
+ .1,
74
+ 1.2
75
+ ],
76
+ [
77
+ "Service",
78
+ .3,
79
+ .8
80
+ ],
81
+ [
82
+ "Innovation",
83
+ .9,
84
+ -.2
85
+ ]
86
+ ];
87
+ var INITIATIVES = [
88
+ [
89
+ "Loyalty programme",
90
+ "Existing",
91
+ "Existing",
92
+ 18
93
+ ],
94
+ [
95
+ "Bundle pricing",
96
+ "Existing",
97
+ "Existing",
98
+ 9
99
+ ],
100
+ [
101
+ "Sales coverage",
102
+ "Existing",
103
+ "Existing",
104
+ 14
105
+ ],
106
+ [
107
+ "Win-back campaign",
108
+ "Existing",
109
+ "Existing",
110
+ 5
111
+ ],
112
+ [
113
+ "Premium tier",
114
+ "Existing",
115
+ "New",
116
+ 22
117
+ ],
118
+ [
119
+ "Mobile app",
120
+ "Existing",
121
+ "New",
122
+ 16
123
+ ],
124
+ [
125
+ "Add-on module",
126
+ "Existing",
127
+ "New",
128
+ 7
129
+ ],
130
+ [
131
+ "Export to Europe",
132
+ "New",
133
+ "Existing",
134
+ 19
135
+ ],
136
+ [
137
+ "Reseller channel",
138
+ "New",
139
+ "Existing",
140
+ 11
141
+ ],
142
+ [
143
+ "Public sector",
144
+ "New",
145
+ "Existing",
146
+ 6
147
+ ],
148
+ [
149
+ "Hardware line",
150
+ "New",
151
+ "New",
152
+ 13
153
+ ],
154
+ [
155
+ "Logistics venture",
156
+ "New",
157
+ "New",
158
+ 8
159
+ ]
160
+ ];
161
+ /** Synthetic rows for the positioning charts: invented brands and initiatives, not measurements of anything real. */
162
+ var PositioningSamples = class {
163
+ /** Mean ratings on a 1 to 7 scale for fictional brands, plus one ideal row, with noise drawn from `seed`. */
164
+ static perceptions(seed = 1) {
165
+ const noise = randomNormal.source(randomLcg(seed / 97))(0, .25);
166
+ const rating = (a, b, loadA, loadB) => +Math.max(1, Math.min(7, 4 + a * loadA + b * loadB + noise())).toFixed(2);
167
+ const rows = BRANDS.map(([brand, a, b, share]) => ({
168
+ brand,
169
+ role: "brand",
170
+ share,
171
+ ...Object.fromEntries(ATTRIBUTES.map(([name, loadA, loadB]) => [name, rating(a, b, loadA, loadB)]))
172
+ }));
173
+ const ideal = {
174
+ brand: "Ideal",
175
+ role: "ideal",
176
+ ...Object.fromEntries(ATTRIBUTES.map(([name, loadA, loadB]) => [name, rating(-.2, 1.2, loadA, loadB)]))
177
+ };
178
+ return [...rows, ideal];
179
+ }
180
+ /** Fictional growth initiatives with an invented investment in `value`. */
181
+ static initiatives(seed = 1) {
182
+ const random = randomLcg(seed / 89);
183
+ return INITIATIVES.map(([initiative, market, product, value]) => ({
184
+ initiative,
185
+ market,
186
+ product,
187
+ value: +(value * (.8 + random() * .4)).toFixed(1)
188
+ }));
189
+ }
190
+ };
191
+ export { PositioningSamples };
@@ -0,0 +1,458 @@
1
+ import { Chart2D } from "../../core/Chart2D.js";
2
+ import { ChartFamily, DataShape, Dimension } from "../../core/enums.js";
3
+ import { FrameworkText } from "./FrameworkText.js";
4
+ import { RaciHeader, RaciIssue, RaciLetter, RaciMark } from "./raciEnums.js";
5
+ import { RaciPlan } from "./RaciPlan.js";
6
+ import { RaciSamples } from "./RaciSamples.js";
7
+ // src/charts/frameworks/RaciMatrix.ts
8
+ var LETTERS = [
9
+ RaciLetter.Responsible,
10
+ RaciLetter.Accountable,
11
+ RaciLetter.Consulted,
12
+ RaciLetter.Informed
13
+ ];
14
+ var NAMES = {
15
+ R: "Responsible",
16
+ A: "Accountable",
17
+ C: "Consulted",
18
+ I: "Informed"
19
+ };
20
+ var SHORT = {
21
+ [RaciIssue.NoAccountable]: "No A",
22
+ [RaciIssue.SeveralAccountable]: "A",
23
+ [RaciIssue.NoResponsible]: "No R"
24
+ };
25
+ var MIN_COLUMN = 24;
26
+ var MIN_LABEL = 96;
27
+ var MIN_ROW = 18;
28
+ var MAX_ROW = 34;
29
+ var BAND = 15;
30
+ var LOAD = 18;
31
+ var BADGE = 16;
32
+ var HEAD = 11;
33
+ var FOOT = 10.5;
34
+ var FOOT_LEAD = 13;
35
+ var COS45 = Math.SQRT1_2;
36
+ /**
37
+ * Assigns each task's work across roles: Responsible, Accountable, Consulted, Informed. Method: the
38
+ * responsibility assignment matrix as in the PMBOK Guide, audited by the standard rule of exactly
39
+ * one A and at least one R per task. A person holding both reads `A/R`, a common convention, and
40
+ * counts toward both. Letters are always text, so colour is never the only encoding.
41
+ */
42
+ var RaciMatrix = class RaciMatrix extends Chart2D {
43
+ static meta = {
44
+ id: "raci-matrix",
45
+ name: "RACI matrix",
46
+ family: ChartFamily.Project,
47
+ dimension: Dimension.Two,
48
+ shape: DataShape.Table,
49
+ description: "Sets tasks against roles with R, A, C and I letters and flags tasks with no Accountable, several, or no Responsible.",
50
+ tags: [
51
+ "framework",
52
+ "project",
53
+ "raci",
54
+ "responsibility",
55
+ "governance"
56
+ ],
57
+ sample: (seed) => RaciSamples.launch(seed),
58
+ variants: {
59
+ header: [
60
+ RaciHeader.Wrapped,
61
+ RaciHeader.Angled,
62
+ RaciHeader.Vertical
63
+ ],
64
+ mark: [RaciMark.Badge, RaciMark.Tile],
65
+ grouping: [false, true],
66
+ flags: [true, false],
67
+ load: [false, true]
68
+ }
69
+ };
70
+ defaults() {
71
+ return {
72
+ task: "task",
73
+ role: "role",
74
+ assignment: "assignment",
75
+ phase: "phase",
76
+ header: RaciHeader.Wrapped,
77
+ mark: RaciMark.Badge,
78
+ grouping: false,
79
+ flags: true,
80
+ load: false
81
+ };
82
+ }
83
+ plotMargin() {
84
+ return {
85
+ top: 8,
86
+ right: 16,
87
+ bottom: 10,
88
+ left: 16
89
+ };
90
+ }
91
+ legendItems() {
92
+ return this.palette.items().map((item) => ({
93
+ ...item,
94
+ label: `${item.key} ${NAMES[item.key]}`
95
+ }));
96
+ }
97
+ /** The parsed tasks with their audit, for showing faults outside the chart. */
98
+ get plan() {
99
+ const { task, role, assignment, phase, data } = this.options;
100
+ return new RaciPlan(data, {
101
+ task,
102
+ role,
103
+ assignment,
104
+ phase
105
+ });
106
+ }
107
+ /** One row per task, one column per role, plus the audit. */
108
+ table() {
109
+ const plan = this.plan;
110
+ return {
111
+ columns: [
112
+ "Task",
113
+ ...plan.roles,
114
+ "Check"
115
+ ],
116
+ rows: plan.tasks.map((task) => [
117
+ task.name,
118
+ ...plan.roles.map((role) => task.cells.get(role)?.join("/") ?? ""),
119
+ RaciMatrix.#check(task)
120
+ ])
121
+ };
122
+ }
123
+ static #check(task) {
124
+ return task.issues.map((issue) => issue === RaciIssue.SeveralAccountable ? `${task.accountable.length} ${SHORT[issue]}` : SHORT[issue]).join(", ");
125
+ }
126
+ /** Equal columns no narrower than a letter badge. Roles past that are left out and counted in the footer. */
127
+ #columns(names, from, area, height) {
128
+ const longest = Math.max(0, ...names.map((name) => FrameworkText.width(name, HEAD)));
129
+ const { header } = this.options;
130
+ const rise = header === RaciHeader.Angled ? Math.min(longest * COS45 + 8, height * .3) : Math.min(longest + 8, height * .34);
131
+ let shown = Math.min(names.length, Math.max(1, Math.floor(area / MIN_COLUMN))), width = 0;
132
+ for (;; shown--) {
133
+ width = area / (shown || 1);
134
+ if (header === RaciHeader.Angled && rise > width / 2) width = (area - rise) / Math.max(.5, shown - .5);
135
+ if (shown <= 1 || width >= MIN_COLUMN) break;
136
+ }
137
+ const columns = names.slice(0, shown).map((role, i) => ({
138
+ role,
139
+ x: from + i * width,
140
+ width
141
+ }));
142
+ const wrapped = Math.max(1, ...columns.map(({ role, width: room }) => Math.min(3, FrameworkText.wrap(role, room - 4, HEAD).length)));
143
+ return {
144
+ columns,
145
+ hiddenRoles: names.slice(shown),
146
+ headerHeight: header === RaciHeader.Wrapped ? wrapped * 14 + 6 : rise
147
+ };
148
+ }
149
+ /** The largest type and most lines per task that fit. Whatever still does not fit is left out and named in the footer. */
150
+ #rows(ordered, labelWidth, top, room) {
151
+ const { grouping } = this.options;
152
+ const wrap = labelWidth - 10;
153
+ const band = (i) => grouping && ordered[i].phase !== ordered[i - 1]?.phase ? BAND : 0;
154
+ const bands = ordered.reduce((sum, _, i) => sum + band(i), 0);
155
+ const attempt = (size, most) => {
156
+ const lead = Math.round(size * 1.25);
157
+ const lines = ordered.map((task) => FrameworkText.clamp(FrameworkText.wrap(task.name, wrap, size), most, wrap, size));
158
+ return {
159
+ size,
160
+ lead,
161
+ lines,
162
+ heights: lines.map((list) => Math.max(MIN_ROW, list.length * lead + 5))
163
+ };
164
+ };
165
+ const fits = (pick) => pick.heights.reduce((sum, height) => sum + height, bands) <= room;
166
+ const pick = [
167
+ [11, 3],
168
+ [11, 2],
169
+ [10, 3],
170
+ [10, 2],
171
+ [11, 1],
172
+ [10, 1]
173
+ ].map(([size, most]) => attempt(size, most)).find(fits) ?? attempt(10, 1);
174
+ const used = pick.heights.reduce((sum, height) => sum + height, bands);
175
+ const spare = ordered.length && used < room ? (room - used) / ordered.length : 0;
176
+ let y = top;
177
+ const tasks = [], phases = [];
178
+ let i = 0;
179
+ for (; i < ordered.length; i++) {
180
+ const task = ordered[i], lift = band(i), height = Math.min(MAX_ROW, pick.heights[i] + spare);
181
+ if (y + lift + height > top + room + .5) break;
182
+ if (lift) phases.push({
183
+ name: task.phase,
184
+ top: y
185
+ });
186
+ y += lift;
187
+ const lines = pick.lines[i];
188
+ tasks.push({
189
+ task,
190
+ lines,
191
+ x: 0,
192
+ y: y + (height - lines.length * pick.lead) / 2 + pick.size - 1,
193
+ lead: pick.lead,
194
+ top: y,
195
+ height
196
+ });
197
+ y += height;
198
+ }
199
+ return {
200
+ tasks,
201
+ hiddenTasks: ordered.slice(i),
202
+ phases,
203
+ size: pick.size,
204
+ lead: pick.lead,
205
+ loadTop: y
206
+ };
207
+ }
208
+ /** Wrapped names break between words if each column is as wide as the longest word. */
209
+ #headerRoom(roles) {
210
+ if (this.options.header !== RaciHeader.Wrapped) return 0;
211
+ const words = roles.flatMap((role) => role.split(" ")).map((word) => FrameworkText.width(word, HEAD) + 6);
212
+ return Math.max(0, ...words) * roles.length;
213
+ }
214
+ #layout(ctx, plan, note) {
215
+ const { flags, load, grouping } = this.options;
216
+ const { width, height } = ctx.plot;
217
+ const lines = FrameworkText.clamp(FrameworkText.wrap(note, width, FOOT), 2, width, FOOT);
218
+ const footerHeight = lines.length ? lines.length * FOOT_LEAD + 6 : 0;
219
+ const checks = flags ? plan.tasks.map((task) => RaciMatrix.#check(task)) : [];
220
+ const wanted = flags ? Math.ceil(Math.max(FrameworkText.width("Check", 10.5), ...checks.map((text) => FrameworkText.width(text, 10) + 12))) + 8 : 0;
221
+ const longest = Math.max(0, ...plan.tasks.map((task) => FrameworkText.width(task.name, 11)));
222
+ const label = Math.min(longest + 10, width * .42, Math.max(width * .34, width - wanted - this.#headerRoom(plan.roles)));
223
+ const labelWidth = Math.max(0, Math.min(Math.max(MIN_LABEL, label), width * .5, width - MIN_COLUMN));
224
+ const checkWidth = width - labelWidth - wanted >= MIN_COLUMN ? wanted : 0;
225
+ const area = Math.max(1, width - labelWidth - checkWidth);
226
+ const columns = this.#columns(plan.roles, labelWidth, area, height);
227
+ const ordered = grouping ? [...new Set(plan.tasks.map((task) => task.phase))].flatMap((phase) => plan.tasks.filter((task) => task.phase === phase)) : plan.tasks;
228
+ const room = height - columns.headerHeight - footerHeight - (load ? LOAD : 0);
229
+ const rows = this.#rows(ordered, labelWidth, columns.headerHeight, room);
230
+ const footer = {
231
+ lines,
232
+ x: 0,
233
+ y: height - footerHeight + 6 + FOOT,
234
+ lead: FOOT_LEAD
235
+ };
236
+ return {
237
+ ...columns,
238
+ ...rows,
239
+ labelWidth,
240
+ checkWidth,
241
+ footer
242
+ };
243
+ }
244
+ static #summary(plan, flags) {
245
+ const parts = [];
246
+ if (flags && plan.tasks.length) {
247
+ const count = (issue) => plan.tasks.filter((task) => task.issues.includes(issue)).length;
248
+ const none = count(RaciIssue.NoAccountable), several = count(RaciIssue.SeveralAccountable), noR = count(RaciIssue.NoResponsible);
249
+ const faults = [
250
+ none && `${none} no A`,
251
+ several && `${several} several A`,
252
+ noR && `${noR} no R`
253
+ ].filter(Boolean).join(", ");
254
+ parts.push(plan.flagged.length ? `${plan.flagged.length} of ${plan.tasks.length} tasks flagged: ${faults}` : `All ${plan.tasks.length} tasks have one A and an R`);
255
+ }
256
+ if (plan.skipped) parts.push(`${plan.skipped} ${plan.skipped === 1 ? "row" : "rows"} unreadable`);
257
+ return parts;
258
+ }
259
+ #plan(ctx, plan) {
260
+ const summary = RaciMatrix.#summary(plan, !!this.options.flags);
261
+ const shown = (hidden, total, noun) => hidden ? [`Showing ${total - hidden} of ${total} ${noun}`] : [];
262
+ let layout = this.#layout(ctx, plan, summary.join(" · "));
263
+ for (let pass = 0; pass < 2 && (layout.hiddenTasks.length || layout.hiddenRoles.length); pass++) {
264
+ const notes = [
265
+ ...summary,
266
+ ...shown(layout.hiddenTasks.length, plan.tasks.length, "tasks"),
267
+ ...shown(layout.hiddenRoles.length, plan.roles.length, "roles")
268
+ ];
269
+ layout = this.#layout(ctx, plan, notes.join(" · "));
270
+ }
271
+ return layout;
272
+ }
273
+ #cells(plan, ctx) {
274
+ const { mark } = this.options;
275
+ return plan.tasks.flatMap((row) => plan.columns.flatMap(({ role, x, width }) => {
276
+ const letters = row.task.cells.get(role);
277
+ if (!letters) return [];
278
+ const label = letters.join("/"), fill = ctx.palette.color(letters[0]), room = Math.max(0, width - 6);
279
+ const size = FrameworkText.width(label, 11) <= room - 6 ? 11 : 9;
280
+ const text = FrameworkText.fit(label, room, size), middle = x + width / 2, y = row.top + row.height / 2;
281
+ const wide = Math.min(room, Math.max(BADGE, FrameworkText.width(text, size) + 8));
282
+ const box = mark === RaciMark.Tile ? {
283
+ x: x + 1,
284
+ y: row.top + 1,
285
+ width: Math.max(0, width - 2),
286
+ height: row.height - 2
287
+ } : {
288
+ x: middle - wide / 2,
289
+ y: y - BADGE / 2,
290
+ width: wide,
291
+ height: BADGE
292
+ };
293
+ return [{
294
+ key: `${row.task.name}\u0000${role}`,
295
+ task: row.task,
296
+ role,
297
+ letters,
298
+ text,
299
+ size,
300
+ fill,
301
+ ...box,
302
+ middle,
303
+ baseline: y + size * .36
304
+ }];
305
+ }));
306
+ }
307
+ #pills(plan, ctx) {
308
+ const { theme, palette } = ctx;
309
+ if (!plan.checkWidth) return [];
310
+ return plan.tasks.filter((row) => row.task.issues.length).map((row) => {
311
+ const text = RaciMatrix.#check(row.task), width = Math.min(plan.checkWidth - 8, FrameworkText.width(text, 10) + 12);
312
+ const fill = row.task.issues[0] === RaciIssue.NoResponsible ? theme.warning : theme.critical;
313
+ return {
314
+ row,
315
+ text: FrameworkText.fit(text, width - 8, 10),
316
+ width,
317
+ height: 16,
318
+ x: ctx.plot.width - plan.checkWidth + 8,
319
+ y: row.top + row.height / 2 - 8,
320
+ baseline: row.top + row.height / 2 + 3.5,
321
+ fill,
322
+ ink: palette.inkOn(fill)
323
+ };
324
+ });
325
+ }
326
+ #drawHeader(ctx, plan, layout) {
327
+ const { header } = this.options;
328
+ const { headerHeight: rise, columns, checkWidth } = layout;
329
+ const wrapped = header === RaciHeader.Wrapped;
330
+ const rows = columns.map((column) => {
331
+ const lines = this.#headerLines(column, rise), middle = column.x + column.width / 2;
332
+ if (wrapped) return {
333
+ role: column.role,
334
+ lines,
335
+ x: middle,
336
+ y: rise - 5 - (lines.length - 1) * 14,
337
+ lead: 14,
338
+ turn: ""
339
+ };
340
+ const vertical = header === RaciHeader.Vertical;
341
+ return {
342
+ role: column.role,
343
+ lines,
344
+ x: 0,
345
+ y: 0,
346
+ lead: 14,
347
+ turn: `translate(${middle + (vertical ? 4 : -2)},${rise - 4}) rotate(${vertical ? -90 : -45})`
348
+ };
349
+ });
350
+ const heads = ctx.text(ctx.marks("role-names", "text", rows, (d) => d.role), HEAD, "ink").attr("font-weight", 600).attr("text-anchor", wrapped ? "middle" : "start");
351
+ ctx.set(heads, { transform: (d) => d.turn || null });
352
+ FrameworkText.write(ctx, heads);
353
+ ctx.hover(heads, (d) => {
354
+ const load = plan.load(d.role);
355
+ return FrameworkText.tip(d.role, LETTERS.map((letter) => [NAMES[letter], load[letter]]));
356
+ });
357
+ const check = ctx.text(ctx.marks("check-name", "text", checkWidth ? ["Check"] : []), 10.5, "secondary").attr("font-weight", 600).text((d) => d);
358
+ ctx.set(check, {
359
+ x: ctx.plot.width - checkWidth + 8,
360
+ y: rise - 5
361
+ });
362
+ }
363
+ #headerLines({ role, width }, rise) {
364
+ const { header } = this.options;
365
+ if (header === RaciHeader.Wrapped) return FrameworkText.clamp(FrameworkText.wrap(role, width - 4, HEAD), 3, width - 4, HEAD);
366
+ return [FrameworkText.fit(role, header === RaciHeader.Angled ? (rise - 6) / COS45 : rise - 6, HEAD)];
367
+ }
368
+ draw(ctx) {
369
+ const { load } = this.options;
370
+ const plan = this.plan, { plot, theme, palette } = ctx;
371
+ if (plan.tasks.length) for (const letter of LETTERS) palette.color(letter);
372
+ const layout = this.#plan(ctx, plan);
373
+ const rightEdge = plot.width;
374
+ const empty = ctx.text(ctx.marks("empty", "text", plan.tasks.length ? [] : ["No assignments"]), 12, "muted").attr("text-anchor", "middle").text((d) => d);
375
+ ctx.set(empty, {
376
+ x: plot.width / 2,
377
+ y: plot.height / 2
378
+ });
379
+ this.#drawHeader(ctx, plan, layout);
380
+ const rows = ctx.marks("task-rows", "g", layout.tasks, (d) => d.task.name);
381
+ const stripes = rows.selectAll(":scope > rect").data((d) => [d]).join("rect").attr("rx", 4).attr("fill", (d) => layout.checkWidth && d.task.issues.length ? d.task.issues[0] === RaciIssue.NoResponsible ? theme.warning : theme.critical : "transparent").attr("fill-opacity", .12);
382
+ ctx.set(stripes, {
383
+ x: 0,
384
+ y: (d) => d.top + 1,
385
+ width: rightEdge,
386
+ height: (d) => d.height - 2
387
+ });
388
+ const labels = rows.selectAll(":scope > text").data((d) => [d]).join("text");
389
+ FrameworkText.write(ctx, ctx.text(labels, layout.size, "ink").attr("pointer-events", "none"));
390
+ ctx.hover(rows, ({ task }) => FrameworkText.tip(task.name, [
391
+ ["Phase", task.phase],
392
+ ["Accountable", task.accountable.join(", ") || "None"],
393
+ ["Responsible", task.responsible.join(", ") || "None"],
394
+ ...task.issues.length ? [["Check", RaciMatrix.#check(task)]] : []
395
+ ]));
396
+ const levels = plan.tasks.length ? [layout.headerHeight, ...layout.tasks.slice(0, -1).map((d) => d.top + d.height)] : [];
397
+ const rules = ctx.marks("rules", "line", levels).attr("stroke", (_, i) => i ? theme.grid : theme.axis);
398
+ ctx.set(rules, {
399
+ x1: 0,
400
+ x2: rightEdge,
401
+ y1: (d) => d,
402
+ y2: (d) => d
403
+ });
404
+ const phases = ctx.text(ctx.marks("phases", "text", layout.phases, (d) => d.name), 10.5, "secondary").attr("font-weight", 600).text((d) => FrameworkText.fit(d.name, plot.width, 10.5));
405
+ ctx.set(phases, {
406
+ x: 0,
407
+ y: (d) => d.top + 11
408
+ });
409
+ const cells = ctx.marks("cells", "g", this.#cells(layout, ctx), (d) => d.key);
410
+ const tiles = cells.selectAll("rect").data((d) => [d]).join("rect").attr("rx", 4).attr("fill", (d) => d.fill);
411
+ ctx.set(tiles, {
412
+ x: (d) => d.x,
413
+ y: (d) => d.y,
414
+ width: (d) => d.width,
415
+ height: (d) => d.height
416
+ });
417
+ const letters = cells.selectAll("text").data((d) => [d]).join("text").attr("text-anchor", "middle").attr("font-weight", 700).attr("font-size", (d) => d.size).attr("font-family", theme.fontFamily).attr("pointer-events", "none").attr("fill", (d) => palette.inkOn(d.fill)).text((d) => d.text);
418
+ ctx.set(letters, {
419
+ x: (d) => d.middle,
420
+ y: (d) => d.baseline
421
+ });
422
+ ctx.hover(cells, (d) => FrameworkText.tip(d.task.name, [["Role", d.role], ["Assignment", d.letters.map((letter) => NAMES[letter]).join(" and ")]], d.fill));
423
+ const pills = ctx.marks("pills", "g", this.#pills(layout, ctx), (d) => d.row.task.name).attr("pointer-events", "none");
424
+ const plates = pills.selectAll("rect").data((d) => [d]).join("rect").attr("rx", 4).attr("fill", (d) => d.fill);
425
+ ctx.set(plates, {
426
+ x: (d) => d.x,
427
+ y: (d) => d.y,
428
+ width: (d) => d.width,
429
+ height: (d) => d.height
430
+ });
431
+ const words = pills.selectAll("text").data((d) => [d]).join("text").attr("font-size", 10).attr("font-weight", 600).attr("font-family", theme.fontFamily).attr("fill", (d) => d.ink).text((d) => d.text);
432
+ ctx.set(words, {
433
+ x: (d) => d.x + 6,
434
+ y: (d) => d.baseline
435
+ });
436
+ const counts = load && plan.tasks.length ? layout.columns.map((column) => ({
437
+ column,
438
+ tasks: plan.tasks.filter((task) => task.cells.has(column.role)).length
439
+ })) : [];
440
+ const loads = ctx.text(ctx.marks("loads", "text", counts, (d) => d.column.role), 11, "ink").attr("text-anchor", "middle").attr("font-weight", 600).text((d) => d.tasks);
441
+ ctx.set(loads, {
442
+ x: (d) => d.column.x + d.column.width / 2,
443
+ y: layout.loadTop + 13
444
+ });
445
+ const loadName = ctx.text(ctx.marks("load-name", "text", counts.length ? ["Tasks per role"] : []), 10.5, "secondary").text((d) => d);
446
+ ctx.set(loadName, {
447
+ x: 0,
448
+ y: layout.loadTop + 13
449
+ });
450
+ const footer = ctx.text(ctx.marks("footer", "text", layout.footer.lines.length ? [layout.footer] : []), FOOT, "secondary");
451
+ FrameworkText.write(ctx, footer);
452
+ ctx.hover(footer, () => ({
453
+ title: layout.footer.lines.join(" "),
454
+ rows: [...layout.hiddenTasks.map((task) => [FrameworkText.fit(task.name, 240, 12), "task not shown"]), ...layout.hiddenRoles.map((role) => [FrameworkText.fit(role, 240, 12), "role not shown"])]
455
+ }));
456
+ }
457
+ };
458
+ export { RaciMatrix };