@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,324 @@
1
+ import { ChartFamily, DataShape, Dimension, LegendPosition, SymbolKind } from "../../core/enums.js";
2
+ import { Scales } from "../../core/Scales.js";
3
+ import { ZoomControls } from "../../core/ZoomControls.js";
4
+ import { GeoChart } from "./GeoChart.js";
5
+ import { GeoClasses, GeoClassification } from "./GeoClasses.js";
6
+ import { GeoLegend } from "./GeoLegend.js";
7
+ import { FloorDoorSwing, FloorPlanModel } from "./FloorPlanModel.js";
8
+ import { FloorPlanSamples } from "./FloorPlanSamples.js";
9
+ import { RoomLabelFit, RoomLabelMode } from "./RoomLabelFit.js";
10
+ import { geoIdentity, symbol } from "d3";
11
+ // src/charts/geomaps/FloorPlanMap.ts
12
+ /** Line draws walls as plan linework; Gap leaves them as a surface-coloured gap between rooms. */
13
+ var FloorWallStyle = /* @__PURE__ */ function(FloorWallStyle) {
14
+ FloorWallStyle["Line"] = "line";
15
+ FloorWallStyle["Gap"] = "gap";
16
+ return FloorWallStyle;
17
+ }({});
18
+ /** Swing draws the plan convention of leaf and quarter arc; Opening marks only the gap in the wall. */
19
+ var FloorDoorStyle = /* @__PURE__ */ function(FloorDoorStyle) {
20
+ FloorDoorStyle["Swing"] = "swing";
21
+ FloorDoorStyle["Opening"] = "opening";
22
+ FloorDoorStyle["None"] = "none";
23
+ return FloorDoorStyle;
24
+ }({});
25
+ /** Where the plan's y axis starts: BottomLeft for CAD and GeoJSON, TopLeft for pixel and SVG coordinates. */
26
+ var FloorPlanOrigin = /* @__PURE__ */ function(FloorPlanOrigin) {
27
+ FloorPlanOrigin["BottomLeft"] = "bottom-left";
28
+ FloorPlanOrigin["TopLeft"] = "top-left";
29
+ return FloorPlanOrigin;
30
+ }({});
31
+ var EDGE = 8;
32
+ var RAIL = 32;
33
+ var BUTTON = 28;
34
+ var WALL = 1.5;
35
+ var MARKER_AREA = 80;
36
+ var MARKER_BOX = 12;
37
+ var SHORT_DOOR = 4;
38
+ var SYMBOLS = [
39
+ SymbolKind.Circle,
40
+ SymbolKind.Square,
41
+ SymbolKind.Diamond,
42
+ SymbolKind.Triangle,
43
+ SymbolKind.Star,
44
+ SymbolKind.Wye,
45
+ SymbolKind.Cross
46
+ ];
47
+ /**
48
+ * Indoor map: rooms in plain x, y units shaded by a value, for occupancy, sensor or space-planning data
49
+ * where the building is the geography. Plans go through an identity projection, so proportions are kept
50
+ * and nothing is treated as a longitude. Doors follow the architectural convention of a leaf and quarter
51
+ * arc. Every floor shares one frame and colour scale, so floors line up and compare. Zooming redraws
52
+ * from the plan, which keeps text and symbols their size and lets small rooms take their labels.
53
+ */
54
+ var FloorPlanMap = class extends GeoChart {
55
+ static meta = {
56
+ id: "floor-plan-map",
57
+ name: "Floor plan map",
58
+ family: ChartFamily.Geospatial,
59
+ dimension: Dimension.Two,
60
+ shape: DataShape.Geo,
61
+ description: "Shades the rooms of a building floor by floor in plain plan units, with fitted labels, doors and asset markers.",
62
+ tags: [
63
+ "map",
64
+ "indoor",
65
+ "floor plan",
66
+ "building",
67
+ "rooms",
68
+ "occupancy",
69
+ "facilities",
70
+ "level",
71
+ "assets"
72
+ ],
73
+ sample: (seed) => FloorPlanSamples.building(seed),
74
+ variants: {
75
+ walls: ["line", "gap"],
76
+ doors: [
77
+ "swing",
78
+ "opening",
79
+ "none"
80
+ ],
81
+ labels: [
82
+ RoomLabelMode.Name,
83
+ RoomLabelMode.NameValue,
84
+ RoomLabelMode.None
85
+ ],
86
+ markers: [true, false],
87
+ classification: [
88
+ GeoClassification.Continuous,
89
+ GeoClassification.Quantile,
90
+ GeoClassification.EqualInterval,
91
+ GeoClassification.NaturalBreaks
92
+ ]
93
+ }
94
+ };
95
+ #plan = null;
96
+ #moved = false;
97
+ defaults() {
98
+ return {
99
+ ...super.defaults(),
100
+ graticule: false,
101
+ legend: LegendPosition.Top,
102
+ value: "value",
103
+ name: "name",
104
+ levelColumn: "level",
105
+ points: "points",
106
+ x: "x",
107
+ y: "y",
108
+ type: "type",
109
+ join: "name",
110
+ levelPicker: true,
111
+ origin: "bottom-left",
112
+ walls: "line",
113
+ doors: "swing",
114
+ labels: RoomLabelMode.Name,
115
+ markers: true,
116
+ classification: GeoClassification.Continuous,
117
+ bins: 5
118
+ };
119
+ }
120
+ get framed() {
121
+ return false;
122
+ }
123
+ get semanticZoom() {
124
+ return true;
125
+ }
126
+ get animated() {
127
+ return !this.#moved && super.animated;
128
+ }
129
+ get #model() {
130
+ return this.#plan ??= new FloorPlanModel(this.options.data, this.options);
131
+ }
132
+ /** The floors in the plan, lowest first. */
133
+ get levels() {
134
+ return this.#model.levels;
135
+ }
136
+ update(patch) {
137
+ this.#plan = null;
138
+ this.#moved = false;
139
+ return super.update(patch);
140
+ }
141
+ viewMoved(transform, settled) {
142
+ this.#moved = true;
143
+ this.render();
144
+ super.viewMoved(transform, settled);
145
+ }
146
+ target() {
147
+ const features = this.#model.rooms.map((room) => room.feature);
148
+ return features.length ? {
149
+ type: "FeatureCollection",
150
+ features
151
+ } : null;
152
+ }
153
+ get #picking() {
154
+ return !!this.options.levelPicker && this.#model.levels.length > 1;
155
+ }
156
+ /** Fits the whole building at one uniform scale, so proportions hold and floors stay in register. */
157
+ frame(ctx) {
158
+ const width = ctx.plot.width, height = Math.max(40, ctx.plot.height - this.keyHeight());
159
+ const left = EDGE + (this.#picking ? RAIL : 0), right = EDGE + (this.options.zoom ? RAIL : 0);
160
+ const { x0, y0, x1, y1 } = this.#model.bounds ?? {
161
+ x0: 0,
162
+ y0: 0,
163
+ x1: 1,
164
+ y1: 1
165
+ };
166
+ const up = this.options.origin === "bottom-left";
167
+ const room = [Math.max(1, width - left - right), Math.max(1, height - 16)];
168
+ const scale = Math.min(x1 > x0 ? room[0] / (x1 - x0) : Infinity, y1 > y0 ? room[1] / (y1 - y0) : Infinity);
169
+ const k = Number.isFinite(scale) ? scale : 1;
170
+ const tx = left + (room[0] - k * (x1 - x0)) / 2 - k * x0;
171
+ const ty = EDGE + (room[1] - k * (y1 - y0)) / 2 + (up ? k * y1 : -k * y0);
172
+ const view = this.viewport.transform;
173
+ const projection = geoIdentity().reflectY(up).scale(k * view.k).translate([view.x + view.k * tx, view.y + view.k * ty]);
174
+ return this.frameOf(projection, width, height);
175
+ }
176
+ #classes() {
177
+ const values = this.#model.rooms.flatMap((room) => room.value ?? []);
178
+ return new GeoClasses(values, this.options.classification, this.options.bins);
179
+ }
180
+ #tone(ctx, classes, value) {
181
+ const { midpoint } = this.options;
182
+ if (midpoint === void 0) return ctx.palette.sequential(classes.tone(value));
183
+ const reach = Math.max(classes.max - midpoint, midpoint - classes.min) || 1;
184
+ return ctx.palette.diverging((value - midpoint) / reach);
185
+ }
186
+ /** Hinge, latch and swing in pixels. Left is the viewer's left, whichever way the plan's y axis runs. */
187
+ #door(door, frame) {
188
+ const hinge = frame.locate(...door.from), latch = frame.locate(...door.to);
189
+ if (!hinge || !latch) return null;
190
+ const dx = latch[0] - hinge[0], dy = latch[1] - hinge[1], radius = Math.hypot(dx, dy);
191
+ const side = door.swing === FloorDoorSwing.Left ? 1 : -1;
192
+ const tip = [hinge[0] + side * dy, hinge[1] - side * dx];
193
+ const swings = door.swing !== FloorDoorSwing.None && this.options.doors === "swing" && radius >= SHORT_DOOR;
194
+ const corners = swings ? [
195
+ hinge,
196
+ latch,
197
+ tip,
198
+ [tip[0] + dx, tip[1] + dy]
199
+ ] : [hinge, latch];
200
+ const xs = corners.map(([x]) => x), ys = corners.map(([, y]) => y), x = Math.min(...xs), y = Math.min(...ys);
201
+ return {
202
+ ...door,
203
+ hinge,
204
+ latch,
205
+ swingPath: swings ? `M${hinge}L${tip}A${radius},${radius} 0 0 ${side > 0 ? 1 : 0} ${latch}` : null,
206
+ box: {
207
+ x,
208
+ y,
209
+ w: Math.max(...xs) - x,
210
+ h: Math.max(...ys) - y
211
+ }
212
+ };
213
+ }
214
+ #drawDoors(ctx, doors) {
215
+ const { theme } = ctx, { walls } = this.options;
216
+ const lined = walls === "line";
217
+ ctx.marks("door-openings", "line", doors, (d) => d.key).attr("x1", (d) => d.hinge[0]).attr("y1", (d) => d.hinge[1]).attr("x2", (d) => d.latch[0]).attr("y2", (d) => d.latch[1]).attr("stroke", lined ? theme.surface : theme.inkSecondary).attr("stroke-width", lined ? 3 : WALL);
218
+ const swung = doors.filter((door) => door.swingPath);
219
+ ctx.marks("door-halos", "path", swung, (d) => d.key).attr("d", (d) => d.swingPath).attr("fill", "none").attr("stroke", theme.surface).attr("stroke-width", 3).attr("stroke-opacity", .7).attr("pointer-events", "none");
220
+ const swings = ctx.marks("door-swings", "path", swung, (d) => d.key).attr("d", (d) => d.swingPath).attr("fill", "none").attr("stroke", theme.inkSecondary).attr("stroke-width", 1);
221
+ ctx.hover(swings, (d) => ({
222
+ title: d.name,
223
+ rows: [["Swing", d.swing]]
224
+ }));
225
+ }
226
+ #drawLabels(ctx, lines) {
227
+ const text = ctx.marks("room-labels", "text", lines, (d) => d.key).attr("x", (d) => d.rotated ? 0 : d.x).attr("y", (d) => d.rotated ? 0 : d.y).attr("transform", (d) => d.rotated ? `translate(${d.x},${d.y}) rotate(-90)` : null).attr("text-anchor", "middle").attr("pointer-events", "none").text((d) => d.text);
228
+ ctx.text(text, 11).attr("font-weight", (d) => d.strong ? 500 : 400).attr("fill", (d) => d.ink);
229
+ }
230
+ drawMap(ctx, frame) {
231
+ const { theme } = ctx, plan = this.#model, { walls, doors, labels, markers, value: column } = this.options;
232
+ const level = plan.selected(this.options.level), lined = walls === "line";
233
+ const rooms = plan.rooms.filter((room) => FloorPlanModel.on(room, level));
234
+ const classes = this.#classes();
235
+ const fill = (room) => room.value === null ? theme.grid : this.#tone(ctx, classes, classes.classed ? classes.center(classes.bin(room.value)) : room.value);
236
+ const shapes = ctx.marks("rooms", "path", rooms, (d) => d.key).attr("fill", fill).attr("fill-rule", "evenodd").attr("stroke", lined ? "none" : theme.surface).attr("stroke-width", lined ? 0 : 2);
237
+ this.fill(ctx, shapes, (d) => frame.path(d.feature) ?? "");
238
+ ctx.hover(shapes, (d) => ({
239
+ title: d.name,
240
+ rows: [[column, d.value ?? "No data"], ...d.level === null ? [] : [["Level", d.level]]]
241
+ }));
242
+ ctx.marks("walls", "path", lined ? rooms : [], (d) => d.key).attr("d", (d) => frame.path(d.feature) ?? "").attr("fill", "none").attr("stroke", theme.inkSecondary).attr("stroke-width", WALL).attr("pointer-events", "none");
243
+ const drawn = doors === "none" ? [] : plan.doors.filter((door) => FloorPlanModel.on(door, level)).flatMap((door) => this.#door(door, frame) ?? []);
244
+ this.#drawDoors(ctx, drawn);
245
+ const kinds = markers ? [...new Set(plan.assets.map((asset) => asset.type))] : [];
246
+ for (const kind of kinds) ctx.palette.color(kind);
247
+ const assets = markers ? plan.assets.filter((asset) => FloorPlanModel.on(asset, level)).flatMap((asset) => {
248
+ const at = frame.locate(asset.x, asset.y);
249
+ return at ? [{
250
+ ...asset,
251
+ at
252
+ }] : [];
253
+ }) : [];
254
+ const shape = (kind) => symbol(Scales.symbol(SYMBOLS[kinds.indexOf(kind) % SYMBOLS.length]), MARKER_AREA)() ?? "";
255
+ const dots = ctx.marks("assets", "path", assets, (d) => d.key).attr("d", (d) => shape(d.type)).attr("transform", (d) => `translate(${d.at})`).attr("fill", (d) => ctx.palette.color(d.type)).attr("stroke", theme.surface).attr("stroke-width", 2);
256
+ ctx.hover(dots, (d) => ({
257
+ title: d.name,
258
+ color: ctx.palette.color(d.type),
259
+ rows: [["Type", d.type], ...d.level === null ? [] : [["Level", d.level]]]
260
+ }));
261
+ const blocked = [...drawn.map((door) => door.box), ...assets.map(({ at: [x, y] }) => ({
262
+ x: x - MARKER_BOX / 2,
263
+ y: y - MARKER_BOX / 2,
264
+ w: MARKER_BOX,
265
+ h: MARKER_BOX
266
+ }))];
267
+ const reach = Math.max(Math.abs(classes.min), Math.abs(classes.max));
268
+ const place = ([x, y]) => frame.locate(x, y) ?? [NaN, NaN];
269
+ const lines = labels === RoomLabelMode.None ? [] : rooms.flatMap((room) => RoomLabelFit.place({
270
+ room,
271
+ place,
272
+ blocked,
273
+ width: frame.width,
274
+ height: frame.height,
275
+ detail: labels === RoomLabelMode.NameValue && room.value !== null ? Scales.format(room.value, reach) : null
276
+ }).map((line) => ({
277
+ ...line,
278
+ ink: this.inkOn(ctx, fill(room))
279
+ })));
280
+ this.#drawLabels(ctx, lines);
281
+ }
282
+ keyHeight() {
283
+ return this.#model.rooms.some((room) => room.value !== null) ? GeoLegend.height : 0;
284
+ }
285
+ drawKey(ctx, frame) {
286
+ const shaded = this.keyHeight() > 0, classes = this.#classes();
287
+ if (shaded) GeoLegend.classes(ctx, frame, classes, (value) => this.#tone(ctx, classes, value));
288
+ else for (const name of ["key-swatches", "key-ticks"]) ctx.group(name).selectAll("*").remove();
289
+ this.#drawPicker(ctx, frame);
290
+ }
291
+ /** An elevator panel: the top floor at the top, sliding around the current floor when they outnumber the room. */
292
+ #drawPicker(ctx, frame) {
293
+ const { levels } = this.#model, current = this.#model.selected(this.options.level);
294
+ const fit = Math.max(1, Math.floor(frame.height / BUTTON)), at = Math.max(0, levels.indexOf(current ?? ""));
295
+ const start = Math.max(0, Math.min(levels.length - fit, at - Math.floor(fit / 2)));
296
+ const buttons = (this.#picking ? levels.slice(start, start + fit).reverse() : []).map((level) => ({
297
+ key: level,
298
+ glyph: level.slice(0, 3),
299
+ label: `Level ${level}`,
300
+ run: () => void this.update({ level })
301
+ }));
302
+ const panel = ctx.nest("translate(0,0)", "level-picker");
303
+ ZoomControls.draw(panel, 30, buttons);
304
+ const { theme } = ctx;
305
+ const items = panel.group("zoom-controls").selectAll("g").attr("aria-pressed", (d) => String(d.key === current));
306
+ items.select("rect").attr("fill", (d) => d.key === current ? theme.ink : theme.surface);
307
+ items.select("text").attr("font-size", 11).attr("fill", (d) => d.key === current ? theme.surface : theme.ink).attr("y", 17);
308
+ }
309
+ table() {
310
+ return {
311
+ columns: [
312
+ "level",
313
+ "name",
314
+ "value"
315
+ ],
316
+ rows: this.#model.rooms.map((room) => [
317
+ room.level,
318
+ room.name,
319
+ room.value
320
+ ])
321
+ };
322
+ }
323
+ };
324
+ export { FloorDoorStyle, FloorPlanMap, FloorPlanOrigin, FloorWallStyle };
@@ -0,0 +1,179 @@
1
+ import { index } from "d3";
2
+ // src/charts/geomaps/FloorPlanModel.ts
3
+ /** Which way a door leaf opens, looking along the plan from hinge to latch. `None` is an open archway. */
4
+ var FloorDoorSwing = /* @__PURE__ */ function(FloorDoorSwing) {
5
+ FloorDoorSwing["Left"] = "left";
6
+ FloorDoorSwing["Right"] = "right";
7
+ FloorDoorSwing["None"] = "none";
8
+ return FloorDoorSwing;
9
+ }({});
10
+ var number = (value) => {
11
+ if (value === null || value === void 0 || value === "") return null;
12
+ const parsed = +value;
13
+ return Number.isFinite(parsed) ? parsed : null;
14
+ };
15
+ var point = (value) => {
16
+ if (!Array.isArray(value)) return null;
17
+ const x = number(value[0]), y = number(value[1]);
18
+ return x === null || y === null ? null : [x, y];
19
+ };
20
+ var levelOf = (value) => value === null || value === void 0 || value === "" || typeof value === "number" && !Number.isFinite(value) ? null : String(value);
21
+ /** Closes a ring, because a polygon stream drops the last point as a repeat of the first. Rings under three corners are dropped. */
22
+ var ring = (points) => {
23
+ if (!Array.isArray(points)) return null;
24
+ const pairs = points.map(point).filter((corner) => !!corner);
25
+ if (!pairs.length) return null;
26
+ const first = pairs[0], last = pairs.at(-1);
27
+ const closed = first[0] === last[0] && first[1] === last[1] ? pairs : [...pairs, first];
28
+ return closed.length < 4 ? null : closed;
29
+ };
30
+ /** How many arrays wrap a coordinate: 2 for a ring, 3 for rings, 4 for several polygons. */
31
+ var depth = (value) => Array.isArray(value) ? 1 + depth(value[0]) : 0;
32
+ /** A polygon whose outer ring is bad is dropped whole, since its holes mean nothing alone. */
33
+ var polygon = (value) => {
34
+ const read = value.map(ring);
35
+ return read[0] ? read.filter((item) => !!item) : [];
36
+ };
37
+ /** Reads a ring, a ring with holes, or several polygons, the last two as GeoJSON nests them. */
38
+ var polygons = (value) => {
39
+ const nesting = depth(value);
40
+ return (nesting === 2 ? [[value]] : nesting === 3 ? [value] : nesting === 4 ? value : []).map(polygon).filter((part) => part.length);
41
+ };
42
+ var isCollection = (data) => data.type === "FeatureCollection";
43
+ var swingOf = (value) => Object.values(FloorDoorSwing).includes(value) ? value : "none";
44
+ /** One plan read from either input form, with bad geometry dropped and values joined. */
45
+ var FloorPlanModel = class FloorPlanModel {
46
+ rooms = [];
47
+ doors = [];
48
+ assets = [];
49
+ /** Lowest floor first: numbers ascending, names in first-seen order. */
50
+ levels;
51
+ bounds;
52
+ constructor(data, fields) {
53
+ const plan = !data ? { rooms: [] } : isCollection(data) ? FloorPlanModel.#fromFeatures(data, fields) : data;
54
+ const seen = /* @__PURE__ */ new Map();
55
+ const unique = (id) => {
56
+ const count = seen.get(id) ?? 0;
57
+ seen.set(id, count + 1);
58
+ return count ? `${id}#${count}` : id;
59
+ };
60
+ const table = fields.rows && index(fields.rows, (row) => String(row[fields.join]));
61
+ for (const [i, source] of (plan.rooms ?? []).entries()) {
62
+ const parts = polygons(source[fields.points]);
63
+ if (!parts.length) continue;
64
+ const row = {
65
+ ...source,
66
+ ...table?.get(String(source[fields.join]))
67
+ };
68
+ const level = levelOf(row[fields.levelColumn]);
69
+ const name = String(row[fields.name] ?? row.id ?? `Room ${i + 1}`);
70
+ this.rooms.push({
71
+ key: unique(`${level ?? ""}/${String(row.id ?? name)}`),
72
+ level,
73
+ name,
74
+ value: number(row[fields.value]),
75
+ row,
76
+ parts,
77
+ poles: /* @__PURE__ */ new Map(),
78
+ feature: {
79
+ type: "Feature",
80
+ properties: {},
81
+ geometry: {
82
+ type: "MultiPolygon",
83
+ coordinates: parts
84
+ }
85
+ }
86
+ });
87
+ }
88
+ for (const door of plan.doors ?? []) {
89
+ const from = point(door.from), to = point(door.to);
90
+ if (!from || !to || from[0] === to[0] && from[1] === to[1]) continue;
91
+ const level = levelOf(door[fields.levelColumn]);
92
+ this.doors.push({
93
+ key: unique(`door/${level ?? ""}/${from}/${to}`),
94
+ level,
95
+ name: door.name ?? "Door",
96
+ from,
97
+ to,
98
+ swing: swingOf(door.swing)
99
+ });
100
+ }
101
+ for (const asset of plan.assets ?? []) {
102
+ const x = number(asset[fields.x]), y = number(asset[fields.y]);
103
+ if (x === null || y === null) continue;
104
+ const level = levelOf(asset[fields.levelColumn]), type = String(asset[fields.type] ?? "Asset");
105
+ this.assets.push({
106
+ key: unique(`asset/${level ?? ""}/${String(asset.id ?? `${type}/${x}/${y}`)}`),
107
+ level,
108
+ name: String(asset[fields.name] ?? type),
109
+ type,
110
+ x,
111
+ y
112
+ });
113
+ }
114
+ this.levels = this.#levels();
115
+ this.bounds = this.#bounds();
116
+ }
117
+ /** Features as plain rows: polygons are rooms, lines are doors, points are assets. */
118
+ static #fromFeatures({ features }, fields) {
119
+ const plan = {
120
+ rooms: [],
121
+ doors: [],
122
+ assets: []
123
+ };
124
+ for (const feature of features ?? []) {
125
+ const { geometry } = feature, properties = {
126
+ id: feature.id,
127
+ ...feature.properties
128
+ };
129
+ if (geometry?.type === "Polygon" || geometry?.type === "MultiPolygon") plan.rooms.push({
130
+ ...properties,
131
+ [fields.points]: geometry.coordinates
132
+ });
133
+ else if (geometry?.type === "LineString") plan.doors.push({
134
+ ...properties,
135
+ from: geometry.coordinates[0],
136
+ to: geometry.coordinates.at(-1)
137
+ });
138
+ else if (geometry?.type === "Point") plan.assets.push({
139
+ ...properties,
140
+ [fields.x]: geometry.coordinates[0],
141
+ [fields.y]: geometry.coordinates[1]
142
+ });
143
+ }
144
+ return plan;
145
+ }
146
+ #levels() {
147
+ const levels = [...new Set([
148
+ ...this.rooms,
149
+ ...this.doors,
150
+ ...this.assets
151
+ ].flatMap((item) => item.level ?? []))];
152
+ return levels.every((level) => number(level) !== null) ? levels.sort((a, b) => +a - +b) : levels;
153
+ }
154
+ /** The whole building, so that floors stay registered with each other. */
155
+ #bounds() {
156
+ const points = [
157
+ ...this.rooms.flatMap((room) => room.parts.flat(2)),
158
+ ...this.doors.flatMap((door) => [door.from, door.to]),
159
+ ...this.assets.map(({ x, y }) => [x, y])
160
+ ];
161
+ if (!points.length) return null;
162
+ const xs = points.map(([x]) => x), ys = points.map(([, y]) => y);
163
+ return {
164
+ x0: Math.min(...xs),
165
+ y0: Math.min(...ys),
166
+ x1: Math.max(...xs),
167
+ y1: Math.max(...ys)
168
+ };
169
+ }
170
+ /** The floor to show: the one asked for, or the lowest. */
171
+ selected(level) {
172
+ return level === void 0 ? this.levels[0] ?? null : String(level);
173
+ }
174
+ /** Whether an item stands on this floor. */
175
+ static on(item, level) {
176
+ return item.level === null || level === null || item.level === level;
177
+ }
178
+ };
179
+ export { FloorDoorSwing, FloorPlanModel };