@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,81 @@
|
|
|
1
|
+
import { FrameworkText } from "./FrameworkText.js";
|
|
2
|
+
import { RaciIssue, RaciLetter } from "./raciEnums.js";
|
|
3
|
+
// src/charts/frameworks/RaciPlan.ts
|
|
4
|
+
var ORDER = [
|
|
5
|
+
RaciLetter.Accountable,
|
|
6
|
+
RaciLetter.Responsible,
|
|
7
|
+
RaciLetter.Consulted,
|
|
8
|
+
RaciLetter.Informed
|
|
9
|
+
];
|
|
10
|
+
var WORDS = {
|
|
11
|
+
responsible: RaciLetter.Responsible,
|
|
12
|
+
accountable: RaciLetter.Accountable,
|
|
13
|
+
consulted: RaciLetter.Consulted,
|
|
14
|
+
informed: RaciLetter.Informed
|
|
15
|
+
};
|
|
16
|
+
var UNPHASED = "Other";
|
|
17
|
+
/** Reads long RACI rows into tasks and roles, and audits each task for exactly one A and at least one R. */
|
|
18
|
+
var RaciPlan = class RaciPlan {
|
|
19
|
+
tasks = [];
|
|
20
|
+
roles = [];
|
|
21
|
+
/** Rows with no task, no role, or an assignment that is not R, A, C or I. */
|
|
22
|
+
skipped = 0;
|
|
23
|
+
constructor(data, fields) {
|
|
24
|
+
const byName = /* @__PURE__ */ new Map();
|
|
25
|
+
for (const raw of Array.isArray(data) ? data : []) {
|
|
26
|
+
const row = raw ?? {};
|
|
27
|
+
const name = FrameworkText.clean(row[fields.task]), role = FrameworkText.clean(row[fields.role]);
|
|
28
|
+
if (!name || !role) {
|
|
29
|
+
this.skipped++;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
let task = byName.get(name);
|
|
33
|
+
if (!task) {
|
|
34
|
+
task = {
|
|
35
|
+
name,
|
|
36
|
+
phase: FrameworkText.clean(row[fields.phase]) || UNPHASED,
|
|
37
|
+
cells: /* @__PURE__ */ new Map(),
|
|
38
|
+
accountable: [],
|
|
39
|
+
responsible: [],
|
|
40
|
+
issues: []
|
|
41
|
+
};
|
|
42
|
+
byName.set(name, task);
|
|
43
|
+
this.tasks.push(task);
|
|
44
|
+
}
|
|
45
|
+
if (!this.roles.includes(role)) this.roles.push(role);
|
|
46
|
+
const letters = RaciPlan.letters(row[fields.assignment]);
|
|
47
|
+
if (!letters) {
|
|
48
|
+
this.skipped++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (letters.length) task.cells.set(role, ORDER.filter((letter) => letters.includes(letter) || task.cells.get(role)?.includes(letter)));
|
|
52
|
+
}
|
|
53
|
+
for (const task of this.tasks) RaciPlan.#audit(task);
|
|
54
|
+
}
|
|
55
|
+
/** Letters in a cell such as `R`, `A/R`, `ar` or `Accountable`. Blank is none; null means unreadable. */
|
|
56
|
+
static letters(value) {
|
|
57
|
+
const text = FrameworkText.clean(value).toLowerCase();
|
|
58
|
+
const found = /* @__PURE__ */ new Set();
|
|
59
|
+
for (const token of text.split(/[^a-z]+/).filter(Boolean)) if (WORDS[token]) found.add(WORDS[token]);
|
|
60
|
+
else if (/^[raci]{1,4}$/.test(token)) for (const char of token) found.add(char.toUpperCase());
|
|
61
|
+
else return null;
|
|
62
|
+
return [...found];
|
|
63
|
+
}
|
|
64
|
+
static #audit(task) {
|
|
65
|
+
const holding = (letter) => [...task.cells].filter(([, letters]) => letters.includes(letter)).map(([role]) => role);
|
|
66
|
+
task.accountable = holding(RaciLetter.Accountable);
|
|
67
|
+
task.responsible = holding(RaciLetter.Responsible);
|
|
68
|
+
if (!task.accountable.length) task.issues.push(RaciIssue.NoAccountable);
|
|
69
|
+
if (task.accountable.length > 1) task.issues.push(RaciIssue.SeveralAccountable);
|
|
70
|
+
if (!task.responsible.length) task.issues.push(RaciIssue.NoResponsible);
|
|
71
|
+
}
|
|
72
|
+
get flagged() {
|
|
73
|
+
return this.tasks.filter((task) => task.issues.length);
|
|
74
|
+
}
|
|
75
|
+
/** How many tasks have this role, by letter. */
|
|
76
|
+
load(role) {
|
|
77
|
+
const count = (letter) => this.tasks.filter((task) => task.cells.get(role)?.includes(letter)).length;
|
|
78
|
+
return Object.fromEntries(ORDER.map((letter) => [letter, count(letter)]));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
export { RaciPlan };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/charts/frameworks/RaciSamples.ts
|
|
2
|
+
/**
|
|
3
|
+
* SYNTHETIC: an invented product launch, with three faults planted so the validation has something
|
|
4
|
+
* to find: one task with no A, one with two, one with no R.
|
|
5
|
+
*/
|
|
6
|
+
var LAUNCH = [
|
|
7
|
+
[
|
|
8
|
+
"Plan",
|
|
9
|
+
"Agree launch scope",
|
|
10
|
+
{
|
|
11
|
+
PM: "A/R",
|
|
12
|
+
Engineering: "C",
|
|
13
|
+
Design: "C",
|
|
14
|
+
Marketing: "I"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
"Plan",
|
|
19
|
+
"Estimate engineering effort and staffing",
|
|
20
|
+
{
|
|
21
|
+
PM: "C",
|
|
22
|
+
Engineering: "R",
|
|
23
|
+
Finance: "I"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
[
|
|
27
|
+
"Plan",
|
|
28
|
+
"Approve launch budget",
|
|
29
|
+
{
|
|
30
|
+
PM: "A",
|
|
31
|
+
Finance: "A",
|
|
32
|
+
Marketing: "R"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"Ship",
|
|
37
|
+
"Build and test release candidate",
|
|
38
|
+
{
|
|
39
|
+
PM: "A",
|
|
40
|
+
Engineering: "R",
|
|
41
|
+
Design: "C"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
[
|
|
45
|
+
"Ship",
|
|
46
|
+
"Write launch announcement",
|
|
47
|
+
{
|
|
48
|
+
PM: "A",
|
|
49
|
+
Design: "C",
|
|
50
|
+
Marketing: "C",
|
|
51
|
+
Support: "I"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
"Ship",
|
|
56
|
+
"Brief customer support",
|
|
57
|
+
{
|
|
58
|
+
PM: "A",
|
|
59
|
+
Marketing: "I",
|
|
60
|
+
Support: "R"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
];
|
|
64
|
+
var RaciSamples = class {
|
|
65
|
+
/** The invented launch as long rows. A new seed rotates its tasks. */
|
|
66
|
+
static launch(seed = 1) {
|
|
67
|
+
const turn = (seed - 1) % LAUNCH.length;
|
|
68
|
+
return [...LAUNCH.slice(turn), ...LAUNCH.slice(0, turn)].flatMap(([phase, task, assignments]) => Object.entries(assignments).map(([role, assignment]) => ({
|
|
69
|
+
phase,
|
|
70
|
+
task,
|
|
71
|
+
role,
|
|
72
|
+
assignment
|
|
73
|
+
})));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
export { RaciSamples };
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { Chart2D } from "../../core/Chart2D.js";
|
|
2
|
+
import { ChartFamily, DataShape, Dimension, LegendPosition } from "../../core/enums.js";
|
|
3
|
+
import { FrameworkText } from "./FrameworkText.js";
|
|
4
|
+
import { SwotItemMark, SwotLayout, SwotOrder, SwotQuadrant, SwotSizing } from "./swotEnums.js";
|
|
5
|
+
import { SwotSamples } from "./SwotSamples.js";
|
|
6
|
+
// src/charts/frameworks/SwotMatrix.ts
|
|
7
|
+
var QUADRANTS = Object.values(SwotQuadrant);
|
|
8
|
+
var HEADER = 24;
|
|
9
|
+
var PAD = 6;
|
|
10
|
+
var SIDE = 124;
|
|
11
|
+
var GAP = 6;
|
|
12
|
+
var RAIL = 18;
|
|
13
|
+
var NOTE = 16;
|
|
14
|
+
var ITEM_GAP = 4;
|
|
15
|
+
var DOT = 4;
|
|
16
|
+
var SIZES = [
|
|
17
|
+
12,
|
|
18
|
+
11,
|
|
19
|
+
10
|
|
20
|
+
];
|
|
21
|
+
var INTERNAL = /* @__PURE__ */ new Set([SwotQuadrant.Strengths, SwotQuadrant.Weaknesses]);
|
|
22
|
+
var HELPFUL = /* @__PURE__ */ new Set([SwotQuadrant.Strengths, SwotQuadrant.Opportunities]);
|
|
23
|
+
var NAMES = /* @__PURE__ */ new Map([
|
|
24
|
+
["s", SwotQuadrant.Strengths],
|
|
25
|
+
["strength", SwotQuadrant.Strengths],
|
|
26
|
+
["strengths", SwotQuadrant.Strengths],
|
|
27
|
+
["w", SwotQuadrant.Weaknesses],
|
|
28
|
+
["weakness", SwotQuadrant.Weaknesses],
|
|
29
|
+
["weaknesses", SwotQuadrant.Weaknesses],
|
|
30
|
+
["o", SwotQuadrant.Opportunities],
|
|
31
|
+
["opportunity", SwotQuadrant.Opportunities],
|
|
32
|
+
["opportunities", SwotQuadrant.Opportunities],
|
|
33
|
+
["t", SwotQuadrant.Threats],
|
|
34
|
+
["threat", SwotQuadrant.Threats],
|
|
35
|
+
["threats", SwotQuadrant.Threats]
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* Lists a situation's internal strengths and weaknesses against its external opportunities and
|
|
39
|
+
* threats. Method: SWOT analysis, usually traced to the Harvard business policy school (Learned,
|
|
40
|
+
* Christensen, Andrews and Guth, 1965): rows are origin, columns are effect. Items that do not fit
|
|
41
|
+
* are counted in a "+N more" line and text shrinks no further than 10px; only a lone item too long
|
|
42
|
+
* for its whole box ends in an ellipsis. Hidden and cut text stays in the tooltip and the CSV.
|
|
43
|
+
*/
|
|
44
|
+
var SwotMatrix = class extends Chart2D {
|
|
45
|
+
static meta = {
|
|
46
|
+
id: "swot-matrix",
|
|
47
|
+
name: "SWOT matrix",
|
|
48
|
+
family: ChartFamily.Matrix,
|
|
49
|
+
dimension: Dimension.Two,
|
|
50
|
+
shape: DataShape.Table,
|
|
51
|
+
description: "Sets out strengths, weaknesses, opportunities and threats as four lists of wrapped text for strategy reviews.",
|
|
52
|
+
tags: [
|
|
53
|
+
"framework",
|
|
54
|
+
"strategy",
|
|
55
|
+
"swot",
|
|
56
|
+
"text",
|
|
57
|
+
"quadrant"
|
|
58
|
+
],
|
|
59
|
+
sample: (seed) => SwotSamples.bakery(seed),
|
|
60
|
+
variants: {
|
|
61
|
+
layout: [
|
|
62
|
+
SwotLayout.Grid,
|
|
63
|
+
SwotLayout.Columns,
|
|
64
|
+
SwotLayout.Rows
|
|
65
|
+
],
|
|
66
|
+
sizing: [SwotSizing.Equal, SwotSizing.Content],
|
|
67
|
+
framing: [true, false],
|
|
68
|
+
itemMark: [SwotItemMark.Bullet, SwotItemMark.Code]
|
|
69
|
+
},
|
|
70
|
+
/** Side-by-side columns share one height, so there is nothing to size. */
|
|
71
|
+
valid: (v) => v.layout !== SwotLayout.Columns || v.sizing === SwotSizing.Equal
|
|
72
|
+
};
|
|
73
|
+
defaults() {
|
|
74
|
+
return {
|
|
75
|
+
quadrant: "quadrant",
|
|
76
|
+
item: "item",
|
|
77
|
+
weight: "weight",
|
|
78
|
+
layout: SwotLayout.Grid,
|
|
79
|
+
sizing: SwotSizing.Equal,
|
|
80
|
+
framing: true,
|
|
81
|
+
itemMark: SwotItemMark.Bullet,
|
|
82
|
+
order: SwotOrder.Input
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
plotMargin() {
|
|
86
|
+
return {
|
|
87
|
+
top: 8,
|
|
88
|
+
right: 16,
|
|
89
|
+
bottom: 12,
|
|
90
|
+
left: 16
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Each quadrant is named on its own header. */
|
|
94
|
+
get legendPosition() {
|
|
95
|
+
return LegendPosition.None;
|
|
96
|
+
}
|
|
97
|
+
/** Rows dropped for naming no SWOT quadrant or no item text. */
|
|
98
|
+
get skipped() {
|
|
99
|
+
return this.#parse().skipped;
|
|
100
|
+
}
|
|
101
|
+
#parse() {
|
|
102
|
+
const { quadrant, item, weight, data, order } = this.options;
|
|
103
|
+
const rows = Array.isArray(data) ? data : [];
|
|
104
|
+
const lists = Object.fromEntries(QUADRANTS.map((name) => [name, []]));
|
|
105
|
+
let skipped = 0;
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
const cells = row ?? {};
|
|
108
|
+
const name = NAMES.get(FrameworkText.clean(cells[quadrant]).toLowerCase()), text = FrameworkText.clean(cells[item]);
|
|
109
|
+
if (!name || !text) {
|
|
110
|
+
skipped++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const raw = cells[weight], score = raw === null || raw === "" ? NaN : Number(raw);
|
|
114
|
+
lists[name].push({
|
|
115
|
+
quadrant: name,
|
|
116
|
+
text,
|
|
117
|
+
weight: Number.isFinite(score) ? score : null,
|
|
118
|
+
code: ""
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
for (const name of QUADRANTS) {
|
|
122
|
+
if (order === SwotOrder.Weight) lists[name].sort((a, b) => (b.weight ?? -Infinity) - (a.weight ?? -Infinity));
|
|
123
|
+
lists[name].forEach((entry, i) => {
|
|
124
|
+
entry.code = `${name[0]}${i + 1}`;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
lists,
|
|
129
|
+
skipped
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
#indent(size) {
|
|
133
|
+
return this.options.itemMark === SwotItemMark.Code ? Math.ceil(FrameworkText.width("T00", size)) + 6 : 16;
|
|
134
|
+
}
|
|
135
|
+
/** Rows put the quadrant's heading beside its text, where the other layouts put it above. */
|
|
136
|
+
#side(width) {
|
|
137
|
+
return this.options.layout === SwotLayout.Rows ? Math.min(SIDE, width * .4) : 0;
|
|
138
|
+
}
|
|
139
|
+
#chrome() {
|
|
140
|
+
return (this.options.layout === SwotLayout.Rows ? 0 : HEADER) + 12;
|
|
141
|
+
}
|
|
142
|
+
/** Height a quadrant wants at this size and width. */
|
|
143
|
+
#wants(list, width, size) {
|
|
144
|
+
const lead = Math.round(size * 1.3), wrap = width - this.#side(width) - 12 - this.#indent(size);
|
|
145
|
+
const text = list.reduce((sum, entry) => sum + FrameworkText.wrap(entry.text, wrap, size).length * lead + ITEM_GAP, -4);
|
|
146
|
+
return this.#chrome() + Math.max(text, lead);
|
|
147
|
+
}
|
|
148
|
+
#heights(frame, grid, lists, width, size) {
|
|
149
|
+
const room = frame.height - GAP * (grid.length - 1);
|
|
150
|
+
const equal = room / grid.length;
|
|
151
|
+
if (this.options.sizing !== SwotSizing.Content) return grid.map(() => equal);
|
|
152
|
+
const floor = this.#chrome() + Math.round(size * 1.3) * 2 + ITEM_GAP;
|
|
153
|
+
const wants = grid.map((row) => Math.max(0, Math.max(...row.map((name) => this.#wants(lists[name], width, size))) - floor));
|
|
154
|
+
const total = wants.reduce((sum, want) => sum + want, 0), spare = room - floor * grid.length;
|
|
155
|
+
return total > 0 && spare > 0 ? wants.map((want) => floor + spare * (want / total)) : grid.map(() => equal);
|
|
156
|
+
}
|
|
157
|
+
#boxes(frame, lists, size) {
|
|
158
|
+
const { layout } = this.options;
|
|
159
|
+
const grid = layout === SwotLayout.Columns ? [QUADRANTS] : layout === SwotLayout.Rows ? QUADRANTS.map((name) => [name]) : [QUADRANTS.slice(0, 2), QUADRANTS.slice(2)];
|
|
160
|
+
const across = grid[0].length, width = (frame.width - GAP * (across - 1)) / across;
|
|
161
|
+
let y = frame.y;
|
|
162
|
+
return this.#heights(frame, grid, lists, width, size).flatMap((height, r) => {
|
|
163
|
+
const row = grid[r].map((quadrant, c) => ({
|
|
164
|
+
quadrant,
|
|
165
|
+
x: frame.x + c * (width + GAP),
|
|
166
|
+
y,
|
|
167
|
+
width,
|
|
168
|
+
height,
|
|
169
|
+
count: lists[quadrant].length,
|
|
170
|
+
side: this.#side(width)
|
|
171
|
+
}));
|
|
172
|
+
y += height + GAP;
|
|
173
|
+
return row;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/** Lists whole items while they fit, keeping a line for the count of the rest. Only a lone item too long for the box is cut. */
|
|
177
|
+
#fill(box, list, size) {
|
|
178
|
+
const lead = Math.round(size * 1.3), indent = this.#indent(size), inner = box.width - box.side - 12, width = inner - indent;
|
|
179
|
+
const left = box.x + box.side + PAD, x = left + indent, room = box.height - this.#chrome();
|
|
180
|
+
const wrapped = list.map((entry) => FrameworkText.wrap(entry.text, width, size));
|
|
181
|
+
const cost = (count) => wrapped.slice(0, count).reduce((sum, lines) => sum + lines.length * lead + ITEM_GAP, 0);
|
|
182
|
+
let shown = list.length;
|
|
183
|
+
if (cost(shown) - ITEM_GAP > room) {
|
|
184
|
+
shown--;
|
|
185
|
+
while (shown > 0 && cost(shown) + lead > room) shown--;
|
|
186
|
+
}
|
|
187
|
+
let cut = false;
|
|
188
|
+
if (shown === 0 && list.length) {
|
|
189
|
+
const rest = list.length > 1 ? lead + ITEM_GAP : 0, rows = Math.floor((room - rest) / lead);
|
|
190
|
+
if (rows >= 1) {
|
|
191
|
+
cut = wrapped[0].length > rows;
|
|
192
|
+
wrapped[0] = FrameworkText.clamp(wrapped[0], rows, width, size);
|
|
193
|
+
shown = 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
let top = box.y + this.#chrome() - PAD;
|
|
197
|
+
const place = (entry, hidden, lines, key) => {
|
|
198
|
+
const block = {
|
|
199
|
+
key,
|
|
200
|
+
quadrant: box.quadrant,
|
|
201
|
+
entry,
|
|
202
|
+
hidden,
|
|
203
|
+
lines,
|
|
204
|
+
x,
|
|
205
|
+
y: top + size,
|
|
206
|
+
lead,
|
|
207
|
+
left,
|
|
208
|
+
top,
|
|
209
|
+
width: inner,
|
|
210
|
+
height: lines.length * lead,
|
|
211
|
+
size
|
|
212
|
+
};
|
|
213
|
+
top += block.height + ITEM_GAP;
|
|
214
|
+
return block;
|
|
215
|
+
};
|
|
216
|
+
const blocks = list.slice(0, shown).map((entry, i) => place(entry, [], wrapped[i], `${box.quadrant}:${i}`));
|
|
217
|
+
const hidden = list.slice(shown);
|
|
218
|
+
if (hidden.length && room >= lead + (shown ? cost(shown) : 0)) blocks.push(place(null, hidden, [`+${hidden.length} more`], `${box.quadrant}:more`));
|
|
219
|
+
return {
|
|
220
|
+
blocks,
|
|
221
|
+
whole: !hidden.length && !cut
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
#plan(frame, lists) {
|
|
225
|
+
let boxes = [], blocks = [];
|
|
226
|
+
for (const size of SIZES) {
|
|
227
|
+
boxes = this.#boxes(frame, lists, size);
|
|
228
|
+
const filled = boxes.map((box) => this.#fill(box, lists[box.quadrant], size));
|
|
229
|
+
blocks = filled.flatMap((fill) => fill.blocks);
|
|
230
|
+
if (filled.every((fill) => fill.whole)) break;
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
boxes,
|
|
234
|
+
blocks
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
#rails(frame) {
|
|
238
|
+
const { layout, framing } = this.options;
|
|
239
|
+
if (!framing) return [];
|
|
240
|
+
const across = (frame.width - GAP) / 2, down = (frame.height - GAP) / 2;
|
|
241
|
+
const top = (key, i) => ({
|
|
242
|
+
key,
|
|
243
|
+
label: key,
|
|
244
|
+
x: frame.x + (across + GAP) * i + across / 2,
|
|
245
|
+
y: 11,
|
|
246
|
+
along: across,
|
|
247
|
+
turned: false
|
|
248
|
+
});
|
|
249
|
+
const side = (key, i) => ({
|
|
250
|
+
key,
|
|
251
|
+
label: key,
|
|
252
|
+
x: 11,
|
|
253
|
+
y: frame.y + (down + GAP) * i + down / 2,
|
|
254
|
+
along: down,
|
|
255
|
+
turned: true
|
|
256
|
+
});
|
|
257
|
+
if (layout === SwotLayout.Columns) return ["Internal", "External"].map(top);
|
|
258
|
+
if (layout === SwotLayout.Rows) return ["Internal", "External"].map(side);
|
|
259
|
+
return [...["Helpful", "Harmful"].map(top), ...["Internal", "External"].map(side)];
|
|
260
|
+
}
|
|
261
|
+
draw(ctx) {
|
|
262
|
+
const { framing, layout, itemMark } = this.options;
|
|
263
|
+
const { lists, skipped } = this.#parse(), { plot, theme, palette } = ctx;
|
|
264
|
+
const colors = new Map(QUADRANTS.map((name) => [name, palette.color(name)]));
|
|
265
|
+
const left = framing && layout !== SwotLayout.Columns ? RAIL : 0, top = framing && layout !== SwotLayout.Rows ? RAIL : 0;
|
|
266
|
+
const frame = {
|
|
267
|
+
x: left,
|
|
268
|
+
y: top,
|
|
269
|
+
width: Math.max(0, plot.width - left),
|
|
270
|
+
height: Math.max(0, plot.height - top - (skipped ? NOTE : 0))
|
|
271
|
+
};
|
|
272
|
+
const { boxes, blocks } = this.#plan(frame, lists);
|
|
273
|
+
const rails = ctx.text(ctx.marks("rails", "text", this.#rails(frame), (d) => d.key), 11).attr("text-anchor", "middle").attr("font-weight", 600);
|
|
274
|
+
rails.text((d) => FrameworkText.fit(d.label, d.along, 11));
|
|
275
|
+
ctx.set(rails, { transform: (d) => `translate(${d.x},${d.y})${d.turned ? " rotate(-90)" : ""}` });
|
|
276
|
+
const rect = {
|
|
277
|
+
x: (d) => d.x,
|
|
278
|
+
y: (d) => d.y,
|
|
279
|
+
width: (d) => d.width
|
|
280
|
+
};
|
|
281
|
+
const panels = ctx.marks("panels", "rect", boxes, (d) => d.quadrant).attr("rx", 4).attr("fill", (d) => colors.get(d.quadrant)).attr("fill-opacity", .1);
|
|
282
|
+
ctx.set(panels, {
|
|
283
|
+
...rect,
|
|
284
|
+
height: (d) => d.height
|
|
285
|
+
});
|
|
286
|
+
const bands = ctx.marks("bands", "rect", boxes, (d) => d.quadrant).attr("rx", 4).attr("fill", (d) => colors.get(d.quadrant));
|
|
287
|
+
ctx.set(bands, {
|
|
288
|
+
x: (d) => d.x,
|
|
289
|
+
y: (d) => d.y,
|
|
290
|
+
width: (d) => d.side || d.width,
|
|
291
|
+
height: (d) => d.side ? d.height : Math.min(HEADER, d.height)
|
|
292
|
+
});
|
|
293
|
+
const shown = boxes.filter((box) => box.height >= HEADER);
|
|
294
|
+
const titles = ctx.text(ctx.marks("titles", "text", shown, (d) => d.quadrant), 12, "ink").attr("font-weight", 600).attr("fill", (d) => palette.inkOn(colors.get(d.quadrant)));
|
|
295
|
+
const headed = (d) => d.side || d.width;
|
|
296
|
+
titles.text((d) => FrameworkText.fit(d.quadrant, headed(d) - 12 - (headed(d) >= 70 ? 22 : 0), 12));
|
|
297
|
+
ctx.set(titles, {
|
|
298
|
+
x: (d) => d.x + PAD,
|
|
299
|
+
y: (d) => d.y + 16
|
|
300
|
+
});
|
|
301
|
+
const counts = ctx.text(ctx.marks("counts", "text", shown.filter((box) => headed(box) >= 70), (d) => d.quadrant), 12, "ink").attr("text-anchor", "end").attr("fill", (d) => palette.inkOn(colors.get(d.quadrant)));
|
|
302
|
+
counts.text((d) => d.count);
|
|
303
|
+
ctx.set(counts, {
|
|
304
|
+
x: (d) => d.x + headed(d) - PAD,
|
|
305
|
+
y: (d) => d.y + 16
|
|
306
|
+
});
|
|
307
|
+
const empties = boxes.filter((box) => !box.count && box.height >= this.#chrome() + 12);
|
|
308
|
+
const none = ctx.text(ctx.marks("none", "text", empties, (d) => d.quadrant), 11, "muted").text("None listed");
|
|
309
|
+
ctx.set(none, {
|
|
310
|
+
x: (d) => d.x + d.side + PAD,
|
|
311
|
+
y: (d) => d.y + this.#chrome() - PAD + 11
|
|
312
|
+
});
|
|
313
|
+
const items = ctx.marks("items", "g", blocks, (d) => d.key);
|
|
314
|
+
ctx.hover(items, (d) => {
|
|
315
|
+
const color = colors.get(d.quadrant);
|
|
316
|
+
if (!d.entry) return {
|
|
317
|
+
title: `${d.hidden.length} more ${d.quadrant}`,
|
|
318
|
+
color,
|
|
319
|
+
rows: d.hidden.flatMap((entry) => FrameworkText.wrap(entry.text, 320, 12).map((line) => [line, ""]))
|
|
320
|
+
};
|
|
321
|
+
const score = d.entry.weight === null ? [] : [["Weight", d.entry.weight]];
|
|
322
|
+
return FrameworkText.tip(d.entry.text, [
|
|
323
|
+
["Quadrant", d.quadrant],
|
|
324
|
+
["Origin", INTERNAL.has(d.quadrant) ? "Internal" : "External"],
|
|
325
|
+
["Effect", HELPFUL.has(d.quadrant) ? "Helpful" : "Harmful"],
|
|
326
|
+
...score
|
|
327
|
+
], color);
|
|
328
|
+
});
|
|
329
|
+
const hits = items.selectAll(":scope > rect").data((d) => [d]).join("rect").attr("fill", "transparent");
|
|
330
|
+
ctx.set(hits, {
|
|
331
|
+
x: (d) => d.left,
|
|
332
|
+
y: (d) => d.top,
|
|
333
|
+
width: (d) => d.width,
|
|
334
|
+
height: (d) => d.height
|
|
335
|
+
});
|
|
336
|
+
const bulleted = (d) => itemMark === SwotItemMark.Bullet && d.entry ? [d] : [];
|
|
337
|
+
const dots = items.selectAll("circle").data(bulleted).join("circle").attr("r", DOT).attr("fill", (d) => colors.get(d.quadrant)).attr("stroke", theme.surface).attr("stroke-width", 2);
|
|
338
|
+
ctx.set(dots, {
|
|
339
|
+
cx: (d) => d.left + DOT + 1,
|
|
340
|
+
cy: (d) => d.top + d.lead / 2
|
|
341
|
+
});
|
|
342
|
+
const coded = (d) => itemMark === SwotItemMark.Code && d.entry ? [d] : [];
|
|
343
|
+
const codes = ctx.text(items.selectAll("text.code").data(coded).join("text").attr("class", "code")).attr("font-weight", 600).attr("font-size", (d) => d.size).text((d) => d.entry.code);
|
|
344
|
+
ctx.set(codes, {
|
|
345
|
+
x: (d) => d.left,
|
|
346
|
+
y: (d) => d.y
|
|
347
|
+
});
|
|
348
|
+
const body = ctx.text(items.selectAll("text.body").data((d) => [d]).join("text").attr("class", "body"), 11, "ink").attr("font-size", (d) => d.size).attr("pointer-events", "none");
|
|
349
|
+
FrameworkText.write(ctx, body);
|
|
350
|
+
const notes = skipped ? [`${skipped} ${skipped === 1 ? "row" : "rows"} left out: no SWOT quadrant or no item text`] : [];
|
|
351
|
+
const note = ctx.text(ctx.marks("note", "text", notes), 10.5, "muted").text((d) => FrameworkText.fit(d, plot.width, 10.5));
|
|
352
|
+
ctx.set(note, {
|
|
353
|
+
x: 0,
|
|
354
|
+
y: plot.height - 3
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
export { SwotMatrix };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { SwotQuadrant } from "./swotEnums.js";
|
|
2
|
+
// src/charts/frameworks/SwotSamples.ts
|
|
3
|
+
/** SYNTHETIC: an invented neighbourhood bakery. Nothing here describes a real business or measurement. */
|
|
4
|
+
var BAKERY = [
|
|
5
|
+
[
|
|
6
|
+
SwotQuadrant.Strengths,
|
|
7
|
+
"Loyal weekday breakfast regulars",
|
|
8
|
+
8
|
|
9
|
+
],
|
|
10
|
+
[
|
|
11
|
+
SwotQuadrant.Strengths,
|
|
12
|
+
"Sourdough starter and recipes nobody nearby can copy",
|
|
13
|
+
9
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
SwotQuadrant.Strengths,
|
|
17
|
+
"Corner site with heavy foot traffic",
|
|
18
|
+
7
|
|
19
|
+
],
|
|
20
|
+
[
|
|
21
|
+
SwotQuadrant.Strengths,
|
|
22
|
+
"Staff who have stayed for years",
|
|
23
|
+
5
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
SwotQuadrant.Strengths,
|
|
27
|
+
"Own delivery bike",
|
|
28
|
+
2
|
|
29
|
+
],
|
|
30
|
+
[
|
|
31
|
+
SwotQuadrant.Weaknesses,
|
|
32
|
+
"Oven capacity caps the morning bake",
|
|
33
|
+
8
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
SwotQuadrant.Weaknesses,
|
|
37
|
+
"No online ordering or card-on-file for wholesale accounts",
|
|
38
|
+
6
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
SwotQuadrant.Weaknesses,
|
|
42
|
+
"Owner does all the buying",
|
|
43
|
+
7
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
SwotQuadrant.Opportunities,
|
|
47
|
+
"Office lunch catering within walking distance of the shop",
|
|
48
|
+
7
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
SwotQuadrant.Opportunities,
|
|
52
|
+
"Weekend baking classes",
|
|
53
|
+
4
|
|
54
|
+
],
|
|
55
|
+
[
|
|
56
|
+
SwotQuadrant.Opportunities,
|
|
57
|
+
"Wholesale bread for two new cafes",
|
|
58
|
+
8
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
SwotQuadrant.Threats,
|
|
62
|
+
"Flour and butter prices keep rising",
|
|
63
|
+
9
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
SwotQuadrant.Threats,
|
|
67
|
+
"A supermarket in-store bakery opening nearby next spring",
|
|
68
|
+
7
|
|
69
|
+
]
|
|
70
|
+
];
|
|
71
|
+
var SwotSamples = class {
|
|
72
|
+
/** The invented bakery, with items rotated by seed so a new seed reorders each quadrant. */
|
|
73
|
+
static bakery(seed = 1) {
|
|
74
|
+
return Object.values(SwotQuadrant).flatMap((quadrant) => {
|
|
75
|
+
const items = BAKERY.filter((item) => item[0] === quadrant);
|
|
76
|
+
const turn = (seed - 1) % items.length;
|
|
77
|
+
return [...items.slice(turn), ...items.slice(0, turn)].map(([, item, weight]) => ({
|
|
78
|
+
quadrant,
|
|
79
|
+
item,
|
|
80
|
+
weight
|
|
81
|
+
}));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
export { SwotSamples };
|