@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,187 @@
1
+ import { CartesianChart } from "../../core/CartesianChart.js";
2
+ import { ChartFamily, DataShape, Dimension } from "../../core/enums.js";
3
+ import { ZoomAxis } from "../../core/ChartViewport.js";
4
+ import { SampleData } from "../../data/SampleData.js";
5
+ import { VanWestendorpCurves, VanWestendorpPoint, VanWestendorpSurvey, VanWestendorpTrace } from "./VanWestendorpSurvey.js";
6
+ import { extent, line, scaleLinear } from "d3";
7
+ // src/charts/frameworks/VanWestendorpChart.ts
8
+ var LANE = 15;
9
+ var MOST_LANES = 2;
10
+ var CHAR = 6.2;
11
+ var money = (value) => value.toLocaleString("en", { maximumFractionDigits: 2 });
12
+ /** Synthetic survey, invented for the sample: anchored on a person's price sense, heaped on multiples of 5 as stated prices are. */
13
+ var syntheticResponses = (seed) => {
14
+ const draws = SampleData.distribution(seed, 1500, 1).map((row) => (row.value - 40) / 8);
15
+ const stated = (value) => Math.max(0, Math.round(value / 5) * 5);
16
+ return Array.from({ length: 300 }, (_, i) => {
17
+ const z = draws.slice(i * 5, i * 5 + 5), anchor = 40 + 13 * Math.max(-2, Math.min(2, z[0]));
18
+ const tooCheap = stated(anchor * (.5 + .08 * z[1])), cheap = Math.max(tooCheap, stated(anchor * (.78 + .1 * z[2])));
19
+ const expensive = Math.max(cheap, stated(anchor * (1.2 + .12 * z[3])));
20
+ return {
21
+ tooCheap,
22
+ cheap,
23
+ expensive,
24
+ tooExpensive: Math.max(expensive, stated(anchor * (1.6 + .16 * z[4])))
25
+ };
26
+ });
27
+ };
28
+ /**
29
+ * Van Westendorp's Price Sensitivity Meter: reads an acceptable price range and a stress-free price off four
30
+ * cumulative curves of stated price thresholds. PMC and PME bound the range, OPP is Too Cheap × Too Expensive
31
+ * and IPP is Cheap × Expensive. See P. van Westendorp, "NSS Price Sensitivity Meter", ESOMAR Congress, 1976.
32
+ * Shows stated acceptability, not demand, so it does not by itself give a revenue-maximising price.
33
+ */
34
+ var VanWestendorpChart = class extends CartesianChart {
35
+ static meta = {
36
+ id: "van-westendorp",
37
+ name: "Van Westendorp price sensitivity meter",
38
+ family: ChartFamily.Statistical,
39
+ dimension: Dimension.Two,
40
+ shape: DataShape.Table,
41
+ description: "Finds the acceptable price range and indifference point from four cumulative price-perception curves.",
42
+ tags: [
43
+ "pricing",
44
+ "survey",
45
+ "psm",
46
+ "price sensitivity"
47
+ ],
48
+ sample: syntheticResponses,
49
+ variants: {
50
+ curves: [VanWestendorpCurves.Direct, VanWestendorpCurves.Inverted],
51
+ trace: [VanWestendorpTrace.Interpolated, VanWestendorpTrace.Stepped],
52
+ band: [true, false]
53
+ }
54
+ };
55
+ #placed = [];
56
+ #lanes = 0;
57
+ defaults() {
58
+ return {
59
+ tooCheap: "tooCheap",
60
+ cheap: "cheap",
61
+ expensive: "expensive",
62
+ tooExpensive: "tooExpensive",
63
+ curves: VanWestendorpCurves.Direct,
64
+ trace: VanWestendorpTrace.Interpolated,
65
+ band: true,
66
+ validate: true,
67
+ xLabel: "Price",
68
+ yLabel: "Respondents (%)"
69
+ };
70
+ }
71
+ /** The computed curves' source: read `crossings()` from it for the four prices. */
72
+ get survey() {
73
+ const { data, tooCheap, cheap, expensive, tooExpensive, validate } = this.options;
74
+ return new VanWestendorpSurvey(data, {
75
+ tooCheap,
76
+ cheap,
77
+ expensive,
78
+ tooExpensive
79
+ }, validate);
80
+ }
81
+ /** The share axis is fixed at 0 to 100 and shares the plot with the label lanes. */
82
+ zoomAxes() {
83
+ return ZoomAxis.X;
84
+ }
85
+ /** Labels go in lanes above 100%, one more each time two would touch. Crowded ones give up their prices, which tooltips keep. */
86
+ #place(crossings, x, width) {
87
+ const clusters = crossings.reduce((all, crossing) => {
88
+ const last = all.at(-1);
89
+ if (last && x(crossing.price) - x(last[0].price) < 1) last.push(crossing);
90
+ else all.push([crossing]);
91
+ return all;
92
+ }, []);
93
+ const names = (cluster) => cluster.map((crossing) => crossing.point).join(" · ");
94
+ const lay = (label) => {
95
+ const ends = [];
96
+ return {
97
+ placed: clusters.flatMap((cluster) => {
98
+ const text = label(cluster), span = text.length * CHAR;
99
+ const left = Math.max(0, Math.min(width - span, x(cluster[0].price) - span / 2));
100
+ const free = ends.findIndex((end) => left >= end + 6), lane = free < 0 ? ends.length : free;
101
+ ends[lane] = left + span;
102
+ return cluster.map((crossing, i) => ({
103
+ ...crossing,
104
+ label: i ? "" : text,
105
+ lane
106
+ }));
107
+ }),
108
+ lanes: ends.length
109
+ };
110
+ };
111
+ const priced = lay((cluster) => `${names(cluster)} ${money(cluster[0].price)}`);
112
+ const { placed, lanes } = priced.lanes <= MOST_LANES ? priced : lay(names);
113
+ this.#placed = placed;
114
+ this.#lanes = lanes;
115
+ }
116
+ get #top() {
117
+ return this.#lanes ? this.#lanes * LANE + 4 : 0;
118
+ }
119
+ scales(ctx) {
120
+ const { width, height } = ctx.plot;
121
+ const { survey } = this;
122
+ const [low = 0, high = 1] = extent(survey.prices);
123
+ const x = scaleLinear().domain(low === high ? [Math.max(0, low - 1), high + 1] : [low, high]).nice().range([0, width]);
124
+ this.#place(survey.crossings(this.options.curves, this.options.trace), x, width);
125
+ return {
126
+ x,
127
+ y: scaleLinear().domain([0, 100]).range([height, this.#top])
128
+ };
129
+ }
130
+ plotMarks(ctx, { x, y }) {
131
+ const { width, height } = ctx.plot;
132
+ const { theme, palette } = ctx;
133
+ const placed = this.#placed.filter((crossing) => x(crossing.price) >= 0 && x(crossing.price) <= width);
134
+ const { survey } = this;
135
+ const curves = survey.curves(this.options.curves, this.options.trace).filter((curve) => curve.vertices.length);
136
+ const low = this.#placed.find((crossing) => crossing.point === VanWestendorpPoint.PMC), high = this.#placed.find((crossing) => crossing.point === VanWestendorpPoint.PME);
137
+ const range = this.options.band && low && high ? [{
138
+ low: Math.min(low.price, high.price),
139
+ high: Math.max(low.price, high.price)
140
+ }] : [];
141
+ const band = ctx.marks("band", "rect", range).attr("fill", theme.grid).attr("opacity", .55);
142
+ ctx.set(band, {
143
+ x: (d) => x(d.low),
144
+ width: (d) => Math.max(0, x(d.high) - x(d.low)),
145
+ y: this.#top,
146
+ height: height - this.#top
147
+ });
148
+ ctx.hover(band, (d) => ({
149
+ title: "Acceptable price range",
150
+ rows: [["PMC", money(d.low)], ["PME", money(d.high)]]
151
+ }));
152
+ const trace = line().x((vertex) => x(vertex.price)).y((vertex) => y(vertex.share));
153
+ const paths = ctx.marks("curves", "path", curves, (curve) => curve.name).attr("fill", "none").attr("stroke-width", 2).attr("stroke-linejoin", "round").attr("stroke", (curve) => palette.color(curve.name));
154
+ ctx.set(paths, { d: (curve) => trace(curve.vertices) });
155
+ ctx.drawOn(paths);
156
+ ctx.hover(paths, (curve) => ({
157
+ title: curve.name,
158
+ color: palette.color(curve.name),
159
+ rows: [["Respondents", survey.respondents]]
160
+ }));
161
+ const row = (crossing) => 11 + (this.#lanes - 1 - crossing.lane) * LANE;
162
+ const drops = ctx.marks("drops", "line", placed, (crossing) => crossing.point).attr("stroke", theme.axis).attr("stroke-dasharray", "3 3");
163
+ ctx.set(drops, {
164
+ x1: (d) => x(d.price),
165
+ x2: (d) => x(d.price),
166
+ y1: (d) => row(d) + 3,
167
+ y2: height
168
+ });
169
+ const dots = ctx.marks("crossings", "circle", placed, (crossing) => crossing.point).attr("fill", theme.ink).attr("stroke", theme.surface).attr("stroke-width", 2);
170
+ ctx.set(dots, {
171
+ cx: (d) => x(d.price),
172
+ cy: (d) => y(d.share),
173
+ r: 5
174
+ }, { r: 0 }, 60);
175
+ ctx.hover(dots, (d) => ({
176
+ title: d.name,
177
+ rows: [["Price", money(d.price)], ["Respondents", `${money(d.share)}%`]]
178
+ }));
179
+ const labels = ctx.marks("crossing-labels", "text", placed.filter((crossing) => crossing.label), (crossing) => crossing.point).attr("stroke", theme.surface).attr("stroke-width", 3).attr("paint-order", "stroke").attr("text-anchor", "middle").attr("font-weight", 600).text((d) => d.label);
180
+ ctx.text(labels, 11, "ink");
181
+ ctx.set(labels, {
182
+ x: (d) => Math.max(d.label.length * CHAR / 2, Math.min(width - d.label.length * CHAR / 2, x(d.price))),
183
+ y: row
184
+ });
185
+ }
186
+ };
187
+ export { VanWestendorpChart };
@@ -0,0 +1,197 @@
1
+ import { bisectLeft, bisectRight } from "d3";
2
+ // src/charts/frameworks/VanWestendorpSurvey.ts
3
+ /** Which pair of middle curves the chart plots and intersects. Both are in published use. */
4
+ var VanWestendorpCurves = /* @__PURE__ */ function(VanWestendorpCurves) {
5
+ /** Cheap and Expensive as asked: PMC is Too Cheap × Expensive, PME is Too Expensive × Cheap. */
6
+ VanWestendorpCurves["Direct"] = "direct";
7
+ /** Not Cheap (100 − Cheap) and Not Expensive (100 − Expensive): PMC is Too Cheap × Not Cheap, PME is Too Expensive × Not Expensive. */
8
+ VanWestendorpCurves["Inverted"] = "inverted";
9
+ return VanWestendorpCurves;
10
+ }({});
11
+ /** How the survey's discrete prices join up. */
12
+ var VanWestendorpTrace = /* @__PURE__ */ function(VanWestendorpTrace) {
13
+ VanWestendorpTrace["Interpolated"] = "interpolated";
14
+ VanWestendorpTrace["Stepped"] = "stepped";
15
+ return VanWestendorpTrace;
16
+ }({});
17
+ var VanWestendorpPoint = /* @__PURE__ */ function(VanWestendorpPoint) {
18
+ VanWestendorpPoint["PMC"] = "PMC";
19
+ VanWestendorpPoint["OPP"] = "OPP";
20
+ VanWestendorpPoint["IPP"] = "IPP";
21
+ VanWestendorpPoint["PME"] = "PME";
22
+ return VanWestendorpPoint;
23
+ }({});
24
+ var SNAP = 1e-9;
25
+ var NAMES = {
26
+ ["PMC"]: "Point of marginal cheapness",
27
+ ["OPP"]: "Optimal price point",
28
+ ["IPP"]: "Indifference price point",
29
+ ["PME"]: "Point of marginal expensiveness"
30
+ };
31
+ var price = (cell) => {
32
+ const value = typeof cell === "number" ? cell : typeof cell === "string" && cell.trim() ? Number(cell) : NaN;
33
+ return Number.isFinite(value) && value >= 0 ? value : null;
34
+ };
35
+ /**
36
+ * Van Westendorp's Price Sensitivity Meter (1976) from raw survey answers. Each curve is the share of
37
+ * respondents placing a price in that category: Too Cheap and Cheap at or above the price, Expensive and
38
+ * Too Expensive at or below it. Every crossing pairs a rising curve with a falling one, so their gap never
39
+ * falls and a crossing is one price, or a flat stretch whose middle is taken.
40
+ */
41
+ var VanWestendorpSurvey = class VanWestendorpSurvey {
42
+ #answers;
43
+ #prices;
44
+ /** Respondents answering nothing usable or, when validating, out of order. */
45
+ dropped;
46
+ respondents;
47
+ /** With `validate`, keeps only complete answers ordered Too Cheap ≤ Cheap ≤ Expensive ≤ Too Expensive. */
48
+ constructor(rows, columns, validate = true) {
49
+ const all = Array.isArray(rows) ? rows : [];
50
+ const kept = all.map((row) => {
51
+ const read = (column) => row !== null && typeof row === "object" ? price(row[column]) : null;
52
+ return [
53
+ read(columns.tooCheap),
54
+ read(columns.cheap),
55
+ read(columns.expensive),
56
+ read(columns.tooExpensive)
57
+ ];
58
+ }).filter((row) => validate ? row.every((value, i) => value !== null && (!i || row[i - 1] <= value)) : row.some((value) => value !== null));
59
+ const column = (i) => kept.map((row) => row[i]).filter((value) => value !== null).sort((a, b) => a - b);
60
+ this.#answers = {
61
+ tooCheap: column(0),
62
+ cheap: column(1),
63
+ expensive: column(2),
64
+ tooExpensive: column(3)
65
+ };
66
+ this.#prices = [...new Set(Object.values(this.#answers).flat())].sort((a, b) => a - b);
67
+ this.respondents = kept.length;
68
+ this.dropped = all.length - kept.length;
69
+ }
70
+ get prices() {
71
+ return this.#prices;
72
+ }
73
+ #falling(answers, trace) {
74
+ const share = (count) => count / answers.length * 100;
75
+ return !answers.length ? [] : this.#prices.flatMap((at) => {
76
+ const reached = {
77
+ price: at,
78
+ share: share(answers.length - bisectLeft(answers, at))
79
+ };
80
+ return trace === "stepped" ? [reached, {
81
+ price: at,
82
+ share: share(answers.length - bisectRight(answers, at))
83
+ }] : [reached];
84
+ });
85
+ }
86
+ #rising(answers, trace) {
87
+ const share = (count) => count / answers.length * 100;
88
+ return !answers.length ? [] : this.#prices.flatMap((at) => {
89
+ const reached = {
90
+ price: at,
91
+ share: share(bisectRight(answers, at))
92
+ };
93
+ return trace === "stepped" ? [{
94
+ price: at,
95
+ share: share(bisectLeft(answers, at))
96
+ }, reached] : [reached];
97
+ });
98
+ }
99
+ #slots(form, trace) {
100
+ const flip = (vertices) => vertices.map((vertex) => ({
101
+ price: vertex.price,
102
+ share: 100 - vertex.share
103
+ }));
104
+ const { tooCheap, cheap, expensive, tooExpensive } = this.#answers;
105
+ const cheapShare = this.#falling(cheap, trace), expensiveShare = this.#rising(expensive, trace);
106
+ const inverted = form === "inverted";
107
+ return {
108
+ tooCheap: this.#falling(tooCheap, trace),
109
+ tooExpensive: this.#rising(tooExpensive, trace),
110
+ up: inverted ? flip(cheapShare) : expensiveShare,
111
+ down: inverted ? flip(expensiveShare) : cheapShare
112
+ };
113
+ }
114
+ /** The four curves, cheapest first, over the same prices. */
115
+ curves(form = "direct", trace = "interpolated") {
116
+ const { tooCheap, tooExpensive, up, down } = this.#slots(form, trace);
117
+ return [
118
+ {
119
+ name: "Too cheap",
120
+ vertices: tooCheap
121
+ },
122
+ ...form === "inverted" ? [{
123
+ name: "Not cheap",
124
+ vertices: up
125
+ }, {
126
+ name: "Not expensive",
127
+ vertices: down
128
+ }] : [{
129
+ name: "Cheap",
130
+ vertices: down
131
+ }, {
132
+ name: "Expensive",
133
+ vertices: up
134
+ }],
135
+ {
136
+ name: "Too expensive",
137
+ vertices: tooExpensive
138
+ }
139
+ ];
140
+ }
141
+ /** The four published intersections, ordered by price. A curve with no answers has no crossing. */
142
+ crossings(form = "direct", trace = "interpolated") {
143
+ const { tooCheap, tooExpensive, up, down } = this.#slots(form, trace);
144
+ return [
145
+ [
146
+ "PMC",
147
+ up,
148
+ tooCheap
149
+ ],
150
+ [
151
+ "OPP",
152
+ tooExpensive,
153
+ tooCheap
154
+ ],
155
+ [
156
+ "IPP",
157
+ up,
158
+ down
159
+ ],
160
+ [
161
+ "PME",
162
+ tooExpensive,
163
+ down
164
+ ]
165
+ ].flatMap(([point, rising, falling]) => {
166
+ const hit = VanWestendorpSurvey.#cross(rising, falling);
167
+ return hit ? [{
168
+ point,
169
+ name: NAMES[point],
170
+ ...hit
171
+ }] : [];
172
+ }).sort((a, b) => a.price - b.price);
173
+ }
174
+ /** Both lists share their prices, vertex by vertex. */
175
+ static #cross(rising, falling) {
176
+ if (!rising.length || !falling.length) return null;
177
+ const gaps = rising.map((vertex, i) => {
178
+ const gap = vertex.share - falling[i].share;
179
+ return Math.abs(gap) < SNAP ? 0 : gap;
180
+ });
181
+ const first = gaps.findIndex((gap) => gap >= 0);
182
+ if (first < 0) return null;
183
+ if (!first || !gaps[first]) {
184
+ const last = Math.max(first, gaps.findLastIndex((gap) => gap <= 0));
185
+ return {
186
+ price: (rising[first].price + rising[last].price) / 2,
187
+ share: rising[first].share
188
+ };
189
+ }
190
+ const before = rising[first - 1], after = rising[first], t = -gaps[first - 1] / (gaps[first] - gaps[first - 1]);
191
+ return {
192
+ price: before.price + (after.price - before.price) * t,
193
+ share: before.share + (after.share - before.share) * t
194
+ };
195
+ }
196
+ };
197
+ export { VanWestendorpCurves, VanWestendorpPoint, VanWestendorpSurvey, VanWestendorpTrace };
@@ -0,0 +1,26 @@
1
+ import { FrameworkText } from "./FrameworkText.js";
2
+ import { RaciHeader, RaciIssue, RaciLetter, RaciMark } from "./raciEnums.js";
3
+ import { RaciPlan } from "./RaciPlan.js";
4
+ import { RaciSamples } from "./RaciSamples.js";
5
+ import { RaciMatrix } from "./RaciMatrix.js";
6
+ import { SwotItemMark, SwotLayout, SwotOrder, SwotQuadrant, SwotSizing } from "./swotEnums.js";
7
+ import { SwotSamples } from "./SwotSamples.js";
8
+ import { SwotMatrix } from "./SwotMatrix.js";
9
+ import { NpsBand, NpsSurvey } from "./NpsSurvey.js";
10
+ import { NpsGauge, NpsIndicator, NpsSweep } from "./NpsGauge.js";
11
+ import { VanWestendorpCurves, VanWestendorpPoint, VanWestendorpSurvey, VanWestendorpTrace } from "./VanWestendorpSurvey.js";
12
+ import { VanWestendorpChart } from "./VanWestendorpChart.js";
13
+ import { PositioningSamples } from "./PositioningSamples.js";
14
+ import { AnsoffDisplay, AnsoffMarketOrder, AnsoffMatrix, AnsoffStage, AnsoffStrategy } from "./AnsoffMatrix.js";
15
+ import { PerceptualMethod, PerceptualProjection, PerceptualRole } from "./PerceptualProjection.js";
16
+ import { PerceptualMap } from "./PerceptualMap.js";
17
+ // src/charts/frameworks/index.ts
18
+ var frameworksCatalog = [
19
+ SwotMatrix,
20
+ RaciMatrix,
21
+ NpsGauge,
22
+ VanWestendorpChart,
23
+ AnsoffMatrix,
24
+ PerceptualMap
25
+ ];
26
+ export { AnsoffDisplay, AnsoffMarketOrder, AnsoffMatrix, AnsoffStage, AnsoffStrategy, FrameworkText, NpsBand, NpsGauge, NpsIndicator, NpsSurvey, NpsSweep, PerceptualMap, PerceptualMethod, PerceptualProjection, PerceptualRole, PositioningSamples, RaciHeader, RaciIssue, RaciLetter, RaciMark, RaciMatrix, RaciPlan, RaciSamples, SwotItemMark, SwotLayout, SwotMatrix, SwotOrder, SwotQuadrant, SwotSamples, SwotSizing, VanWestendorpChart, VanWestendorpCurves, VanWestendorpPoint, VanWestendorpSurvey, VanWestendorpTrace, frameworksCatalog };
@@ -0,0 +1,30 @@
1
+ // src/charts/frameworks/raciEnums.ts
2
+ /** RACI responsibility assignment: who does the work, who answers for it, who is asked, who is told. */
3
+ var RaciLetter = /* @__PURE__ */ function(RaciLetter) {
4
+ RaciLetter["Responsible"] = "R";
5
+ RaciLetter["Accountable"] = "A";
6
+ RaciLetter["Consulted"] = "C";
7
+ RaciLetter["Informed"] = "I";
8
+ return RaciLetter;
9
+ }({});
10
+ /** Faults in a task's assignments, from the rule of exactly one A and at least one R. */
11
+ var RaciIssue = /* @__PURE__ */ function(RaciIssue) {
12
+ RaciIssue["NoAccountable"] = "no-accountable";
13
+ RaciIssue["SeveralAccountable"] = "several-accountable";
14
+ RaciIssue["NoResponsible"] = "no-responsible";
15
+ return RaciIssue;
16
+ }({});
17
+ /** How role names sit above their columns. */
18
+ var RaciHeader = /* @__PURE__ */ function(RaciHeader) {
19
+ RaciHeader["Wrapped"] = "wrapped";
20
+ RaciHeader["Angled"] = "angled";
21
+ RaciHeader["Vertical"] = "vertical";
22
+ return RaciHeader;
23
+ }({});
24
+ /** How an assignment is drawn. The letter is always text. */
25
+ var RaciMark = /* @__PURE__ */ function(RaciMark) {
26
+ RaciMark["Badge"] = "badge";
27
+ RaciMark["Tile"] = "tile";
28
+ return RaciMark;
29
+ }({});
30
+ export { RaciHeader, RaciIssue, RaciLetter, RaciMark };
@@ -0,0 +1,34 @@
1
+ // src/charts/frameworks/swotEnums.ts
2
+ /** The four SWOT headings, in reading order. */
3
+ var SwotQuadrant = /* @__PURE__ */ function(SwotQuadrant) {
4
+ SwotQuadrant["Strengths"] = "Strengths";
5
+ SwotQuadrant["Weaknesses"] = "Weaknesses";
6
+ SwotQuadrant["Opportunities"] = "Opportunities";
7
+ SwotQuadrant["Threats"] = "Threats";
8
+ return SwotQuadrant;
9
+ }({});
10
+ /** How the quadrants are arranged. */
11
+ var SwotLayout = /* @__PURE__ */ function(SwotLayout) {
12
+ SwotLayout["Grid"] = "grid";
13
+ SwotLayout["Columns"] = "columns";
14
+ SwotLayout["Rows"] = "rows";
15
+ return SwotLayout;
16
+ }({});
17
+ /** Whether stacked quadrants share height equally or by how much text each holds. */
18
+ var SwotSizing = /* @__PURE__ */ function(SwotSizing) {
19
+ SwotSizing["Equal"] = "equal";
20
+ SwotSizing["Content"] = "content";
21
+ return SwotSizing;
22
+ }({});
23
+ /** What leads each item: a bullet, or a reference code such as S1 for citing it in a TOWS analysis. */
24
+ var SwotItemMark = /* @__PURE__ */ function(SwotItemMark) {
25
+ SwotItemMark["Bullet"] = "bullet";
26
+ SwotItemMark["Code"] = "code";
27
+ return SwotItemMark;
28
+ }({});
29
+ var SwotOrder = /* @__PURE__ */ function(SwotOrder) {
30
+ SwotOrder["Input"] = "input";
31
+ SwotOrder["Weight"] = "weight";
32
+ return SwotOrder;
33
+ }({});
34
+ export { SwotItemMark, SwotLayout, SwotOrder, SwotQuadrant, SwotSizing };
@@ -143,7 +143,7 @@ var CircosPlot = class extends RadialChart {
143
143
  });
