@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,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 };
|