@mstone6969/graph 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -14
- package/dist/ExcelWriter-3ZSYUH4T.cjs +154 -0
- package/dist/GraphType.js +24 -38
- package/dist/PdfWriter-6HJFYMCO.cjs +205 -0
- package/dist/catalog-YWX6FHRM.cjs +8 -0
- package/dist/catalog.js +5 -1
- package/dist/charts/bars/index.js +2 -12
- package/dist/charts/comparison/EffectForestPlot.js +1 -1
- package/dist/charts/comparison/index.js +1 -8
- package/dist/charts/demography/Nomogram.js +3 -3
- package/dist/charts/density/HexbinChart.js +6 -4
- package/dist/charts/diagrams/ArchitectureCanvas.js +68 -0
- package/dist/charts/diagrams/ArchitectureDiagram.js +331 -0
- package/dist/charts/diagrams/ArchitectureLayout.js +106 -0
- package/dist/charts/diagrams/ArchitectureLinkLabels.js +78 -0
- package/dist/charts/diagrams/ArchitectureModel.js +108 -0
- package/dist/charts/diagrams/ArchitectureRouter.js +348 -0
- package/dist/charts/diagrams/ArchitectureSamples.js +312 -0
- package/dist/charts/diagrams/ArchitectureShapes.js +130 -0
- package/dist/charts/diagrams/ArchitectureText.js +25 -0
- package/dist/charts/diagrams/DiagramText.js +38 -0
- package/dist/charts/diagrams/EntityRelationshipDiagram.js +465 -0
- package/dist/charts/diagrams/ErCrowsFoot.js +33 -0
- package/dist/charts/diagrams/ErSamples.js +205 -0
- package/dist/charts/diagrams/ErSchema.js +122 -0
- package/dist/charts/diagrams/LogicCircuit.js +154 -0
- package/dist/charts/diagrams/LogicCircuitLayout.js +600 -0
- package/dist/charts/diagrams/LogicCircuitSamples.js +66 -0
- package/dist/charts/diagrams/LogicGateDiagram.js +216 -0
- package/dist/charts/diagrams/LogicGateSymbol.js +113 -0
- package/dist/charts/diagrams/NetworkTopologyDiagram.js +247 -0
- package/dist/charts/diagrams/SequenceDiagram.js +351 -0
- package/dist/charts/diagrams/SequenceLayout.js +422 -0
- package/dist/charts/diagrams/SequenceModel.js +221 -0
- package/dist/charts/diagrams/TopologyLayout.js +143 -0
- package/dist/charts/diagrams/architectureEnums.js +50 -0
- package/dist/charts/diagrams/index.js +26 -0
- package/dist/charts/diagrams/logicEnums.js +42 -0
- package/dist/charts/diagrams/sequenceEnums.js +40 -0
- package/dist/charts/distribution/HistogramChart.js +25 -3
- package/dist/charts/distribution/index.js +1 -3
- package/dist/charts/earthscience/HovmollerDiagram.js +477 -0
- package/dist/charts/earthscience/HovmollerEnums.js +42 -0
- package/dist/charts/earthscience/HovmollerField.js +180 -0
- package/dist/charts/earthscience/HovmollerSample.js +40 -0
- package/dist/charts/earthscience/HovmollerSpeed.js +71 -0
- package/dist/charts/earthscience/index.js +10 -2
- package/dist/charts/engineering/ConstellationDiagram.js +26 -6
- package/dist/charts/engineering/EyeDiagram.js +2 -1
- package/dist/charts/engineering/PoleZeroPlot.js +32 -8
- package/dist/charts/financial/RelativeStrengthRotation.js +434 -0
- package/dist/charts/financial/RelativeStrengthRotationMath.js +71 -0
- package/dist/charts/financial/RelativeStrengthRotationSamples.js +27 -0
- package/dist/charts/financial/index.js +5 -3
- package/dist/charts/flow/index.js +1 -5
- package/dist/charts/frameworks/AnsoffMatrix.js +523 -0
- package/dist/charts/frameworks/FrameworkText.js +98 -0
- package/dist/charts/frameworks/NpsGauge.js +279 -0
- package/dist/charts/frameworks/NpsSurvey.js +58 -0
- package/dist/charts/frameworks/PerceptualMap.js +419 -0
- package/dist/charts/frameworks/PerceptualProjection.js +142 -0
- package/dist/charts/frameworks/PositioningSamples.js +191 -0
- package/dist/charts/frameworks/RaciMatrix.js +458 -0
- package/dist/charts/frameworks/RaciPlan.js +81 -0
- package/dist/charts/frameworks/RaciSamples.js +76 -0
- package/dist/charts/frameworks/SwotMatrix.js +358 -0
- package/dist/charts/frameworks/SwotSamples.js +85 -0
- package/dist/charts/frameworks/VanWestendorpChart.js +187 -0
- package/dist/charts/frameworks/VanWestendorpSurvey.js +197 -0
- package/dist/charts/frameworks/index.js +26 -0
- package/dist/charts/frameworks/raciEnums.js +30 -0
- package/dist/charts/frameworks/swotEnums.js +34 -0
- package/dist/charts/genomics/CircosPlot.js +1 -1
- package/dist/charts/geoflows/GeoFlowChart.js +2 -2
- package/dist/charts/geoflows/GeoFlowSample.js +2 -1
- package/dist/charts/geomaps/FloorPlanMap.js +324 -0
- package/dist/charts/geomaps/FloorPlanModel.js +179 -0
- package/dist/charts/geomaps/FloorPlanSamples.js +416 -0
- package/dist/charts/geomaps/GeoLand.js +2 -2
- package/dist/charts/geomaps/GeoSamples.js +2 -1
- package/dist/charts/geomaps/GraticuleMap.js +2 -2
- package/dist/charts/geomaps/RoomLabelFit.js +200 -0
- package/dist/charts/geomaps/TissotMap.js +2 -2
- package/dist/charts/geomaps/index.js +8 -4
- package/dist/charts/hierarchy/SunburstChart.js +17 -10
- package/dist/charts/hierarchy/index.js +2 -11
- package/dist/charts/indicators/BulletChart.js +8 -2
- package/dist/charts/indicators/SpeedometerChart.js +1 -1
- package/dist/charts/indicators/ThermometerChart.js +1 -1
- package/dist/charts/lifescience/LoessFit.js +113 -0
- package/dist/charts/lifescience/MaPlot.js +337 -0
- package/dist/charts/lifescience/MaPlotSamples.js +35 -0
- package/dist/charts/lifescience/OncoGlyph.js +137 -0
- package/dist/charts/lifescience/OncoPrint.js +373 -0
- package/dist/charts/lifescience/OncoPrintMatrix.js +107 -0
- package/dist/charts/lifescience/OncoPrintSamples.js +142 -0
- package/dist/charts/lifescience/PedigreeChart.js +306 -0
- package/dist/charts/lifescience/PedigreeLayout.js +261 -0
- package/dist/charts/lifescience/PedigreeRows.js +70 -0
- package/dist/charts/lifescience/PedigreeSamples.js +161 -0
- package/dist/charts/lifescience/PedigreeSymbol.js +47 -0
- package/dist/charts/lifescience/RamachandranPlot.js +364 -0
- package/dist/charts/lifescience/RamachandranRegions.js +2712 -0
- package/dist/charts/lifescience/RamachandranSamples.js +235 -0
- package/dist/charts/lifescience/index.js +25 -0
- package/dist/charts/lifescience/pedigreeEnums.js +21 -0
- package/dist/charts/lifescience/ramachandranEnums.js +34 -0
- package/dist/charts/linesareas/DualAxisChart.js +1 -1
- package/dist/charts/linesareas/LineEnums.js +1 -15
- package/dist/charts/linesareas/MultiLineChart.js +1 -1
- package/dist/charts/linesareas/TrendChart.js +15 -5
- package/dist/charts/linesareas/index.js +2 -6
- package/dist/charts/massive/MassiveChart.js +3 -2
- package/dist/charts/mathematical/FourierSeriesChart.js +7 -1
- package/dist/charts/mathematical/index.js +2 -6
- package/dist/charts/matrix/GridPlot.js +27 -2
- package/dist/charts/matrix/TilegramChart.js +2 -2
- package/dist/charts/mlcharts/AttentionGrid.js +79 -0
- package/dist/charts/mlcharts/AttentionHeatmap.js +329 -0
- package/dist/charts/mlcharts/AttentionModel.js +132 -0
- package/dist/charts/mlcharts/AttentionSamples.js +82 -0
- package/dist/charts/mlcharts/CellNumber.js +8 -0
- package/dist/charts/mlcharts/CurveDiagnosticSamples.js +95 -0
- package/dist/charts/mlcharts/CurvePickMark.js +31 -0
- package/dist/charts/mlcharts/DecisionBoundaryChart.js +316 -0
- package/dist/charts/mlcharts/DecisionSamples.js +60 -0
- package/dist/charts/mlcharts/DecisionSurface.js +130 -0
- package/dist/charts/mlcharts/DistinctTicks.js +13 -0
- package/dist/charts/mlcharts/ElbowMath.js +153 -0
- package/dist/charts/mlcharts/ElbowPlot.js +273 -0
- package/dist/charts/mlcharts/ExplainabilitySamples.js +70 -0
- package/dist/charts/mlcharts/FeatureImportanceChart.js +150 -0
- package/dist/charts/mlcharts/ImportanceRanking.js +98 -0
- package/dist/charts/mlcharts/LearningCurveChart.js +276 -0
- package/dist/charts/mlcharts/LearningCurveMath.js +92 -0
- package/dist/charts/mlcharts/PartialDependence.js +92 -0
- package/dist/charts/mlcharts/PartialDependencePlot.js +225 -0
- package/dist/charts/mlcharts/WideLabel.js +28 -0
- package/dist/charts/mlcharts/curveDiagnosticEnums.js +37 -0
- package/dist/charts/mlcharts/index.js +32 -0
- package/dist/charts/proportion/index.js +5 -9
- package/dist/charts/scatter/CategoryScatterBase.js +1 -4
- package/dist/charts/scatter/ConnectedScatter.js +7 -4
- package/dist/charts/scatter/MarginalScatter.js +13 -8
- package/dist/charts/scatter/index.js +4 -17
- package/dist/charts/scientific/SpectrogramChart.js +9 -3
- package/dist/charts/scientific/index.js +2 -2
- package/dist/charts/signals/index.js +3 -13
- package/dist/charts/statistics/LorenzCurve.js +7 -4
- package/dist/charts/threebars/Candlestick3DChart.js +18 -2
- package/dist/charts/threeglobe/GlobeChart.js +2 -2
- package/dist/charts/threeglobe/GlobeSamples.js +2 -1
- package/dist/charts/threeglobe/RingGlobe.js +1 -1
- package/dist/charts/threenetwork/ConeTreeChart.js +2 -1
- package/dist/charts/threenetwork/Gantt3DChart.js +3 -2
- package/dist/charts/threesurfaces/Ridgeline3DChart.js +22 -4
- package/dist/charts/threevolume/LorenzAttractor.js +19 -0
- package/dist/charts/threevolume/VolumeRaycastChart.js +4 -3
- package/dist/charts/threevolume/volumeEnums.js +1 -0
- package/dist/charts/timeline/index.js +2 -10
- package/dist/chunk-753UBLV2.cjs +33 -0
- package/dist/chunk-VX54A7KA.cjs +17 -0
- package/dist/{chunk-W5GAAAZY.cjs → chunk-WAODDPYX.cjs} +24874 -17620
- package/dist/chunk-Z3JPIJSC.cjs +338 -0
- package/dist/core/CartesianChart.js +87 -11
- package/dist/core/Chart.js +63 -15
- package/dist/core/Chart2D.js +54 -17
- package/dist/core/Chart3D.js +56 -20
- package/dist/core/ChartExportError.js +2 -0
- package/dist/core/ChartExporter.js +50 -8
- package/dist/core/ColumnCheck.js +53 -0
- package/dist/core/Context2D.js +29 -15
- package/dist/core/Context3D.js +7 -2
- package/dist/core/ExcelWriter.js +152 -0
- package/dist/core/ExportText.js +28 -0
- package/dist/core/MarkOrigin.js +88 -0
- package/dist/core/MarkStyle.js +294 -0
- package/dist/core/Palette.js +27 -7
- package/dist/core/PdfPage.js +13 -0
- package/dist/core/PdfWriter.js +245 -0
- package/dist/core/Scales.js +52 -2
- package/dist/core/SceneOverlay.js +4 -4
- package/dist/core/TextFit.js +37 -0
- package/dist/core/Theme.js +28 -1
- package/dist/core/enums.js +33 -1
- package/dist/core/pathMorph.js +15 -0
- package/dist/data/SampleData.js +1 -17
- package/dist/data/WorldSample.js +23 -0
- package/dist/index.cjs +810 -691
- package/dist/index.d.cts +8390 -2925
- package/dist/index.d.ts +8392 -2927
- package/dist/index.js +106 -56
- package/dist/react/ChartView.js +18 -8
- package/dist/react/GraphHandle.js +2 -0
- package/dist/react/components.js +74 -116
- package/package.json +13 -3
- package/dist/catalog-4UP2YFNR.cjs +0 -7
- package/dist/charts/bars/BulletBar.js +0 -184
- package/dist/charts/bars/ErrorBarColumn.js +0 -147
- package/dist/charts/bars/HistogramBar.js +0 -217
- package/dist/charts/bars/MarimekkoBar.js +0 -191
- package/dist/charts/bars/ParetoBar.js +0 -143
- package/dist/charts/comparison/PanelChart.js +0 -70
- package/dist/charts/comparison/SmallMultipleBars.js +0 -134
- package/dist/charts/comparison/SpanChart.js +0 -118
- package/dist/charts/comparison/TrellisChart.js +0 -158
- package/dist/charts/distribution/RidgeSlabs3DChart.js +0 -169
- package/dist/charts/financial/Candlestick3D.js +0 -157
- package/dist/charts/flow/BumpFlow.js +0 -212
- package/dist/charts/flow/StreamFlow.js +0 -174
- package/dist/charts/geomaps/ThiessenMap.js +0 -106
- package/dist/charts/hierarchy/ConeTree3D.js +0 -135
- package/dist/charts/hierarchy/Hierarchy3DChart.js +0 -27
- package/dist/charts/hierarchy/SpherePack3D.js +0 -81
- package/dist/charts/hierarchy/SunburstTower3D.js +0 -93
- package/dist/charts/hierarchy/TreemapSkyline3D.js +0 -98
- package/dist/charts/linesareas/ConnectedScatterTime.js +0 -206
- package/dist/charts/linesareas/SmallMultipleLines.js +0 -153
- package/dist/charts/mathematical/ParametricSurface3D.js +0 -234
- package/dist/charts/mathematical/StrangeAttractor3D.js +0 -251
- package/dist/charts/proportion/ConeFunnel.js +0 -126
- package/dist/charts/proportion/ExtrudedPieChart.js +0 -123
- package/dist/charts/scatter/BeeswarmScatter.js +0 -151
- package/dist/charts/scatter/BinnedScatterBase.js +0 -51
- package/dist/charts/scatter/ContourScatter.js +0 -116
- package/dist/charts/scatter/GridBinScatter.js +0 -98
- package/dist/charts/scatter/HexbinScatter.js +0 -74
- package/dist/charts/scatter/StripScatter.js +0 -107
- package/dist/charts/scatter/SunflowerScatter.js +0 -109
- package/dist/charts/signals/ConstellationChart.js +0 -153
- package/dist/charts/signals/CorrelogramChart.js +0 -171
- package/dist/charts/signals/EyeDiagramChart.js +0 -155
- package/dist/charts/signals/PoleZeroChart.js +0 -164
- package/dist/charts/signals/SpectrogramChart.js +0 -79
- package/dist/charts/timeline/CalendarSkylineChart.js +0 -128
- package/dist/charts/timeline/ScheduleBlocksChart.js +0 -119
- package/dist/charts/timeline/SpiralTimeline.js +0 -154
- package/dist/charts/timeline/StreamTimeline.js +0 -141
|
@@ -0,0 +1,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 };
|