@opendata-ai/openchart-engine 7.2.1 → 7.2.3
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/dist/index.js +55 -35
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +9 -3
- package/src/__tests__/compile-chart.test.ts +26 -0
- package/src/annotations/__tests__/compute.test.ts +83 -0
- package/src/annotations/resolve-range.ts +23 -6
- package/src/annotations/resolve-refline.ts +6 -1
- package/src/compile.ts +19 -9
- package/src/layout/dimensions.ts +21 -13
- package/src/layout/metrics.ts +34 -20
package/dist/index.js
CHANGED
|
@@ -766,16 +766,18 @@ function resolveRangeAnnotation(annotation, scales, chartArea, isDark) {
|
|
|
766
766
|
let y2 = chartArea.y;
|
|
767
767
|
let width = chartArea.width;
|
|
768
768
|
let height = chartArea.height;
|
|
769
|
+
const extend2 = annotation.extendToEdges !== false;
|
|
770
|
+
const resolveEdge = (value2, scale, edge) => extend2 ? resolvePositionEdge(value2, scale, edge) : resolvePosition(value2, scale);
|
|
769
771
|
if (annotation.x1 !== void 0 && annotation.x2 !== void 0) {
|
|
770
|
-
const x1px =
|
|
771
|
-
const x2px =
|
|
772
|
+
const x1px = resolveEdge(annotation.x1, scales.x, "start");
|
|
773
|
+
const x2px = resolveEdge(annotation.x2, scales.x, "end");
|
|
772
774
|
if (x1px === null || x2px === null) return null;
|
|
773
775
|
x2 = Math.min(x1px, x2px);
|
|
774
776
|
width = Math.abs(x2px - x1px);
|
|
775
777
|
}
|
|
776
778
|
if (annotation.y1 !== void 0 && annotation.y2 !== void 0) {
|
|
777
|
-
const y1px =
|
|
778
|
-
const y2px =
|
|
779
|
+
const y1px = resolveEdge(annotation.y1, scales.y, "end");
|
|
780
|
+
const y2px = resolveEdge(annotation.y2, scales.y, "start");
|
|
779
781
|
if (y1px === null || y2px === null) return null;
|
|
780
782
|
y2 = Math.min(y1px, y2px);
|
|
781
783
|
height = Math.abs(y2px - y1px);
|
|
@@ -788,7 +790,12 @@ function resolveRangeAnnotation(annotation, scales, chartArea, isDark) {
|
|
|
788
790
|
const baseDx = centered ? 0 : anchor === "right" ? -4 : 4;
|
|
789
791
|
const baseDy = 14;
|
|
790
792
|
const labelDelta = applyOffset({ dx: baseDx, dy: baseDy }, annotation.labelOffset);
|
|
791
|
-
const style = makeAnnotationLabelStyle(
|
|
793
|
+
const style = makeAnnotationLabelStyle(
|
|
794
|
+
annotation.fontSize ?? 11,
|
|
795
|
+
annotation.fontWeight ?? 500,
|
|
796
|
+
void 0,
|
|
797
|
+
isDark
|
|
798
|
+
);
|
|
792
799
|
if (centered) {
|
|
793
800
|
style.textAnchor = "middle";
|
|
794
801
|
} else if (anchor === "right") {
|
|
@@ -893,7 +900,12 @@ function resolveRefLineAnnotation(annotation, scales, chartArea, isDark) {
|
|
|
893
900
|
}
|
|
894
901
|
const labelDelta = applyOffset({ dx: baseDx, dy: baseDy }, annotation.labelOffset);
|
|
895
902
|
const defaultStroke2 = isDark ? DARK_REFLINE_STROKE : LIGHT_REFLINE_STROKE;
|
|
896
|
-
const style = makeAnnotationLabelStyle(
|
|
903
|
+
const style = makeAnnotationLabelStyle(
|
|
904
|
+
annotation.fontSize ?? 11,
|
|
905
|
+
annotation.fontWeight ?? 400,
|
|
906
|
+
annotation.stroke ?? defaultStroke2,
|
|
907
|
+
isDark
|
|
908
|
+
);
|
|
897
909
|
style.textAnchor = textAnchor;
|
|
898
910
|
label = {
|
|
899
911
|
text: annotation.label,
|
|
@@ -8973,9 +8985,9 @@ function compileLayerIndependent(leaves, layerSpec, options, compileChart2) {
|
|
|
8973
8985
|
const hasRightAxisTitle = !!yAxisConfig?.title;
|
|
8974
8986
|
const tickExtent = TICK_LABEL_OFFSET + rightAxisWidth;
|
|
8975
8987
|
const bodyFontSize = theme.fonts?.sizes?.body ?? 13;
|
|
8976
|
-
const
|
|
8988
|
+
const axisTitleOffset2 = getAxisTitleOffset(options.width);
|
|
8977
8989
|
const halfGlyph = Math.ceil(bodyFontSize / 2);
|
|
8978
|
-
const titleExtent = hasRightAxisTitle ?
|
|
8990
|
+
const titleExtent = hasRightAxisTitle ? axisTitleOffset2 + halfGlyph + (options.width < BREAKPOINT_COMPACT_MAX ? 0 : AXIS_TITLE_TRAILING_PAD) : 0;
|
|
8979
8991
|
const rightReserve = Math.max(tickExtent, titleExtent);
|
|
8980
8992
|
const optionsWithReserve = {
|
|
8981
8993
|
...options,
|
|
@@ -10187,12 +10199,11 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
10187
10199
|
|
|
10188
10200
|
// src/layout/dimensions.ts
|
|
10189
10201
|
import {
|
|
10190
|
-
AXIS_TITLE_GAP,
|
|
10191
10202
|
AXIS_TITLE_TRAILING_PAD as AXIS_TITLE_TRAILING_PAD2,
|
|
10203
|
+
axisTitleOffset,
|
|
10192
10204
|
BREAKPOINT_COMPACT_MAX as BREAKPOINT_COMPACT_MAX2,
|
|
10193
10205
|
computeChrome as computeChrome3,
|
|
10194
10206
|
estimateTextWidth as estimateTextWidth15,
|
|
10195
|
-
getAxisTitleOffset as getAxisTitleOffset2,
|
|
10196
10207
|
HPAD_COMPACT_FRACTION,
|
|
10197
10208
|
HPAD_COMPACT_MIN,
|
|
10198
10209
|
LABEL_GAP_COMPACT,
|
|
@@ -10202,7 +10213,6 @@ import {
|
|
|
10202
10213
|
MAX_LEFT_LABEL_FRACTION_MEDIUM,
|
|
10203
10214
|
MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX,
|
|
10204
10215
|
NARROW_VIEWPORT_MAX,
|
|
10205
|
-
TICK_LABEL_OFFSET as TICK_LABEL_OFFSET2,
|
|
10206
10216
|
TOP_PAD_EXTRA_NARROW
|
|
10207
10217
|
} from "@opendata-ai/openchart-core";
|
|
10208
10218
|
|
|
@@ -10299,19 +10309,23 @@ function measureLegendWrap(entries, maxWidth, labelStyle, maxRows, entryGap = EN
|
|
|
10299
10309
|
|
|
10300
10310
|
// src/layout/metrics.ts
|
|
10301
10311
|
import { estimateTextWidth as estimateTextWidth14 } from "@opendata-ai/openchart-core";
|
|
10302
|
-
var
|
|
10303
|
-
var
|
|
10312
|
+
var DEFAULT_LABEL_FONT_SIZE = 10;
|
|
10313
|
+
var DEFAULT_VALUE_FONT_SIZE = 22;
|
|
10304
10314
|
var LABEL_LINE_HEIGHT_RATIO = 1.4;
|
|
10305
10315
|
var VALUE_LINE_HEIGHT_RATIO = 1.15;
|
|
10306
10316
|
var INTER_ROW_GAP = 4;
|
|
10307
10317
|
var TOP_GAP = 16;
|
|
10308
10318
|
var BOTTOM_GAP = 20;
|
|
10319
|
+
var DEFAULT_METRIC_FONT_SIZES = {
|
|
10320
|
+
label: DEFAULT_LABEL_FONT_SIZE,
|
|
10321
|
+
value: DEFAULT_VALUE_FONT_SIZE
|
|
10322
|
+
};
|
|
10309
10323
|
var MIN_BAR_WIDTH2 = 480;
|
|
10310
10324
|
var MIN_CHART_HEIGHT = 150;
|
|
10311
10325
|
var CELL_INNER_PAD = 8;
|
|
10312
|
-
function metricBarHeight() {
|
|
10313
|
-
const labelLine =
|
|
10314
|
-
const valueLine =
|
|
10326
|
+
function metricBarHeight(fonts = DEFAULT_METRIC_FONT_SIZES) {
|
|
10327
|
+
const labelLine = fonts.label * LABEL_LINE_HEIGHT_RATIO;
|
|
10328
|
+
const valueLine = fonts.value * VALUE_LINE_HEIGHT_RATIO;
|
|
10315
10329
|
return TOP_GAP + labelLine + INTER_ROW_GAP + valueLine + BOTTOM_GAP;
|
|
10316
10330
|
}
|
|
10317
10331
|
function valueRunText(metric) {
|
|
@@ -10320,19 +10334,19 @@ function valueRunText(metric) {
|
|
|
10320
10334
|
if (metric.secondary) parts.push(metric.secondary);
|
|
10321
10335
|
return parts.join(" ");
|
|
10322
10336
|
}
|
|
10323
|
-
function computeMetricBar(metrics, metricsTopY, metricsArea, remainingChartHeight, measureText) {
|
|
10337
|
+
function computeMetricBar(metrics, metricsTopY, metricsArea, remainingChartHeight, measureText, fonts = DEFAULT_METRIC_FONT_SIZES) {
|
|
10324
10338
|
if (!metrics || metrics.length === 0) return void 0;
|
|
10325
10339
|
if (metricsArea.width < MIN_BAR_WIDTH2) return void 0;
|
|
10326
10340
|
if (remainingChartHeight < MIN_CHART_HEIGHT) return void 0;
|
|
10327
10341
|
const cellWidth = metricsArea.width / metrics.length;
|
|
10328
10342
|
for (const metric of metrics) {
|
|
10329
10343
|
const text = valueRunText(metric);
|
|
10330
|
-
const measured = measureText ? measureText(text,
|
|
10344
|
+
const measured = measureText ? measureText(text, fonts.value, 510).width : estimateTextWidth14(text, fonts.value, 510);
|
|
10331
10345
|
if (measured > cellWidth - CELL_INNER_PAD) return void 0;
|
|
10332
10346
|
}
|
|
10333
|
-
const labelLine =
|
|
10334
|
-
const labelY = metricsTopY + TOP_GAP +
|
|
10335
|
-
const valueY = metricsTopY + TOP_GAP + labelLine + INTER_ROW_GAP +
|
|
10347
|
+
const labelLine = fonts.label * LABEL_LINE_HEIGHT_RATIO;
|
|
10348
|
+
const labelY = metricsTopY + TOP_GAP + fonts.label;
|
|
10349
|
+
const valueY = metricsTopY + TOP_GAP + labelLine + INTER_ROW_GAP + fonts.value;
|
|
10336
10350
|
const cells = metrics.map((metric, i) => ({
|
|
10337
10351
|
x: metricsArea.x + i * cellWidth,
|
|
10338
10352
|
cellWidth,
|
|
@@ -10343,12 +10357,15 @@ function computeMetricBar(metrics, metricsTopY, metricsArea, remainingChartHeigh
|
|
|
10343
10357
|
}));
|
|
10344
10358
|
return {
|
|
10345
10359
|
y: metricsTopY,
|
|
10346
|
-
height: metricBarHeight(),
|
|
10360
|
+
height: metricBarHeight(fonts),
|
|
10347
10361
|
cells
|
|
10348
10362
|
};
|
|
10349
10363
|
}
|
|
10350
10364
|
|
|
10351
10365
|
// src/layout/dimensions.ts
|
|
10366
|
+
function metricFonts(theme) {
|
|
10367
|
+
return { label: theme.fonts.sizes.metricLabel, value: theme.fonts.sizes.metricValue };
|
|
10368
|
+
}
|
|
10352
10369
|
function chromeToInput(chrome) {
|
|
10353
10370
|
return {
|
|
10354
10371
|
eyebrow: chrome.eyebrow,
|
|
@@ -10480,7 +10497,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10480
10497
|
const topAxisGap = isRadial && chrome.topHeight === 0 ? 0 : axisMargin + inlineTickOverhang;
|
|
10481
10498
|
const topPad = width < NARROW_VIEWPORT_MAX ? padding + TOP_PAD_EXTRA_NARROW : padding;
|
|
10482
10499
|
const wantsMetrics = !!spec.metrics && spec.metrics.length > 0 && chromeMode !== "hidden";
|
|
10483
|
-
const tentativeMetricsHeight = wantsMetrics ? metricBarHeight() : 0;
|
|
10500
|
+
const tentativeMetricsHeight = wantsMetrics ? metricBarHeight(metricFonts(theme)) : 0;
|
|
10484
10501
|
const margins = {
|
|
10485
10502
|
top: topPad + chrome.topHeight + tentativeMetricsHeight,
|
|
10486
10503
|
right: hPad + (isRadial ? hPad : axisMargin),
|
|
@@ -10653,10 +10670,10 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10653
10670
|
theme.fonts.weights.normal
|
|
10654
10671
|
);
|
|
10655
10672
|
}
|
|
10656
|
-
const
|
|
10657
|
-
const
|
|
10658
|
-
const halfGlyph = Math.ceil(
|
|
10659
|
-
const rotatedLabelMargin =
|
|
10673
|
+
const titleFontSize = theme.fonts.sizes.body;
|
|
10674
|
+
const offset = axisTitleOffset(estTickLabelWidth, titleFontSize, width);
|
|
10675
|
+
const halfGlyph = Math.ceil(titleFontSize / 2);
|
|
10676
|
+
const rotatedLabelMargin = offset + halfGlyph + (width < BREAKPOINT_COMPACT_MAX2 ? 0 : AXIS_TITLE_TRAILING_PAD2);
|
|
10660
10677
|
margins.left = Math.max(margins.left, hPad + rotatedLabelMargin);
|
|
10661
10678
|
}
|
|
10662
10679
|
if (options.rightAxisReserve && options.rightAxisReserve > 0) {
|
|
@@ -10714,7 +10731,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10714
10731
|
fallbackMetricsTopY,
|
|
10715
10732
|
fallbackMetricsArea,
|
|
10716
10733
|
chartArea.height,
|
|
10717
|
-
options.measureText
|
|
10734
|
+
options.measureText,
|
|
10735
|
+
theme
|
|
10718
10736
|
) : void 0;
|
|
10719
10737
|
if (wantsMetrics && !fallbackMetrics) {
|
|
10720
10738
|
margins.top = Math.max(0, margins.top - tentativeMetricsHeight);
|
|
@@ -10737,7 +10755,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10737
10755
|
}
|
|
10738
10756
|
const metricsTopY = topPad + chrome.topHeight;
|
|
10739
10757
|
const metricsArea = { x: hPad, width: Math.max(0, width - hPad * 2) };
|
|
10740
|
-
const resolvedMetrics = wantsMetrics ? resolveMetrics(spec, metricsTopY, metricsArea, chartArea.height, options.measureText) : void 0;
|
|
10758
|
+
const resolvedMetrics = wantsMetrics ? resolveMetrics(spec, metricsTopY, metricsArea, chartArea.height, options.measureText, theme) : void 0;
|
|
10741
10759
|
if (wantsMetrics && !resolvedMetrics) {
|
|
10742
10760
|
margins.top = Math.max(0, margins.top - tentativeMetricsHeight);
|
|
10743
10761
|
chartArea = {
|
|
@@ -10756,13 +10774,14 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10756
10774
|
xAxisHeight
|
|
10757
10775
|
};
|
|
10758
10776
|
}
|
|
10759
|
-
function resolveMetrics(spec, metricsTopY, metricsArea, remainingChartHeight, measureText) {
|
|
10777
|
+
function resolveMetrics(spec, metricsTopY, metricsArea, remainingChartHeight, measureText, theme) {
|
|
10760
10778
|
return computeMetricBar(
|
|
10761
10779
|
spec.metrics,
|
|
10762
10780
|
metricsTopY,
|
|
10763
10781
|
metricsArea,
|
|
10764
10782
|
remainingChartHeight,
|
|
10765
|
-
measureText
|
|
10783
|
+
measureText,
|
|
10784
|
+
metricFonts(theme)
|
|
10766
10785
|
);
|
|
10767
10786
|
}
|
|
10768
10787
|
|
|
@@ -14265,17 +14284,18 @@ function compileChart(spec, options) {
|
|
|
14265
14284
|
const chartArea = dims.chartArea;
|
|
14266
14285
|
const legendArea = { ...chartArea };
|
|
14267
14286
|
if ("entries" in legendLayout && legendLayout.entries.length > 0) {
|
|
14287
|
+
const legendInnerWidth = options.width - theme.spacing.padding - chartArea.x;
|
|
14268
14288
|
const gap = legendGap(options.width);
|
|
14269
14289
|
switch (legendLayout.position) {
|
|
14270
14290
|
case "top":
|
|
14271
|
-
legendArea.x =
|
|
14272
|
-
legendArea.width =
|
|
14291
|
+
legendArea.x = chartArea.x;
|
|
14292
|
+
legendArea.width = legendInnerWidth;
|
|
14273
14293
|
legendArea.y -= legendLayout.bounds.height + gap;
|
|
14274
14294
|
legendArea.height += legendLayout.bounds.height + gap;
|
|
14275
14295
|
break;
|
|
14276
14296
|
case "bottom":
|
|
14277
|
-
legendArea.x =
|
|
14278
|
-
legendArea.width =
|
|
14297
|
+
legendArea.x = chartArea.x;
|
|
14298
|
+
legendArea.width = legendInnerWidth;
|
|
14279
14299
|
legendArea.height += legendLayout.bounds.height + gap + dims.xAxisHeight;
|
|
14280
14300
|
break;
|
|
14281
14301
|
case "right":
|