144
144
  });
145
145
  const reach = (d) => outer + (d.major ? 5 : 3);
146
- const lines = ctx.marks("ticks", "line", ticks, (d) => d.key).attr("stroke", ctx.theme.inkMuted).attr("stroke-width", (d) => d.major ? 1 : .6);
146
+ const lines = ctx.marks("axis-ticks", "line", ticks, (d) => d.key).attr("stroke", ctx.theme.inkMuted).attr("stroke-width", (d) => d.major ? 1 : .6);
147
147
  const xy = (a, r) => point(a, r).split(" ").map(Number);
148
148
  ctx.set(lines, {
149
149
  x1: (d) => xy(d.a, outer)[0],
@@ -1,7 +1,7 @@
1
1
  import { Chart2D } from "../../core/Chart2D.js";
2
2
  import { LegendPosition } from "../../core/enums.js";
3
3
  import { Scales } from "../../core/Scales.js";
4
- import { SampleData } from "../../data/SampleData.js";
4
+ import { WorldSample } from "../../data/WorldSample.js";
5
5
  import { GeoViewport } from "../geomaps/GeoViewport.js";
6
6
  import { GeoFlowFrame, GeoFlowProjection, isAzimuthal } from "./GeoFlowFrame.js";
7
7
  import { geoGraticule10, max } from "d3";
@@ -129,7 +129,7 @@ var GeoFlowChart = class extends Chart2D {
129
129
  this.plotGeo(ctx, frame);
130
130
  }
131
131
  get land() {
132
- return this.options.basemap ?? (countries ??= SampleData.world(1));
132
+ return this.options.basemap ?? (countries ??= WorldSample.countries(1));
133
133
  }
134
134
  drawBasemap(ctx, frame, graticules = true) {
135
135
  const { theme } = ctx;
@@ -1,4 +1,5 @@
1
1
  import { SampleData } from "../../data/SampleData.js";
2
+ import { WorldSample } from "../../data/WorldSample.js";
2
3
  import { geoCentroid, geoDistance } from "d3";
3
4
  // src/charts/geoflows/GeoFlowSample.ts
4
5
  /** Broad region of a place, from its coordinates; demo data only. */
@@ -237,7 +238,7 @@ var GeoFlowSample = class {
237
238
  }
238
239
  /** `{ name, lon, lat, value, series }`: one point per country, at its centroid. */
239
240
  static places(seed) {
240
- return SampleData.world(seed).features.map((country) => {
241
+ return WorldSample.countries(seed).features.map((country) => {
241
242
  const [lon, lat] = geoCentroid(country);
242
243
  return {
243
244
  name: String(country.properties?.name ?? ""),