@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,245 @@
|
|
|
1
|
+
import { ChartExporter } from "./ChartExporter.js";
|
|
2
|
+
import { ChartExportError, ExportErrorCode } from "./ChartExportError.js";
|
|
3
|
+
import { ExportText } from "./ExportText.js";
|
|
4
|
+
import { PdfOrientation, PdfPageSize } from "./PdfPage.js";
|
|
5
|
+
import { PDFDocument, PageSizes, StandardFonts, rgb } from "pdf-lib";
|
|
6
|
+
// src/core/PdfWriter.ts
|
|
7
|
+
var SIZES = {
|
|
8
|
+
[PdfPageSize.A4]: PageSizes.A4,
|
|
9
|
+
[PdfPageSize.Letter]: PageSizes.Letter
|
|
10
|
+
};
|
|
11
|
+
var MARGIN = 36;
|
|
12
|
+
var TITLE = 16;
|
|
13
|
+
var TEXT = 8;
|
|
14
|
+
var FOOTER = 7;
|
|
15
|
+
var ROW = 14;
|
|
16
|
+
var PADDING = 4;
|
|
17
|
+
var [MIN_COLUMN, MAX_COLUMN] = [44, 180];
|
|
18
|
+
/** Column widths come from this many rows, so a long table costs the same to measure. */
|
|
19
|
+
var MEASURED = 500;
|
|
20
|
+
/** Around 500 pages, where a viewer struggles and a spreadsheet does better. */
|
|
21
|
+
var MAX_ROWS = 25e3;
|
|
22
|
+
/** Canvas pixels per point, for text the built-in fonts have no glyph for. */
|
|
23
|
+
var SHARPNESS = 4;
|
|
24
|
+
var [ASCENT, DESCENT] = [1.05, .35];
|
|
25
|
+
var FALLBACK_FONT = "Helvetica, Arial, \"Noto Sans\", sans-serif";
|
|
26
|
+
var INK = rgb(.1, .1, .1);
|
|
27
|
+
var QUIET = rgb(.4, .4, .4);
|
|
28
|
+
var RULE = rgb(.8, .8, .8);
|
|
29
|
+
var BAND = rgb(.94, .94, .94);
|
|
30
|
+
/** Places a chart's PNG on a page and sets `table()` out as real text, one set of pages per block of columns that fits. */
|
|
31
|
+
var PdfWriter = class PdfWriter {
|
|
32
|
+
#doc;
|
|
33
|
+
#regular;
|
|
34
|
+
#bold;
|
|
35
|
+
#glyphs;
|
|
36
|
+
#pictures = /* @__PURE__ */ new Map();
|
|
37
|
+
#ruler = null;
|
|
38
|
+
#width;
|
|
39
|
+
#height;
|
|
40
|
+
constructor(doc, regular, bold, size) {
|
|
41
|
+
this.#doc = doc;
|
|
42
|
+
this.#regular = regular;
|
|
43
|
+
this.#bold = bold;
|
|
44
|
+
this.#glyphs = new Set(regular.getCharacterSet());
|
|
45
|
+
[this.#width, this.#height] = size;
|
|
46
|
+
}
|
|
47
|
+
static async write(chart, options = {}) {
|
|
48
|
+
const table = options.table ? ExportText.rows(chart, "A PDF data table") : null;
|
|
49
|
+
return PdfWriter.file(await chart.toPNG({ scale: options.scale ?? 3 }), table, options);
|
|
50
|
+
}
|
|
51
|
+
static async file(png, table, options) {
|
|
52
|
+
const { pageSize = PdfPageSize.A4, orientation = PdfOrientation.Auto, title } = options;
|
|
53
|
+
const size = SIZES[pageSize];
|
|
54
|
+
if (!size) throw new RangeError(`Unknown PDF page size ${pageSize}.`);
|
|
55
|
+
if (!Object.values(PdfOrientation).includes(orientation)) throw new RangeError(`Unknown PDF orientation ${orientation}.`);
|
|
56
|
+
if (table && table.rows.length > MAX_ROWS) throw new ChartExportError(ExportErrorCode.Unsupported, `A PDF table holds ${MAX_ROWS} rows and this one has ${table.rows.length}. Use Excel or CSV for the data.`);
|
|
57
|
+
const doc = await PDFDocument.create();
|
|
58
|
+
doc.setCreator("@mstone6969/graph");
|
|
59
|
+
doc.setProducer("pdf-lib");
|
|
60
|
+
if (title) doc.setTitle(title);
|
|
61
|
+
const chart = await doc.embedPng(await png.arrayBuffer());
|
|
62
|
+
const [narrow, wide] = size;
|
|
63
|
+
const landscape = orientation === PdfOrientation.Landscape || orientation === PdfOrientation.Auto && chart.width >= chart.height;
|
|
64
|
+
const writer = new PdfWriter(doc, await doc.embedFont(StandardFonts.Helvetica), await doc.embedFont(StandardFonts.HelveticaBold), landscape ? [wide, narrow] : [narrow, wide]);
|
|
65
|
+
await writer.#chartPage(chart, title);
|
|
66
|
+
if (table) await writer.#tablePages(table);
|
|
67
|
+
await writer.#numberPages();
|
|
68
|
+
return new Blob([await doc.save()], { type: "application/pdf" });
|
|
69
|
+
}
|
|
70
|
+
#page() {
|
|
71
|
+
return this.#doc.addPage([this.#width, this.#height]);
|
|
72
|
+
}
|
|
73
|
+
get #room() {
|
|
74
|
+
return this.#width - 72;
|
|
75
|
+
}
|
|
76
|
+
async #chartPage(chart, title) {
|
|
77
|
+
const page = this.#page();
|
|
78
|
+
let top = this.#height - MARGIN;
|
|
79
|
+
if (title) {
|
|
80
|
+
top -= TITLE;
|
|
81
|
+
await this.#text(page, title, MARGIN, top, this.#room, {
|
|
82
|
+
size: TITLE,
|
|
83
|
+
bold: true
|
|
84
|
+
});
|
|
85
|
+
top -= TITLE;
|
|
86
|
+
}
|
|
87
|
+
const fit = Math.min(this.#room / chart.width, (top - MARGIN) / chart.height);
|
|
88
|
+
const [width, height] = [chart.width * fit, chart.height * fit];
|
|
89
|
+
page.drawImage(chart, {
|
|
90
|
+
x: (this.#width - width) / 2,
|
|
91
|
+
y: top - height,
|
|
92
|
+
width,
|
|
93
|
+
height
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async #tablePages({ columns, rows }) {
|
|
97
|
+
const texts = rows.map((row) => columns.map((_, c) => ExportText.of(row[c])));
|
|
98
|
+
const natural = columns.map((column, c) => {
|
|
99
|
+
const widest = texts.slice(0, MEASURED).reduce((most, row) => Math.max(most, this.#textWidth(PdfWriter.#clean(row[c]), TEXT)), this.#textWidth(PdfWriter.#clean(column), TEXT, true));
|
|
100
|
+
return Math.min(MAX_COLUMN, Math.max(MIN_COLUMN, widest + 8));
|
|
101
|
+
});
|
|
102
|
+
for (const block of this.#blocks(natural)) {
|
|
103
|
+
const used = block.reduce((sum, c) => sum + natural[c], 0);
|
|
104
|
+
const widths = block.map((c) => natural[c] * this.#room / used);
|
|
105
|
+
let page = null, y = 0;
|
|
106
|
+
for (const [r, row] of texts.entries()) {
|
|
107
|
+
if (!page || y < 50) {
|
|
108
|
+
page = this.#page();
|
|
109
|
+
y = this.#height - MARGIN - ROW;
|
|
110
|
+
page.drawRectangle({
|
|
111
|
+
x: MARGIN,
|
|
112
|
+
y: y - PADDING,
|
|
113
|
+
width: this.#room,
|
|
114
|
+
height: ROW,
|
|
115
|
+
color: BAND
|
|
116
|
+
});
|
|
117
|
+
await this.#row(page, block.map((c) => columns[c]), block.map((c) => typeof rows[0][c] === "number"), widths, y, true);
|
|
118
|
+
y -= ROW;
|
|
119
|
+
}
|
|
120
|
+
await this.#row(page, block.map((c) => row[c]), block.map((c) => typeof rows[r][c] === "number"), widths, y, false);
|
|
121
|
+
page.drawLine({
|
|
122
|
+
start: {
|
|
123
|
+
x: MARGIN,
|
|
124
|
+
y: y - PADDING
|
|
125
|
+
},
|
|
126
|
+
end: {
|
|
127
|
+
x: MARGIN + this.#room,
|
|
128
|
+
y: y - PADDING
|
|
129
|
+
},
|
|
130
|
+
thickness: .25,
|
|
131
|
+
color: RULE
|
|
132
|
+
});
|
|
133
|
+
y -= ROW;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Greedy runs of columns that fit a page. Later runs repeat the first column, so their rows can still be told apart. */
|
|
138
|
+
#blocks(widths) {
|
|
139
|
+
const blocks = [];
|
|
140
|
+
let used = Infinity;
|
|
141
|
+
widths.forEach((width, c) => {
|
|
142
|
+
if (used + width > this.#room) {
|
|
143
|
+
blocks.push(c ? [0, c] : [c]);
|
|
144
|
+
used = (c ? widths[0] : 0) + width;
|
|
145
|
+
} else {
|
|
146
|
+
blocks.at(-1).push(c);
|
|
147
|
+
used += width;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return blocks;
|
|
151
|
+
}
|
|
152
|
+
async #row(page, cells, right, widths, y, bold) {
|
|
153
|
+
let x = MARGIN;
|
|
154
|
+
for (const [c, cell] of cells.entries()) {
|
|
155
|
+
const room = widths[c] - 8;
|
|
156
|
+
await this.#text(page, cell, right[c] ? x + PADDING + room : x + PADDING, y, room, {
|
|
157
|
+
size: TEXT,
|
|
158
|
+
bold,
|
|
159
|
+
right: right[c]
|
|
160
|
+
});
|
|
161
|
+
x += widths[c];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async #numberPages() {
|
|
165
|
+
const pages = this.#doc.getPages();
|
|
166
|
+
if (pages.length < 2) return;
|
|
167
|
+
for (const [i, page] of pages.entries()) await this.#text(page, `Page ${i + 1} of ${pages.length}`, this.#width - MARGIN, MARGIN / 2, this.#room, {
|
|
168
|
+
size: FOOTER,
|
|
169
|
+
color: QUIET,
|
|
170
|
+
right: true
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
static #clean(text) {
|
|
174
|
+
return text.replace(/\s+/g, " ").trim();
|
|
175
|
+
}
|
|
176
|
+
#encodable(text) {
|
|
177
|
+
for (const char of text) if (!this.#glyphs.has(char.codePointAt(0))) return false;
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
#font(bold) {
|
|
181
|
+
return bold ? this.#bold : this.#regular;
|
|
182
|
+
}
|
|
183
|
+
#cssFont(px, bold) {
|
|
184
|
+
return `${bold ? "bold " : ""}${px}px ${FALLBACK_FONT}`;
|
|
185
|
+
}
|
|
186
|
+
#textWidth(text, size, bold) {
|
|
187
|
+
if (this.#encodable(text)) return this.#font(bold).widthOfTextAtSize(text, size);
|
|
188
|
+
this.#ruler ??= ChartExporter.paint(ChartExporter.canvas(1, 1));
|
|
189
|
+
this.#ruler.font = this.#cssFont(size * SHARPNESS, bold);
|
|
190
|
+
return this.#ruler.measureText(text).width / SHARPNESS;
|
|
191
|
+
}
|
|
192
|
+
/** Cuts by code point, so an ellipsis never lands inside a surrogate pair. */
|
|
193
|
+
#fit(text, room, size, bold) {
|
|
194
|
+
if (this.#textWidth(text, size, bold) <= room) return text;
|
|
195
|
+
const chars = [...text];
|
|
196
|
+
let [low, high] = [0, chars.length];
|
|
197
|
+
while (low < high) {
|
|
198
|
+
const middle = Math.ceil((low + high) / 2);
|
|
199
|
+
if (this.#textWidth(`${chars.slice(0, middle).join("")}…`, size, bold) <= room) low = middle;
|
|
200
|
+
else high = middle - 1;
|
|
201
|
+
}
|
|
202
|
+
return `${chars.slice(0, low).join("").trimEnd()}…`;
|
|
203
|
+
}
|
|
204
|
+
/** The 14 built-in fonts cover Western European text only. Anything else is drawn as a small picture, so it shows but cannot be selected. */
|
|
205
|
+
async #text(page, text, x, y, room, style) {
|
|
206
|
+
const { size, bold, color = INK, right } = style;
|
|
207
|
+
const clean = PdfWriter.#clean(text);
|
|
208
|
+
if (!clean) return;
|
|
209
|
+
const shown = this.#fit(clean, room, size, bold);
|
|
210
|
+
const width = this.#textWidth(shown, size, bold);
|
|
211
|
+
const left = right ? x - width : x;
|
|
212
|
+
if (this.#encodable(shown)) {
|
|
213
|
+
page.drawText(shown, {
|
|
214
|
+
x: left,
|
|
215
|
+
y,
|
|
216
|
+
size,
|
|
217
|
+
font: this.#font(bold),
|
|
218
|
+
color
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const picture = await this.#picture(shown, width, size, bold);
|
|
223
|
+
page.drawImage(picture, {
|
|
224
|
+
x: left,
|
|
225
|
+
y: y - size * DESCENT,
|
|
226
|
+
width: picture.width / SHARPNESS,
|
|
227
|
+
height: picture.height / SHARPNESS
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
async #picture(text, width, size, bold) {
|
|
231
|
+
const key = `${size}|${bold}|${text}`;
|
|
232
|
+
const known = this.#pictures.get(key);
|
|
233
|
+
if (known) return known;
|
|
234
|
+
const canvas = ChartExporter.canvas(Math.ceil(width * SHARPNESS), Math.ceil(size * (ASCENT + DESCENT) * SHARPNESS));
|
|
235
|
+
const paint = ChartExporter.paint(canvas);
|
|
236
|
+
paint.font = this.#cssFont(size * SHARPNESS, bold);
|
|
237
|
+
paint.fillStyle = "#1a1a1a";
|
|
238
|
+
paint.textBaseline = "alphabetic";
|
|
239
|
+
paint.fillText(text, 0, size * ASCENT * SHARPNESS);
|
|
240
|
+
const picture = await this.#doc.embedPng(await (await ChartExporter.png(canvas)).arrayBuffer());
|
|
241
|
+
this.#pictures.set(key, picture);
|
|
242
|
+
return picture;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
export { PdfWriter };
|
package/dist/core/Scales.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { AxisWindow } from "./AxisWindow.js";
|
|
1
2
|
import { CurveKind, ScaleKind, SymbolKind } from "./enums.js";
|
|
2
|
-
import { curveBasis, curveBumpX, curveCardinal, curveCatmullRom, curveLinear, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore, scaleLinear, scaleLog, scalePow, scaleSqrt, scaleSymlog, symbolCircle, symbolCross, symbolDiamond, symbolSquare, symbolStar, symbolTriangle, symbolWye } from "d3";
|
|
3
|
+
import { curveBasis, curveBumpX, curveCardinal, curveCatmullRom, curveLinear, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore, interpolateNumber, scaleLinear, scaleLog, scalePow, scaleSqrt, scaleSymlog, symbolCircle, symbolCross, symbolDiamond, symbolSquare, symbolStar, symbolTriangle, symbolWye } from "d3";
|
|
3
4
|
// src/core/Scales.ts
|
|
4
5
|
var CURVES = {
|
|
5
6
|
[CurveKind.Linear]: curveLinear,
|
|
@@ -23,7 +24,7 @@ var SYMBOLS = {
|
|
|
23
24
|
[SymbolKind.Wye]: symbolWye
|
|
24
25
|
};
|
|
25
26
|
/** Maps library enums to d3 factories. */
|
|
26
|
-
var Scales = class {
|
|
27
|
+
var Scales = class Scales {
|
|
27
28
|
static curve(kind = CurveKind.Linear) {
|
|
28
29
|
return CURVES[kind];
|
|
29
30
|
}
|
|
@@ -39,6 +40,50 @@ var Scales = class {
|
|
|
39
40
|
default: return scaleLinear();
|
|
40
41
|
}
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Turns a built continuous scale into `kind` in place, because subclasses close over the scales
|
|
45
|
+
* they build. Says if it did: dates, bands and piecewise domains stay as they are, and so does `Log`
|
|
46
|
+
* over a value at or below 0. `floor`, the smallest value plotted, lets `Log` start a zero-based domain.
|
|
47
|
+
*/
|
|
48
|
+
static retype(scale, kind, floor = null) {
|
|
49
|
+
const domain = scale.domain?.();
|
|
50
|
+
if (AxisWindow.kind(scale) !== "continuous" || !scale.interpolate || domain?.length !== 2 || domain.some((value) => value instanceof Date)) return false;
|
|
51
|
+
if (kind === ScaleKind.Log) {
|
|
52
|
+
const ends = domain, low = ends[0] <= ends[1] ? 0 : 1;
|
|
53
|
+
if (ends[1 - low] <= 0 || ends[low] <= 0 && floor === null) return false;
|
|
54
|
+
if (ends[low] <= 0) {
|
|
55
|
+
ends[low] = 10 ** Math.floor(Math.log10(floor));
|
|
56
|
+
scale.domain(ends);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
Scales.#adopt(scale, kind, scale.copy().interpolate(interpolateNumber).range([0, 1]).clamp(false));
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/** `reader` undoes the scale's own normalising. What the scale's new shape cannot place lands on the low end, never on NaN. */
|
|
63
|
+
static #adopt(scale, kind, reader) {
|
|
64
|
+
const shape = Scales.continuous(kind).range([0, 1]);
|
|
65
|
+
const synced = () => shape.domain(scale.domain());
|
|
66
|
+
const { copy } = scale;
|
|
67
|
+
scale.interpolate((from, to) => {
|
|
68
|
+
synced();
|
|
69
|
+
return (t) => {
|
|
70
|
+
const at = shape(reader.domain(scale.domain()).invert(t));
|
|
71
|
+
return from + (to - from) * (Number.isFinite(at) ? at : 0);
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
scale.shows = (value) => synced()(value);
|
|
75
|
+
scale.invert = (at) => {
|
|
76
|
+
const [from, to] = scale.range();
|
|
77
|
+
return synced().invert((at - from) / (to - from));
|
|
78
|
+
};
|
|
79
|
+
scale.tickFormat = (...args) => synced().tickFormat(...args);
|
|
80
|
+
scale.ticks = (...args) => kind === ScaleKind.Log ? decades(synced().ticks(...args)) : synced().ticks(...args);
|
|
81
|
+
scale.copy = () => {
|
|
82
|
+
const twin = copy.call(scale);
|
|
83
|
+
Scales.#adopt(twin, kind, reader.copy());
|
|
84
|
+
return twin;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
42
87
|
/** Clean axis ticks: full below 10,000, compact above. */
|
|
43
88
|
static format(value, reach) {
|
|
44
89
|
if (reach >= 1e4) return Intl.NumberFormat("en", {
|
|
@@ -48,4 +93,9 @@ var Scales = class {
|
|
|
48
93
|
return value.toLocaleString("en", { maximumFractionDigits: reach < 10 ? 2 : 0 });
|
|
49
94
|
}
|
|
50
95
|
};
|
|
96
|
+
/** A log scale offers a tick per mantissa, far more than an axis can label: keep powers of ten while there are at least two. */
|
|
97
|
+
function decades(ticks) {
|
|
98
|
+
const tens = ticks.filter((tick) => Math.abs(Math.log10(tick) % 1) < 1e-9);
|
|
99
|
+
return tens.length >= 2 ? tens : ticks.filter((_, i) => i % Math.ceil(ticks.length / 6) === 0);
|
|
100
|
+
}
|
|
51
101
|
export { Scales };
|
|
@@ -31,7 +31,7 @@ var SceneOverlay = class SceneOverlay {
|
|
|
31
31
|
paint.fillText(element.textContent, (at.x + 1) / 2 * width - center.x * box.width, (1 - at.y) / 2 * height + (.5 - center.y) * parseFloat(element.style.fontSize || "11") * LINE_HEIGHT);
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
-
static #header(paint, { width }, theme, { title, subtitle, items }) {
|
|
34
|
+
static #header(paint, { width }, theme, { title, subtitle, items, fontScale = 1 }) {
|
|
35
35
|
let y = INSET.y;
|
|
36
36
|
const line = (text, font, color, size) => {
|
|
37
37
|
paint.font = font;
|
|
@@ -39,9 +39,9 @@ var SceneOverlay = class SceneOverlay {
|
|
|
39
39
|
paint.fillText(text, INSET.x, y + size * LINE_HEIGHT / 2);
|
|
40
40
|
y += size * LINE_HEIGHT;
|
|
41
41
|
};
|
|
42
|
-
if (title) line(title, `600
|
|
43
|
-
if (subtitle) line(subtitle,
|
|
44
|
-
paint.font =
|
|
42
|
+
if (title) line(title, `600 ${15 * fontScale}px ${theme.fontFamily}`, theme.ink, 15 * fontScale);
|
|
43
|
+
if (subtitle) line(subtitle, `${12 * fontScale}px ${theme.fontFamily}`, theme.inkSecondary, 12 * fontScale);
|
|
44
|
+
paint.font = `${12 * fontScale}px ${theme.fontFamily}`;
|
|
45
45
|
let x = INSET.x;
|
|
46
46
|
y += items.length ? 13 : 0;
|
|
47
47
|
for (const { key, label, color } of items) {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/core/TextFit.ts
|
|
2
|
+
var ELLIPSIS = "…";
|
|
3
|
+
/** Weights from glyph widths measured in the theme's system font, where one unit is an average Latin character. */
|
|
4
|
+
var ELLIPSIS_WEIGHT = 1.5;
|
|
5
|
+
var THIN = /^[fijlrt.,:;'|!()[\] ]$/;
|
|
6
|
+
var BROAD = /^[A-Zmw@%]$/;
|
|
7
|
+
/** One grapheme drawn about a full em wide: CJK, kana, hangul, full-width forms and emoji. */
|
|
8
|
+
var WIDE = /\p{Script=Han}|\p{Script=Hiragana}|\p{Script=Katakana}|\p{Script=Hangul}|[ -〿!-⦆¢-₩]|\p{Emoji_Presentation}|️/u;
|
|
9
|
+
var ZERO = /^[\p{M}\p{Cf}]+$/u;
|
|
10
|
+
var segmenter = typeof Intl.Segmenter === "function" ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
11
|
+
var graphemes = (text) => segmenter ? Array.from(segmenter.segment(text), (part) => part.segment) : Array.from(text);
|
|
12
|
+
var cells = (grapheme) => ZERO.test(grapheme) ? 0 : WIDE.test(grapheme) ? 2 : 1;
|
|
13
|
+
/** A closer estimate for deciding on a cut, so all-caps text stops in time and text of thin letters is left whole. */
|
|
14
|
+
var weight = (grapheme) => THIN.test(grapheme) ? .6 : BROAD.test(grapheme) ? 1.3 : cells(grapheme) === 1 ? 1.05 : cells(grapheme);
|
|
15
|
+
/** Estimated text width and truncation, without measuring. `char` is the px width of an average Latin character. */
|
|
16
|
+
var TextFit = class {
|
|
17
|
+
/** Width of `text` in px. Wide scripts count double, combining marks count nothing. */
|
|
18
|
+
static width(text, char) {
|
|
19
|
+
return graphemes(text).reduce((sum, grapheme) => sum + cells(grapheme), 0) * char;
|
|
20
|
+
}
|
|
21
|
+
/** `text` cut to `room` px with an ellipsis. Whole graphemes only, so an emoji or accented letter never splits. */
|
|
22
|
+
static fit(text, room, char) {
|
|
23
|
+
const parts = graphemes(text), limit = room / char;
|
|
24
|
+
if (parts.reduce((sum, part) => sum + weight(part), 0) <= limit) return text;
|
|
25
|
+
let used = ELLIPSIS_WEIGHT, keep = 0;
|
|
26
|
+
while (keep < parts.length && used + weight(parts[keep]) <= limit) used += weight(parts[keep++]);
|
|
27
|
+
return `${parts.slice(0, keep).join("").trimEnd()}${ELLIPSIS}`;
|
|
28
|
+
}
|
|
29
|
+
/** Adds a `<title>` child holding the full text, or removes it when `full` gives nothing. */
|
|
30
|
+
static hint(selection, full) {
|
|
31
|
+
selection.selectAll("title").data((datum) => {
|
|
32
|
+
const text = full(datum);
|
|
33
|
+
return text ? [text] : [];
|
|
34
|
+
}).join("title").text((text) => text);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export { TextFit };
|
package/dist/core/Theme.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ThemeMode } from "./enums.js";
|
|
2
|
+
import { hsl } from "d3";
|
|
2
3
|
// src/core/Theme.ts
|
|
3
4
|
var SEQUENTIAL = [
|
|
4
5
|
"#cde2fb",
|
|
@@ -66,10 +67,36 @@ var Theme = class Theme {
|
|
|
66
67
|
...STATUS,
|
|
67
68
|
fontFamily: FONT
|
|
68
69
|
};
|
|
69
|
-
static resolve(theme = ThemeMode.Auto) {
|
|
70
|
+
static resolve(theme = ThemeMode.Auto, style) {
|
|
71
|
+
const tokens = Theme.#mode(theme);
|
|
72
|
+
return style ? Theme.#styled(tokens, style) : tokens;
|
|
73
|
+
}
|
|
74
|
+
static #mode(theme) {
|
|
70
75
|
if (typeof theme === "object") return theme;
|
|
71
76
|
if (theme === ThemeMode.Auto) return globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ? Theme.dark : Theme.light;
|
|
72
77
|
return theme === ThemeMode.Dark ? Theme.dark : Theme.light;
|
|
73
78
|
}
|
|
79
|
+
/** A style colour as a CSS colour: token names such as `good` follow the theme's mode. */
|
|
80
|
+
static #paint(tokens, value) {
|
|
81
|
+
const token = tokens[value];
|
|
82
|
+
return typeof token === "string" ? token : value;
|
|
83
|
+
}
|
|
84
|
+
/** Slots past a short `colors` hold `inkMuted`, the colour `Palette` folds into Other, so nothing reads an empty slot. */
|
|
85
|
+
static #styled(tokens, { colors, sequential, diverging, fontFamily }) {
|
|
86
|
+
const paint = (value) => Theme.#paint(tokens, value);
|
|
87
|
+
const stops = sequential && sequential.length >= 2 ? sequential.map(paint) : null;
|
|
88
|
+
if (!colors?.length && !stops && !diverging && !fontFamily) return tokens;
|
|
89
|
+
const styled = { ...tokens };
|
|
90
|
+
if (colors?.length) styled.categorical = Array.from({ length: Math.max(colors.length, tokens.categorical.length) }, (_, i) => colors[i] === void 0 ? tokens.inkMuted : paint(colors[i]));
|
|
91
|
+
if (stops) styled.sequential = hsl(tokens.surface).l < .5 ? stops.reverse() : stops;
|
|
92
|
+
if (diverging) [styled.divergingLow, styled.divergingMid, styled.divergingHigh] = diverging.map(paint);
|
|
93
|
+
if (fontFamily) styled.fontFamily = fontFamily;
|
|
94
|
+
return styled;
|
|
95
|
+
}
|
|
96
|
+
/** `seriesColors` resolved against `tokens`, for `Palette`. */
|
|
97
|
+
static pins(tokens, style) {
|
|
98
|
+
const pinned = style?.seriesColors;
|
|
99
|
+
return pinned && Object.fromEntries(Object.entries(pinned).map(([key, value]) => [key, Theme.#paint(tokens, value)]));
|
|
100
|
+
}
|
|
74
101
|
};
|
|
75
102
|
export { Theme };
|
package/dist/core/enums.js
CHANGED
|
@@ -104,4 +104,36 @@ var ColorScale = /* @__PURE__ */ function(ColorScale) {
|
|
|
104
104
|
ColorScale["Categorical"] = "categorical";
|
|
105
105
|
return ColorScale;
|
|
106
106
|
}({});
|
|
107
|
-
|
|
107
|
+
/** What draws a chart's marks. */
|
|
108
|
+
var RenderType = /* @__PURE__ */ function(RenderType) {
|
|
109
|
+
RenderType["Svg"] = "svg";
|
|
110
|
+
RenderType["Canvas"] = "canvas";
|
|
111
|
+
RenderType["WebGL"] = "webgl";
|
|
112
|
+
return RenderType;
|
|
113
|
+
}({});
|
|
114
|
+
var StrokeDash = /* @__PURE__ */ function(StrokeDash) {
|
|
115
|
+
StrokeDash["Solid"] = "solid";
|
|
116
|
+
StrokeDash["Dashed"] = "dashed";
|
|
117
|
+
StrokeDash["Dotted"] = "dotted";
|
|
118
|
+
return StrokeDash;
|
|
119
|
+
}({});
|
|
120
|
+
var StrokeCap = /* @__PURE__ */ function(StrokeCap) {
|
|
121
|
+
StrokeCap["Butt"] = "butt";
|
|
122
|
+
StrokeCap["Round"] = "round";
|
|
123
|
+
StrokeCap["Square"] = "square";
|
|
124
|
+
return StrokeCap;
|
|
125
|
+
}({});
|
|
126
|
+
var StrokeJoin = /* @__PURE__ */ function(StrokeJoin) {
|
|
127
|
+
StrokeJoin["Miter"] = "miter";
|
|
128
|
+
StrokeJoin["Round"] = "round";
|
|
129
|
+
StrokeJoin["Bevel"] = "bevel";
|
|
130
|
+
return StrokeJoin;
|
|
131
|
+
}({});
|
|
132
|
+
/** `shape-rendering` on marks: `Crisp` snaps edges to pixels, `Precise` favours exact geometry. */
|
|
133
|
+
var EdgeRendering = /* @__PURE__ */ function(EdgeRendering) {
|
|
134
|
+
EdgeRendering["Auto"] = "auto";
|
|
135
|
+
EdgeRendering["Crisp"] = "crisp";
|
|
136
|
+
EdgeRendering["Precise"] = "precise";
|
|
137
|
+
return EdgeRendering;
|
|
138
|
+
}({});
|
|
139
|
+
export { ChartFamily, ColorScale, CurveKind, DataShape, Dimension, EdgeRendering, LegendPosition, Orientation, RenderType, ScaleKind, StackMode, StrokeCap, StrokeDash, StrokeJoin, SymbolKind, ThemeMode };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { interpolateString } from "d3";
|
|
2
|
+
// src/core/pathMorph.ts
|
|
3
|
+
var COMMAND = /([A-Za-df-z])([^A-Za-df-z]*)/g;
|
|
4
|
+
var NUMBER = /[-+]?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/gi;
|
|
5
|
+
/** Commands and arc flags, with every other number gone. Flags take 0 or 1 only, so two paths tween if and only if these match. */
|
|
6
|
+
var skeleton = (d) => [...d.matchAll(COMMAND)].map(([, command, args]) => {
|
|
7
|
+
const numbers = args.match(NUMBER) ?? [];
|
|
8
|
+
const arc = command === "A" || command === "a";
|
|
9
|
+
return `${command}${numbers.length}${arc ? numbers.filter((_, i) => i % 7 === 3 || i % 7 === 4).join("") : ""}`;
|
|
10
|
+
}).join();
|
|
11
|
+
/** An attribute tween for `d`. A path with arcs snaps unless its start has the same skeleton; all other paths tween as plain strings. */
|
|
12
|
+
function pathMorph(from, to) {
|
|
13
|
+
return /a/i.test(to) && skeleton(from) !== skeleton(to) ? () => to : interpolateString(from, to);
|
|
14
|
+
}
|
|
15
|
+
export { pathMorph };
|
package/dist/data/SampleData.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import countries_110m_default from "../vendor/world-atlas/countries-110m.json.js";
|
|
2
1
|
import { randomLcg, randomNormal, randomUniform } from "d3";
|
|
3
|
-
import { feature } from "topojson-client";
|
|
4
2
|
// src/data/SampleData.ts
|
|
5
3
|
var NAMES = [
|
|
6
4
|
"North",
|
|
@@ -28,6 +26,7 @@ var SERIES = [
|
|
|
28
26
|
];
|
|
29
27
|
/** Deterministic demo datasets: same seed, same data. All gallery and test data comes from here. */
|
|
30
28
|
var SampleData = class {
|
|
29
|
+
/** Seeded generators behind every sample. */
|
|
31
30
|
static rng(seed) {
|
|
32
31
|
const source = randomLcg(seed * .6180339887 % 1);
|
|
33
32
|
return {
|
|
@@ -193,21 +192,6 @@ var SampleData = class {
|
|
|
193
192
|
values
|
|
194
193
|
};
|
|
195
194
|
}
|
|
196
|
-
/** Lower-48-free world countries as GeoJSON features; `properties.value` is seeded. */
|
|
197
|
-
static world(seed = 1) {
|
|
198
|
-
const { u } = this.rng(seed);
|
|
199
|
-
const geo = feature(countries_110m_default, countries_110m_default.objects.countries);
|
|
200
|
-
return {
|
|
201
|
-
...geo,
|
|
202
|
-
features: geo.features.map((f) => ({
|
|
203
|
-
...f,
|
|
204
|
-
properties: {
|
|
205
|
-
...f.properties,
|
|
206
|
-
value: +(u() * 100).toFixed(1)
|
|
207
|
-
}
|
|
208
|
-
}))
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
195
|
/** `{ name, lon, lat, value }`, world cities. */
|
|
212
196
|
static cities(seed = 1) {
|
|
213
197
|
const { u } = this.rng(seed);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import countries_110m_default from "../vendor/world-atlas/countries-110m.json.js";
|
|
2
|
+
import { SampleData } from "./SampleData.js";
|
|
3
|
+
import { feature } from "topojson-client";
|
|
4
|
+
// src/data/WorldSample.ts
|
|
5
|
+
/** The world atlas as sample data, apart from `SampleData` so charts without a map do not carry it. */
|
|
6
|
+
var WorldSample = class {
|
|
7
|
+
/** World countries as GeoJSON features; `properties.value` is seeded. */
|
|
8
|
+
static countries(seed = 1) {
|
|
9
|
+
const { u } = SampleData.rng(seed);
|
|
10
|
+
const geo = feature(countries_110m_default, countries_110m_default.objects.countries);
|
|
11
|
+
return {
|
|
12
|
+
...geo,
|
|
13
|
+
features: geo.features.map((f) => ({
|
|
14
|
+
...f,
|
|
15
|
+
properties: {
|
|
16
|
+
...f.properties,
|
|
17
|
+
value: +(u() * 100).toFixed(1)
|
|
18
|
+
}
|
|
19
|
+
}))
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export { WorldSample };
|