csat-chart.js 1.2.0 → 1.5.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/CHANGELOG.md +255 -0
- package/README.md +189 -16
- package/dist/csat-chart.cjs +931 -304
- package/dist/csat-chart.cjs.map +1 -1
- package/dist/csat-chart.d.cts +235 -7
- package/dist/csat-chart.d.mts +235 -7
- package/dist/csat-chart.mjs +926 -304
- package/dist/csat-chart.mjs.map +1 -1
- package/dist/csat-chart.umd.min.js +1 -1
- package/dist/csat-chart.umd.min.js.map +1 -1
- package/package.json +66 -66
package/dist/csat-chart.cjs
CHANGED
|
@@ -31,6 +31,7 @@ __export(src_exports, {
|
|
|
31
31
|
MONTH_LABELS_EN: () => MONTH_LABELS_EN2,
|
|
32
32
|
MONTH_LABELS_NUM: () => MONTH_LABELS_NUM2,
|
|
33
33
|
autoRange: () => autoRange,
|
|
34
|
+
createAdAsData: () => createAdAsData,
|
|
34
35
|
createDefaultAbsBarData: () => createDefaultAbsBarData,
|
|
35
36
|
createDefaultCategoryDotData: () => createDefaultCategoryDotData,
|
|
36
37
|
createDefaultClimateData: () => createDefaultClimateData,
|
|
@@ -38,6 +39,7 @@ __export(src_exports, {
|
|
|
38
39
|
createDefaultDataTableData: () => createDefaultDataTableData,
|
|
39
40
|
createDefaultDeviationAData: () => createDefaultDeviationAData,
|
|
40
41
|
createDefaultDeviationBData: () => createDefaultDeviationBData,
|
|
42
|
+
createDefaultEconPlaneData: () => createDefaultEconPlaneData,
|
|
41
43
|
createDefaultGraphOptions: () => createDefaultGraphOptions,
|
|
42
44
|
createDefaultHythergraphData: () => createDefaultHythergraphData,
|
|
43
45
|
createDefaultLineData: () => createDefaultLineData,
|
|
@@ -48,6 +50,8 @@ __export(src_exports, {
|
|
|
48
50
|
createDefaultStackedData: () => createDefaultStackedData,
|
|
49
51
|
createDefaultTernaryData: () => createDefaultTernaryData,
|
|
50
52
|
createDefaultTreemapData: () => createDefaultTreemapData,
|
|
53
|
+
createPointShiftData: () => createPointShiftData,
|
|
54
|
+
createSupplyDemandData: () => createSupplyDemandData,
|
|
51
55
|
ensureFonts: () => ensureFonts,
|
|
52
56
|
installRoundRectPolyfill: () => installRoundRectPolyfill,
|
|
53
57
|
isCsatChartType: () => isCsatChartType,
|
|
@@ -59,6 +63,7 @@ __export(src_exports, {
|
|
|
59
63
|
renderDataTable: () => renderDataTable,
|
|
60
64
|
renderDeviationAGraph: () => renderDeviationAGraph,
|
|
61
65
|
renderDeviationBGraph: () => renderDeviationBGraph,
|
|
66
|
+
renderEconPlane: () => renderEconPlane,
|
|
62
67
|
renderHythergraph: () => renderHythergraph,
|
|
63
68
|
renderLineGraph: () => renderLineGraph,
|
|
64
69
|
renderMatrixTable: () => renderMatrixTable,
|
|
@@ -72,13 +77,19 @@ __export(src_exports, {
|
|
|
72
77
|
module.exports = __toCommonJS(src_exports);
|
|
73
78
|
|
|
74
79
|
// src/core/canvas/renderer.ts
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return
|
|
80
|
+
var DEFAULT_SERIF_STACK = "'Noto Serif KR', 'NanumMyeongjo', serif";
|
|
81
|
+
var DEFAULT_SANS_STACK = "'Noto Sans KR', sans-serif";
|
|
82
|
+
var CUSTOM_FALLBACK_STACK = "'Noto Serif KR', serif";
|
|
83
|
+
function fontStackOf(fonts, role) {
|
|
84
|
+
if (role === "custom") return fonts.customFont || CUSTOM_FALLBACK_STACK;
|
|
85
|
+
if (role === "sans") return fonts.fontStack?.sans || DEFAULT_SANS_STACK;
|
|
86
|
+
return fonts.fontStack?.serif || DEFAULT_SERIF_STACK;
|
|
87
|
+
}
|
|
88
|
+
function sansFont(fonts) {
|
|
89
|
+
return fontStackOf(fonts, "sans");
|
|
90
|
+
}
|
|
91
|
+
function getFont(size, fonts, weight = "normal", role = fonts.fontFamily ?? "serif") {
|
|
92
|
+
return `${weight} ${size}px ${fontStackOf(fonts, role)}`;
|
|
82
93
|
}
|
|
83
94
|
function clearCanvas(ctx, w, h) {
|
|
84
95
|
ctx.fillStyle = "#fff";
|
|
@@ -126,7 +137,6 @@ function autoRange(values, maxTicks = 8) {
|
|
|
126
137
|
}
|
|
127
138
|
|
|
128
139
|
// src/core/canvas/labels.ts
|
|
129
|
-
var LABEL_FONT = "'Noto Sans KR', sans-serif";
|
|
130
140
|
var MIN_SHRINK = 0.6;
|
|
131
141
|
function fitFontSize(ctx, text, fontSize, available, makeFont) {
|
|
132
142
|
if (available <= 0) return fontSize;
|
|
@@ -136,11 +146,12 @@ function fitFontSize(ctx, text, fontSize, available, makeFont) {
|
|
|
136
146
|
const scaled = Math.max(fontSize * MIN_SHRINK, fontSize * (available / w));
|
|
137
147
|
return Math.floor(scaled);
|
|
138
148
|
}
|
|
139
|
-
function drawTitle({ ctx, plotX, plotW, title, fontSize, canvasWidth }) {
|
|
149
|
+
function drawTitle({ ctx, plotX, plotW, title, fontSize, fonts, canvasWidth }) {
|
|
140
150
|
if (!title) return;
|
|
141
151
|
ctx.save();
|
|
142
152
|
ctx.fillStyle = "#000";
|
|
143
|
-
const
|
|
153
|
+
const labelFont = sansFont(fonts);
|
|
154
|
+
const makeFont = (size2) => `bold ${size2}px ${labelFont}`;
|
|
144
155
|
const centerX = plotX + plotW / 2;
|
|
145
156
|
const available = canvasWidth != null ? Math.max(0, Math.min(centerX, canvasWidth - centerX) * 2 - 12) : plotW;
|
|
146
157
|
const size = fitFontSize(ctx, title, fontSize, available, makeFont);
|
|
@@ -161,9 +172,11 @@ function drawSourceAndFootnote({
|
|
|
161
172
|
sourceInline,
|
|
162
173
|
footnotes,
|
|
163
174
|
fontSize,
|
|
175
|
+
fonts,
|
|
164
176
|
canvasWidth
|
|
165
177
|
}) {
|
|
166
178
|
ctx.save();
|
|
179
|
+
const labelFont = sansFont(fonts);
|
|
167
180
|
let leftX = plotX;
|
|
168
181
|
let rightX = plotX + plotW;
|
|
169
182
|
if (canvasWidth != null) {
|
|
@@ -178,7 +191,7 @@ function drawSourceAndFootnote({
|
|
|
178
191
|
let y = height - 6 - totalFootnoteH - (sourceBelow ? sourceH : 0);
|
|
179
192
|
const rightEdge = canvasWidth != null ? rightX : plotX + plotW;
|
|
180
193
|
const available = Math.max(0, rightEdge - leftX);
|
|
181
|
-
const sourceFont = (size) => `bold ${size}px ${
|
|
194
|
+
const sourceFont = (size) => `bold ${size}px ${labelFont}`;
|
|
182
195
|
if (source && !sourceBelow && !inlineSource) {
|
|
183
196
|
ctx.fillStyle = "#555";
|
|
184
197
|
const size = fitFontSize(ctx, source, fontSize, available, sourceFont);
|
|
@@ -195,7 +208,7 @@ function drawSourceAndFootnote({
|
|
|
195
208
|
for (let i = 0; i < filtered.length; i++) {
|
|
196
209
|
const text = "* " + filtered[i];
|
|
197
210
|
ctx.fillStyle = "#555";
|
|
198
|
-
const makeFont = (size2) => `${size2}px ${
|
|
211
|
+
const makeFont = (size2) => `${size2}px ${labelFont}`;
|
|
199
212
|
const size = fitFontSize(ctx, text, fontSize * 0.9, footnoteAvailable, makeFont);
|
|
200
213
|
ctx.font = makeFont(size);
|
|
201
214
|
ctx.textAlign = "left";
|
|
@@ -213,7 +226,7 @@ function drawSourceAndFootnote({
|
|
|
213
226
|
ctx.fillText(source, rightX, lastFootnoteY, inlineSourceW > 0 ? inlineSourceW : void 0);
|
|
214
227
|
}
|
|
215
228
|
if (sourceBelow) {
|
|
216
|
-
const makeFont = (size) => `bold ${size}px ${
|
|
229
|
+
const makeFont = (size) => `bold ${size}px ${labelFont}`;
|
|
217
230
|
const half = available > 0 ? available / 2 : 0;
|
|
218
231
|
ctx.fillStyle = "#555";
|
|
219
232
|
ctx.textBaseline = "bottom";
|
|
@@ -491,8 +504,7 @@ function drawYAxis({
|
|
|
491
504
|
step,
|
|
492
505
|
label,
|
|
493
506
|
side,
|
|
494
|
-
|
|
495
|
-
customFont,
|
|
507
|
+
fonts,
|
|
496
508
|
tickFontSize,
|
|
497
509
|
labelFontSize,
|
|
498
510
|
drawGrid = false
|
|
@@ -506,7 +518,7 @@ function drawYAxis({
|
|
|
506
518
|
ctx.lineTo(x, plot.y + plot.h);
|
|
507
519
|
ctx.stroke();
|
|
508
520
|
ctx.fillStyle = "#000";
|
|
509
|
-
ctx.font = getFont(tickFontSize,
|
|
521
|
+
ctx.font = getFont(tickFontSize, fonts, "bold");
|
|
510
522
|
ctx.textBaseline = "middle";
|
|
511
523
|
ctx.textAlign = side === "left" ? "right" : "left";
|
|
512
524
|
const ticks = [];
|
|
@@ -548,7 +560,7 @@ function drawYAxis({
|
|
|
548
560
|
}
|
|
549
561
|
if (label) {
|
|
550
562
|
ctx.save();
|
|
551
|
-
const makeFont = (size) => getFont(size,
|
|
563
|
+
const makeFont = (size) => getFont(size, fonts, "bold");
|
|
552
564
|
ctx.fillStyle = "#000";
|
|
553
565
|
ctx.textBaseline = "bottom";
|
|
554
566
|
ctx.textAlign = side === "left" ? "right" : "left";
|
|
@@ -566,8 +578,7 @@ function drawXAxis({
|
|
|
566
578
|
height,
|
|
567
579
|
labels,
|
|
568
580
|
indices,
|
|
569
|
-
|
|
570
|
-
customFont,
|
|
581
|
+
fonts,
|
|
571
582
|
tickFontSize
|
|
572
583
|
}) {
|
|
573
584
|
const plot = plotArea(padding, width, height);
|
|
@@ -579,7 +590,7 @@ function drawXAxis({
|
|
|
579
590
|
ctx.lineTo(plot.x + plot.w, y);
|
|
580
591
|
ctx.stroke();
|
|
581
592
|
ctx.fillStyle = "#000";
|
|
582
|
-
ctx.font = getFont(tickFontSize,
|
|
593
|
+
ctx.font = getFont(tickFontSize, fonts, "bold");
|
|
583
594
|
ctx.textAlign = "center";
|
|
584
595
|
ctx.textBaseline = "top";
|
|
585
596
|
const showIndices = indices ?? labels.map((_, i) => i);
|
|
@@ -686,14 +697,17 @@ function drawInsideIcon(ctx, item, x, cy, size) {
|
|
|
686
697
|
ctx.lineTo(x + w, cy);
|
|
687
698
|
ctx.stroke();
|
|
688
699
|
ctx.setLineDash([]);
|
|
689
|
-
ctx.fillStyle = item.fillStyle;
|
|
700
|
+
ctx.fillStyle = item.hollow ? "#fff" : item.fillStyle;
|
|
701
|
+
if (item.hollow) ctx.lineWidth = 1.5;
|
|
690
702
|
const r = size * 0.3;
|
|
691
703
|
if (item.marker === "square") {
|
|
692
704
|
ctx.fillRect(x + w / 2 - r, cy - r, r * 2, r * 2);
|
|
705
|
+
if (item.hollow) ctx.strokeRect(x + w / 2 - r, cy - r, r * 2, r * 2);
|
|
693
706
|
} else {
|
|
694
707
|
ctx.beginPath();
|
|
695
708
|
ctx.arc(x + w / 2, cy, r, 0, Math.PI * 2);
|
|
696
709
|
ctx.fill();
|
|
710
|
+
if (item.hollow) ctx.stroke();
|
|
697
711
|
}
|
|
698
712
|
return;
|
|
699
713
|
}
|
|
@@ -704,14 +718,13 @@ function drawInsideIcon(ctx, item, x, cy, size) {
|
|
|
704
718
|
ctx.strokeRect(x, cy - size / 2, size, size);
|
|
705
719
|
}
|
|
706
720
|
var LINE_ICON_SIZE = 36;
|
|
707
|
-
var LEGEND_FONT = "'Noto Sans KR', sans-serif";
|
|
708
721
|
var ICON_GAP = 10;
|
|
709
722
|
var BOX_PADDING = 12;
|
|
710
723
|
var ITEM_SPACING = 30;
|
|
711
724
|
var ROW_GAP = 6;
|
|
712
|
-
function measureLegendWidth(ctx, labels, fontSize, iconType = "rect") {
|
|
725
|
+
function measureLegendWidth(ctx, labels, fontSize, fonts, iconType = "rect") {
|
|
713
726
|
ctx.save();
|
|
714
|
-
ctx.font = `bold ${fontSize}px ${
|
|
727
|
+
ctx.font = `bold ${fontSize}px ${sansFont(fonts)}`;
|
|
715
728
|
const iconSize = iconType === "line" ? LINE_ICON_SIZE : 16;
|
|
716
729
|
const iconGap = 10;
|
|
717
730
|
const padding = 12;
|
|
@@ -719,11 +732,12 @@ function measureLegendWidth(ctx, labels, fontSize, iconType = "rect") {
|
|
|
719
732
|
ctx.restore();
|
|
720
733
|
return maxW + padding * 2 + 30;
|
|
721
734
|
}
|
|
722
|
-
function layoutBottomLegend(ctx, labels, iconWidths, fontSize, boxW, opts = {}) {
|
|
735
|
+
function layoutBottomLegend(ctx, labels, iconWidths, fontSize, boxW, fonts, opts = {}) {
|
|
723
736
|
const iconGap = opts.iconGap ?? ICON_GAP;
|
|
724
737
|
const padding = opts.padding ?? BOX_PADDING;
|
|
725
738
|
const spacing = opts.spacing ?? ITEM_SPACING;
|
|
726
|
-
const
|
|
739
|
+
const legendFont = sansFont(fonts);
|
|
740
|
+
const fontOf = (fs2) => opts.font ? withFontSize(opts.font, fs2) : `bold ${fs2}px ${legendFont}`;
|
|
727
741
|
const inner = Math.max(1, boxW - padding * 2);
|
|
728
742
|
ctx.save();
|
|
729
743
|
const widthsAt = (fs2) => {
|
|
@@ -760,11 +774,11 @@ function layoutBottomLegend(ctx, labels, iconWidths, fontSize, boxW, opts = {})
|
|
|
760
774
|
boxH: rows.length * lineHeight + (rows.length - 1) * ROW_GAP + padding * 2
|
|
761
775
|
};
|
|
762
776
|
}
|
|
763
|
-
function measureBottomLegend(ctx, labels, fontSize, plotW, iconType = "rect", bottomOffset = 50) {
|
|
777
|
+
function measureBottomLegend(ctx, labels, fontSize, plotW, fonts, iconType = "rect", bottomOffset = 50) {
|
|
764
778
|
if (labels.length === 0) return 0;
|
|
765
779
|
const sizeOf = (t) => t === "line" ? LINE_ICON_SIZE : 16;
|
|
766
780
|
const iconWidths = labels.map((_, i) => sizeOf(Array.isArray(iconType) ? iconType[i] ?? "rect" : iconType));
|
|
767
|
-
const { boxH } = layoutBottomLegend(ctx, labels, iconWidths, fontSize, plotW);
|
|
781
|
+
const { boxH } = layoutBottomLegend(ctx, labels, iconWidths, fontSize, plotW, fonts);
|
|
768
782
|
return bottomOffset + boxH + 2;
|
|
769
783
|
}
|
|
770
784
|
function drawLegend({
|
|
@@ -778,12 +792,14 @@ function drawLegend({
|
|
|
778
792
|
canvasW,
|
|
779
793
|
canvasH,
|
|
780
794
|
fontSize,
|
|
795
|
+
fonts,
|
|
781
796
|
bottomOffset = 50,
|
|
782
797
|
rightGap = 20
|
|
783
798
|
}) {
|
|
784
799
|
if (items.length === 0) return 0;
|
|
800
|
+
const legendFont = sansFont(fonts);
|
|
785
801
|
ctx.save();
|
|
786
|
-
ctx.font = `bold ${fontSize}px ${
|
|
802
|
+
ctx.font = `bold ${fontSize}px ${legendFont}`;
|
|
787
803
|
const iconGap = ICON_GAP;
|
|
788
804
|
const padding = BOX_PADDING;
|
|
789
805
|
const lineHeight = fontSize + 8;
|
|
@@ -796,7 +812,8 @@ function drawLegend({
|
|
|
796
812
|
items.map((i) => i.label),
|
|
797
813
|
items.map(iconWidthOf),
|
|
798
814
|
fontSize,
|
|
799
|
-
boxW
|
|
815
|
+
boxW,
|
|
816
|
+
fonts
|
|
800
817
|
);
|
|
801
818
|
const boxH = layout.boxH;
|
|
802
819
|
const boxX = plotX;
|
|
@@ -817,7 +834,7 @@ function drawLegend({
|
|
|
817
834
|
const item = items[index];
|
|
818
835
|
const iSize = iconWidthOf(item);
|
|
819
836
|
drawIcon(ctx, item, cx, cy, iSize);
|
|
820
|
-
ctx.font = `bold ${layout.fontSize}px ${
|
|
837
|
+
ctx.font = `bold ${layout.fontSize}px ${legendFont}`;
|
|
821
838
|
ctx.fillStyle = "#000";
|
|
822
839
|
ctx.textAlign = "left";
|
|
823
840
|
ctx.textBaseline = "middle";
|
|
@@ -846,7 +863,7 @@ function drawLegend({
|
|
|
846
863
|
const ix = boxX + padding;
|
|
847
864
|
const iSize = iconWidthOf(item);
|
|
848
865
|
drawIcon(ctx, item, ix, cy, iSize);
|
|
849
|
-
ctx.font = `bold ${fontSize}px ${
|
|
866
|
+
ctx.font = `bold ${fontSize}px ${legendFont}`;
|
|
850
867
|
ctx.fillStyle = "#000";
|
|
851
868
|
ctx.textAlign = "left";
|
|
852
869
|
ctx.textBaseline = "middle";
|
|
@@ -1047,10 +1064,8 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1047
1064
|
clearCanvas(ctx, w, h);
|
|
1048
1065
|
const showLegend = options.showLegend;
|
|
1049
1066
|
const legendPos = options.legendPosition;
|
|
1050
|
-
const legendW = showLegend && legendPos === "right" && !data.insideLegend ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5) : 0;
|
|
1067
|
+
const legendW = showLegend && legendPos === "right" && !data.insideLegend ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5, options) : 0;
|
|
1051
1068
|
const isVertical = data.barDirection === "vertical";
|
|
1052
|
-
const font = options.fontFamily;
|
|
1053
|
-
const customFont = options.customFont;
|
|
1054
1069
|
const n = data.categories.length;
|
|
1055
1070
|
const sCount = data.seriesLabels.length;
|
|
1056
1071
|
const hasGroups = !isVertical && !!data.groups && data.groups.length > 0;
|
|
@@ -1059,18 +1074,18 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1059
1074
|
let unitW = 0;
|
|
1060
1075
|
if (hasGroups || data.unitAdjacent || data.insideLegend) {
|
|
1061
1076
|
ctx.save();
|
|
1062
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
1077
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
1063
1078
|
if (hasGroups) {
|
|
1064
1079
|
groupLabelW = widestLabel(ctx, data.groups.map((g) => g.label));
|
|
1065
1080
|
catLabelW = widestLabel(ctx, data.categories.map((c) => c.label));
|
|
1066
1081
|
}
|
|
1067
|
-
ctx.font = getFont(options.fontSize.axisLabel,
|
|
1082
|
+
ctx.font = getFont(options.fontSize.axisLabel, options, "bold");
|
|
1068
1083
|
unitW = ctx.measureText(data.unit).width;
|
|
1069
1084
|
ctx.restore();
|
|
1070
1085
|
}
|
|
1071
1086
|
const padRight = isVertical ? 60 + legendW : data.unitAdjacent ? 40 + unitW : 160 + legendW;
|
|
1072
1087
|
const padLeft = isVertical ? 130 : hasGroups ? 20 + groupLabelW + 14 + catLabelW + 12 : 100;
|
|
1073
|
-
const legendReserve = showLegend && legendPos === "bottom" && !data.insideLegend ? measureBottomLegend(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5, w - padLeft - padRight) : 0;
|
|
1088
|
+
const legendReserve = showLegend && legendPos === "bottom" && !data.insideLegend ? measureBottomLegend(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5, w - padLeft - padRight, options) : 0;
|
|
1074
1089
|
const padding = {
|
|
1075
1090
|
top: options.title ? 100 : 50,
|
|
1076
1091
|
right: padRight,
|
|
@@ -1134,8 +1149,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1134
1149
|
step: axis.step,
|
|
1135
1150
|
label: data.unit,
|
|
1136
1151
|
side: "left",
|
|
1137
|
-
|
|
1138
|
-
customFont,
|
|
1152
|
+
fonts: options,
|
|
1139
1153
|
tickFontSize: options.fontSize.tick,
|
|
1140
1154
|
labelFontSize: options.fontSize.axisLabel,
|
|
1141
1155
|
drawGrid: true
|
|
@@ -1165,7 +1179,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1165
1179
|
ctx.strokeRect(cx - barW / 2, y, barW, barH);
|
|
1166
1180
|
if (options.showDataLabels && barH > options.fontSize.dataLabel) {
|
|
1167
1181
|
ctx.fillStyle = isLightFill(s) ? "#000" : "#fff";
|
|
1168
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
1182
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options, "bold");
|
|
1169
1183
|
ctx.textAlign = "center";
|
|
1170
1184
|
ctx.textBaseline = "middle";
|
|
1171
1185
|
ctx.fillText(String(val), cx, y + barH / 2);
|
|
@@ -1191,7 +1205,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1191
1205
|
ctx.strokeRect(bx, by, barW, barH);
|
|
1192
1206
|
if (options.showDataLabels && barH > options.fontSize.dataLabel) {
|
|
1193
1207
|
ctx.fillStyle = isLightFill(s) ? "#000" : "#fff";
|
|
1194
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
1208
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options, "bold");
|
|
1195
1209
|
ctx.textAlign = "center";
|
|
1196
1210
|
ctx.textBaseline = "bottom";
|
|
1197
1211
|
ctx.fillText(String(val), bx + barW / 2, by - 4);
|
|
@@ -1211,7 +1225,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1211
1225
|
}
|
|
1212
1226
|
const labelY = data.categoryLabelAtBaseline ? baseY + 6 : plotY + plotH + 12;
|
|
1213
1227
|
ctx.fillStyle = "#000";
|
|
1214
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
1228
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
1215
1229
|
ctx.textAlign = "center";
|
|
1216
1230
|
ctx.textBaseline = "top";
|
|
1217
1231
|
const catStride = labelStride(catArea, widestLabel(ctx, data.categories.map((c) => c.label)));
|
|
@@ -1235,7 +1249,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1235
1249
|
if (ticks.length === 0 || Math.abs(ticks[ticks.length - 1] - axis.max) > 1e-9) {
|
|
1236
1250
|
ticks.push(axis.max);
|
|
1237
1251
|
}
|
|
1238
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
1252
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
1239
1253
|
ctx.fillStyle = "#000";
|
|
1240
1254
|
ctx.textAlign = "center";
|
|
1241
1255
|
ctx.textBaseline = "top";
|
|
@@ -1263,7 +1277,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1263
1277
|
ctx.restore();
|
|
1264
1278
|
}
|
|
1265
1279
|
}
|
|
1266
|
-
ctx.font = getFont(options.fontSize.axisLabel,
|
|
1280
|
+
ctx.font = getFont(options.fontSize.axisLabel, options, "bold");
|
|
1267
1281
|
ctx.fillStyle = "#000";
|
|
1268
1282
|
ctx.textAlign = "left";
|
|
1269
1283
|
ctx.textBaseline = "top";
|
|
@@ -1303,7 +1317,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1303
1317
|
ctx.strokeRect(bx, cy - barH / 2, bw, barH);
|
|
1304
1318
|
if (options.showDataLabels && bw > options.fontSize.dataLabel * 2) {
|
|
1305
1319
|
ctx.fillStyle = isLightFill(s) ? "#000" : "#fff";
|
|
1306
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
1320
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options, "bold");
|
|
1307
1321
|
ctx.textAlign = "center";
|
|
1308
1322
|
ctx.textBaseline = "middle";
|
|
1309
1323
|
ctx.fillText(String(val), bx + bw / 2, cy);
|
|
@@ -1329,7 +1343,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1329
1343
|
ctx.strokeRect(bx, by, bw, barH);
|
|
1330
1344
|
if (options.showDataLabels && bw > options.fontSize.dataLabel * 2) {
|
|
1331
1345
|
ctx.fillStyle = isLightFill(s) ? "#000" : "#fff";
|
|
1332
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
1346
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options, "bold");
|
|
1333
1347
|
ctx.textAlign = "left";
|
|
1334
1348
|
ctx.textBaseline = "middle";
|
|
1335
1349
|
ctx.fillText(String(val), bx + bw + 4, by + barH / 2);
|
|
@@ -1341,7 +1355,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1341
1355
|
const catLabelX = hasGroups ? plotX - 12 : plotX - 10;
|
|
1342
1356
|
for (let c = 0; c < n; c++) {
|
|
1343
1357
|
ctx.fillStyle = "#000";
|
|
1344
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
1358
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
1345
1359
|
ctx.textAlign = "right";
|
|
1346
1360
|
ctx.textBaseline = "middle";
|
|
1347
1361
|
ctx.fillText(data.categories[c].label, catLabelX, centerY[c]);
|
|
@@ -1355,7 +1369,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1355
1369
|
}
|
|
1356
1370
|
}
|
|
1357
1371
|
}
|
|
1358
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
1372
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
1359
1373
|
if (showLegend && data.insideLegend) {
|
|
1360
1374
|
drawInsideLegend({
|
|
1361
1375
|
ctx,
|
|
@@ -1372,7 +1386,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1372
1386
|
canvasW: w,
|
|
1373
1387
|
canvasH: h,
|
|
1374
1388
|
fontSize: options.fontSize.dataLabel * 0.9,
|
|
1375
|
-
font: getFont(options.fontSize.dataLabel * 0.9,
|
|
1389
|
+
font: getFont(options.fontSize.dataLabel * 0.9, options, "bold"),
|
|
1376
1390
|
avoid: barRects
|
|
1377
1391
|
});
|
|
1378
1392
|
} else if (showLegend) {
|
|
@@ -1385,6 +1399,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1385
1399
|
}));
|
|
1386
1400
|
drawLegend({
|
|
1387
1401
|
ctx,
|
|
1402
|
+
fonts: options,
|
|
1388
1403
|
items,
|
|
1389
1404
|
position: legendPos,
|
|
1390
1405
|
plotX,
|
|
@@ -1396,7 +1411,7 @@ function renderAbsBarGraph(ctx, w, h, data, options) {
|
|
|
1396
1411
|
fontSize: options.fontSize.dataLabel * 0.85 + 5
|
|
1397
1412
|
});
|
|
1398
1413
|
}
|
|
1399
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
1414
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
1400
1415
|
}
|
|
1401
1416
|
function formatTick2(val) {
|
|
1402
1417
|
if (Number.isInteger(val)) return val.toString();
|
|
@@ -1411,6 +1426,7 @@ function createDefaultGraphOptions() {
|
|
|
1411
1426
|
footnotes: [""],
|
|
1412
1427
|
fontFamily: "serif",
|
|
1413
1428
|
customFont: "",
|
|
1429
|
+
fontStack: {},
|
|
1414
1430
|
fontSize: {
|
|
1415
1431
|
title: 36,
|
|
1416
1432
|
axisLabel: 28,
|
|
@@ -1593,6 +1609,60 @@ function createDefaultCubeData() {
|
|
|
1593
1609
|
};
|
|
1594
1610
|
}
|
|
1595
1611
|
|
|
1612
|
+
// src/core/types/econplane.ts
|
|
1613
|
+
function createSupplyDemandData() {
|
|
1614
|
+
return {
|
|
1615
|
+
quadrants: "first",
|
|
1616
|
+
xAxis: { label: "\uC218\uB7C9(kg)", min: 0, max: 9, ticks: [1, 2, 3, 4, 5, 6, 7, 8], broken: false },
|
|
1617
|
+
yAxis: { label: "\uAC00\uACA9(\uCC9C \uC6D0)", min: 0, max: 9, ticks: [1, 2, 3, 4, 5, 6, 7, 8], broken: false },
|
|
1618
|
+
grid: true,
|
|
1619
|
+
dash: "dashed",
|
|
1620
|
+
lines: [
|
|
1621
|
+
{ label: "\uC218\uC694", from: { x: 0, y: 8 }, to: { x: 8, y: 0 }, labelAt: "to" },
|
|
1622
|
+
{ label: "\uACF5\uAE09", from: { x: 0, y: 0 }, to: { x: 8, y: 8 }, labelAt: "to" }
|
|
1623
|
+
],
|
|
1624
|
+
points: [{ x: 4, y: 4, label: "E", labelPos: "top", guide: "none", dot: true }],
|
|
1625
|
+
arrows: []
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
function createAdAsData() {
|
|
1629
|
+
return {
|
|
1630
|
+
quadrants: "first",
|
|
1631
|
+
xAxis: { label: "\uC2E4\uC9C8 GDP", min: 0, max: 10, ticks: [], broken: false },
|
|
1632
|
+
yAxis: { label: "\uBB3C\uAC00", min: 0, max: 10, ticks: [], broken: false },
|
|
1633
|
+
grid: false,
|
|
1634
|
+
dash: "dashed",
|
|
1635
|
+
lines: [
|
|
1636
|
+
{ label: "\uCD1D\uC218\uC694", from: { x: 1, y: 8.5 }, to: { x: 8.5, y: 1 }, labelAt: "to" },
|
|
1637
|
+
{ label: "\uCD1D\uACF5\uAE09", from: { x: 1, y: 1 }, to: { x: 8.5, y: 8.5 }, labelAt: "to" }
|
|
1638
|
+
],
|
|
1639
|
+
points: [{ x: 4.75, y: 4.75, label: "E", labelPos: "right", guide: "none", dot: true }],
|
|
1640
|
+
arrows: []
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
function createPointShiftData() {
|
|
1644
|
+
return {
|
|
1645
|
+
quadrants: "first",
|
|
1646
|
+
xAxis: { label: "\uC218\uB7C9(\uB9CC \uAC1C)", min: 0, max: 18, ticks: [5, 10, 15], broken: false },
|
|
1647
|
+
yAxis: { label: "\uAC00\uACA9(\uB2EC\uB7EC)", min: 0, max: 18, ticks: [10, 15], broken: false },
|
|
1648
|
+
grid: false,
|
|
1649
|
+
dash: "dashed",
|
|
1650
|
+
lines: [],
|
|
1651
|
+
points: [
|
|
1652
|
+
{ x: 5, y: 10, label: "E", labelPos: "top", guide: "both", dot: true },
|
|
1653
|
+
{ x: 10, y: 10, label: "A", labelPos: "top", guide: "both", dot: true },
|
|
1654
|
+
{ x: 15, y: 15, label: "B", labelPos: "top", guide: "both", dot: true }
|
|
1655
|
+
],
|
|
1656
|
+
arrows: [
|
|
1657
|
+
{ from: { x: 5, y: 10 }, to: { x: 10, y: 10 }, offset: 0, shorten: 10, label: "", labelPos: "top" },
|
|
1658
|
+
{ from: { x: 10, y: 10 }, to: { x: 15, y: 15 }, offset: 0, shorten: 10, label: "", labelPos: "top" }
|
|
1659
|
+
]
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
function createDefaultEconPlaneData() {
|
|
1663
|
+
return createSupplyDemandData();
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1596
1666
|
// src/core/types/radar.ts
|
|
1597
1667
|
function createDefaultRadarData() {
|
|
1598
1668
|
return {
|
|
@@ -1713,15 +1783,13 @@ function drawMarker(ctx, marker, cx, cy, r) {
|
|
|
1713
1783
|
}
|
|
1714
1784
|
function renderCategoryDotGraph(ctx, w, h, data, options) {
|
|
1715
1785
|
clearCanvas(ctx, w, h);
|
|
1716
|
-
const font = options.fontFamily;
|
|
1717
|
-
const customFont = options.customFont;
|
|
1718
1786
|
const n = data.categories.length;
|
|
1719
1787
|
const sCount = data.seriesLabels.length;
|
|
1720
1788
|
const markers = data.seriesMarkers ?? DOT_MARKER_ORDER;
|
|
1721
1789
|
const showLegend = options.showLegend && sCount > 1;
|
|
1722
1790
|
const legendPos = options.legendPosition;
|
|
1723
|
-
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5, "circle") : 0;
|
|
1724
|
-
const legendReserve = showLegend && legendPos === "bottom" ? measureBottomLegend(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85, w - 130 - (60 + legendW), "circle") : 0;
|
|
1791
|
+
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5, options, "circle") : 0;
|
|
1792
|
+
const legendReserve = showLegend && legendPos === "bottom" ? measureBottomLegend(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85, w - 130 - (60 + legendW), options, "circle") : 0;
|
|
1725
1793
|
const padding = {
|
|
1726
1794
|
top: options.title ? 100 : 50,
|
|
1727
1795
|
right: 60 + legendW,
|
|
@@ -1766,8 +1834,7 @@ function renderCategoryDotGraph(ctx, w, h, data, options) {
|
|
|
1766
1834
|
step: axis.step,
|
|
1767
1835
|
label: data.unit,
|
|
1768
1836
|
side: "left",
|
|
1769
|
-
|
|
1770
|
-
customFont,
|
|
1837
|
+
fonts: options,
|
|
1771
1838
|
tickFontSize: options.fontSize.tick,
|
|
1772
1839
|
labelFontSize: options.fontSize.axisLabel,
|
|
1773
1840
|
drawGrid: true
|
|
@@ -1790,7 +1857,7 @@ function renderCategoryDotGraph(ctx, w, h, data, options) {
|
|
|
1790
1857
|
ctx.fillStyle = "#000";
|
|
1791
1858
|
drawMarker(ctx, markers[s % markers.length], cx, cy, data.dotRadius);
|
|
1792
1859
|
if (options.showDataLabels) {
|
|
1793
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
1860
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options, "bold");
|
|
1794
1861
|
ctx.textAlign = "center";
|
|
1795
1862
|
ctx.textBaseline = "bottom";
|
|
1796
1863
|
ctx.fillText(String(val), cx, cy - data.dotRadius - 4);
|
|
@@ -1811,7 +1878,7 @@ function renderCategoryDotGraph(ctx, w, h, data, options) {
|
|
|
1811
1878
|
}
|
|
1812
1879
|
}
|
|
1813
1880
|
ctx.fillStyle = "#000";
|
|
1814
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
1881
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
1815
1882
|
ctx.textAlign = "center";
|
|
1816
1883
|
ctx.textBaseline = "top";
|
|
1817
1884
|
for (let c = 0; c < n; c++) {
|
|
@@ -1819,7 +1886,7 @@ function renderCategoryDotGraph(ctx, w, h, data, options) {
|
|
|
1819
1886
|
ctx.fillText(data.categories[c].label, cx, plotY + plotH + 12);
|
|
1820
1887
|
}
|
|
1821
1888
|
if (options.title) {
|
|
1822
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title });
|
|
1889
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: options.fontSize.title });
|
|
1823
1890
|
}
|
|
1824
1891
|
if (showLegend) {
|
|
1825
1892
|
const items = data.seriesLabels.map((label) => ({
|
|
@@ -1829,6 +1896,7 @@ function renderCategoryDotGraph(ctx, w, h, data, options) {
|
|
|
1829
1896
|
}));
|
|
1830
1897
|
drawLegend({
|
|
1831
1898
|
ctx,
|
|
1899
|
+
fonts: options,
|
|
1832
1900
|
items,
|
|
1833
1901
|
position: legendPos,
|
|
1834
1902
|
plotX,
|
|
@@ -1842,6 +1910,7 @@ function renderCategoryDotGraph(ctx, w, h, data, options) {
|
|
|
1842
1910
|
}
|
|
1843
1911
|
drawSourceAndFootnote({
|
|
1844
1912
|
ctx,
|
|
1913
|
+
fonts: options,
|
|
1845
1914
|
plotX,
|
|
1846
1915
|
plotW,
|
|
1847
1916
|
height: h,
|
|
@@ -1885,15 +1954,13 @@ function drawMarker2(ctx, marker, cx, cy, r, hollow) {
|
|
|
1885
1954
|
}
|
|
1886
1955
|
function renderLineGraph(ctx, w, h, data, options) {
|
|
1887
1956
|
clearCanvas(ctx, w, h);
|
|
1888
|
-
const font = options.fontFamily;
|
|
1889
|
-
const customFont = options.customFont;
|
|
1890
1957
|
const n = data.xLabels.length;
|
|
1891
1958
|
const useLegend = data.labelPlacement === "legend" && options.showLegend;
|
|
1892
1959
|
const legendPos = options.legendPosition;
|
|
1893
|
-
const legendW = useLegend && legendPos === "right" ? measureLegendWidth(ctx, data.series.map((s) => s.label), options.fontSize.dataLabel * 0.85 + 5, "line") : 0;
|
|
1894
|
-
ctx.font = getFont(options.fontSize.dataLabel,
|
|
1960
|
+
const legendW = useLegend && legendPos === "right" ? measureLegendWidth(ctx, data.series.map((s) => s.label), options.fontSize.dataLabel * 0.85 + 5, options, "line") : 0;
|
|
1961
|
+
ctx.font = getFont(options.fontSize.dataLabel, options, "bold");
|
|
1895
1962
|
const endLabelW = data.labelPlacement === "lineEnd" ? widestLabel(ctx, data.series.map((s) => s.label)) + 16 : 0;
|
|
1896
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
1963
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
1897
1964
|
const xUnitW = data.xUnit ? ctx.measureText(data.xUnit).width + ctx.measureText(data.xLabels[n - 1] ?? "").width / 2 + 12 : 0;
|
|
1898
1965
|
const padRight = 24 + legendW + Math.max(endLabelW, xUnitW);
|
|
1899
1966
|
const legendReserve = useLegend && legendPos === "bottom" && !data.insideLegend ? measureBottomLegend(
|
|
@@ -1901,6 +1968,7 @@ function renderLineGraph(ctx, w, h, data, options) {
|
|
|
1901
1968
|
data.series.map((s) => s.label),
|
|
1902
1969
|
options.fontSize.dataLabel * 0.85,
|
|
1903
1970
|
w - 130 - padRight,
|
|
1971
|
+
options,
|
|
1904
1972
|
"line"
|
|
1905
1973
|
) : 0;
|
|
1906
1974
|
const padding = {
|
|
@@ -1949,8 +2017,7 @@ function renderLineGraph(ctx, w, h, data, options) {
|
|
|
1949
2017
|
step: axis.step,
|
|
1950
2018
|
label: data.yUnit,
|
|
1951
2019
|
side: "left",
|
|
1952
|
-
|
|
1953
|
-
customFont,
|
|
2020
|
+
fonts: options,
|
|
1954
2021
|
tickFontSize: options.fontSize.tick,
|
|
1955
2022
|
labelFontSize: options.fontSize.axisLabel,
|
|
1956
2023
|
drawGrid: true
|
|
@@ -2042,7 +2109,7 @@ function renderLineGraph(ctx, w, h, data, options) {
|
|
|
2042
2109
|
}
|
|
2043
2110
|
if (data.labelPlacement === "lineEnd") {
|
|
2044
2111
|
ctx.fillStyle = "#000";
|
|
2045
|
-
ctx.font = getFont(options.fontSize.dataLabel,
|
|
2112
|
+
ctx.font = getFont(options.fontSize.dataLabel, options, "bold");
|
|
2046
2113
|
const placer = new LabelPlacer();
|
|
2047
2114
|
const lineHeight = options.fontSize.dataLabel * 1.1;
|
|
2048
2115
|
const columnX = plotX + plotW + 10;
|
|
@@ -2072,7 +2139,7 @@ function renderLineGraph(ctx, w, h, data, options) {
|
|
|
2072
2139
|
});
|
|
2073
2140
|
}
|
|
2074
2141
|
ctx.fillStyle = "#000";
|
|
2075
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
2142
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
2076
2143
|
ctx.textAlign = "center";
|
|
2077
2144
|
ctx.textBaseline = "top";
|
|
2078
2145
|
const xStride = labelStride(stepX, widestLabel(ctx, data.xLabels));
|
|
@@ -2086,11 +2153,11 @@ function renderLineGraph(ctx, w, h, data, options) {
|
|
|
2086
2153
|
ctx.fillText(data.xUnit, plotX + plotW + lastLabelHalf + 6, plotY + plotH + 10);
|
|
2087
2154
|
}
|
|
2088
2155
|
if (options.title) {
|
|
2089
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
2156
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
2090
2157
|
}
|
|
2091
2158
|
if (useLegend && data.insideLegend) {
|
|
2092
2159
|
ctx.save();
|
|
2093
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
2160
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options, "bold");
|
|
2094
2161
|
const rowH = options.fontSize.dataLabel * 1.15;
|
|
2095
2162
|
const swatch = rowH * 0.75;
|
|
2096
2163
|
const boxW = swatch + 6 + widestLabel(ctx, data.series.map((s) => s.label)) + 10;
|
|
@@ -2122,6 +2189,7 @@ function renderLineGraph(ctx, w, h, data, options) {
|
|
|
2122
2189
|
}));
|
|
2123
2190
|
drawLegend({
|
|
2124
2191
|
ctx,
|
|
2192
|
+
fonts: options,
|
|
2125
2193
|
items,
|
|
2126
2194
|
position: legendPos,
|
|
2127
2195
|
plotX,
|
|
@@ -2135,6 +2203,7 @@ function renderLineGraph(ctx, w, h, data, options) {
|
|
|
2135
2203
|
}
|
|
2136
2204
|
drawSourceAndFootnote({
|
|
2137
2205
|
ctx,
|
|
2206
|
+
fonts: options,
|
|
2138
2207
|
plotX,
|
|
2139
2208
|
plotW,
|
|
2140
2209
|
height: h,
|
|
@@ -2155,9 +2224,8 @@ function renderMatrixTable(ctx, w, h, data, options) {
|
|
|
2155
2224
|
const n = data.names.length;
|
|
2156
2225
|
if (n === 0) return;
|
|
2157
2226
|
const font = options.fontFamily;
|
|
2158
|
-
const customFont = options.customFont;
|
|
2159
2227
|
const base = options.fontSize.tick;
|
|
2160
|
-
ctx.font = getFont(base,
|
|
2228
|
+
ctx.font = getFont(base, options, "bold");
|
|
2161
2229
|
const widest = Math.max(
|
|
2162
2230
|
...data.names.map((t) => ctx.measureText(t).width),
|
|
2163
2231
|
...cellValues(data).map((v) => ctx.measureText(formatValue(v, data.groupThousands)).width)
|
|
@@ -2184,7 +2252,7 @@ function renderMatrixTable(ctx, w, h, data, options) {
|
|
|
2184
2252
|
const centered = top + unitH + Math.max(0, (h - top - unitH - footH - tableH) / 2);
|
|
2185
2253
|
const tableY = Math.max(top + unitH, Math.min(centered, h - footH - tableH));
|
|
2186
2254
|
if (data.unit) {
|
|
2187
|
-
ctx.font = getFont(options.fontSize.dataLabel,
|
|
2255
|
+
ctx.font = getFont(options.fontSize.dataLabel, options, "bold");
|
|
2188
2256
|
ctx.fillStyle = "#000";
|
|
2189
2257
|
ctx.textAlign = "right";
|
|
2190
2258
|
ctx.textBaseline = "bottom";
|
|
@@ -2204,11 +2272,11 @@ function renderMatrixTable(ctx, w, h, data, options) {
|
|
|
2204
2272
|
ctx.fillStyle = "#000";
|
|
2205
2273
|
if (isName) {
|
|
2206
2274
|
const family = data.nameIsSymbol && !data.nameIsSymbol[i] ? "sans" : font;
|
|
2207
|
-
ctx.font = getFont(cellFontSize,
|
|
2275
|
+
ctx.font = getFont(cellFontSize, options, "bold", family);
|
|
2208
2276
|
ctx.textAlign = "center";
|
|
2209
2277
|
ctx.fillText(data.names[i], x + cellW / 2, y + cellH / 2);
|
|
2210
2278
|
} else {
|
|
2211
|
-
ctx.font = getFont(cellFontSize,
|
|
2279
|
+
ctx.font = getFont(cellFontSize, options, "bold");
|
|
2212
2280
|
ctx.textAlign = "right";
|
|
2213
2281
|
ctx.fillText(
|
|
2214
2282
|
formatValue(data.values[i]?.[j] ?? 0, data.groupThousands),
|
|
@@ -2218,9 +2286,10 @@ function renderMatrixTable(ctx, w, h, data, options) {
|
|
|
2218
2286
|
}
|
|
2219
2287
|
}
|
|
2220
2288
|
}
|
|
2221
|
-
drawTitle({ ctx, plotX: tableX, plotW: tableW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
2289
|
+
drawTitle({ ctx, fonts: options, plotX: tableX, plotW: tableW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
2222
2290
|
drawSourceAndFootnote({
|
|
2223
2291
|
ctx,
|
|
2292
|
+
fonts: options,
|
|
2224
2293
|
plotX: tableX,
|
|
2225
2294
|
plotW: tableW,
|
|
2226
2295
|
height: h,
|
|
@@ -2254,12 +2323,11 @@ function renderDataTable(ctx, w, h, data, options) {
|
|
|
2254
2323
|
const rows = data.rows.length;
|
|
2255
2324
|
if (cols === 0 || rows === 0) return;
|
|
2256
2325
|
const font = options.fontFamily;
|
|
2257
|
-
const customFont = options.customFont;
|
|
2258
2326
|
const base = options.fontSize.tick;
|
|
2259
|
-
ctx.font = getFont(base,
|
|
2327
|
+
ctx.font = getFont(base, options, "bold");
|
|
2260
2328
|
const labelTextW = Math.max(
|
|
2261
2329
|
ctx.measureText(data.cornerLabel).width,
|
|
2262
|
-
...data.rows.map((r) => rowLabelWidth(ctx, r, base,
|
|
2330
|
+
...data.rows.map((r) => rowLabelWidth(ctx, r, base, options))
|
|
2263
2331
|
);
|
|
2264
2332
|
const valueTextW = Math.max(
|
|
2265
2333
|
...data.columns.map((c) => ctx.measureText(c).width),
|
|
@@ -2294,17 +2362,17 @@ function renderDataTable(ctx, w, h, data, options) {
|
|
|
2294
2362
|
ctx.fillStyle = "#000";
|
|
2295
2363
|
ctx.textBaseline = "middle";
|
|
2296
2364
|
ctx.textAlign = "center";
|
|
2297
|
-
ctx.font = getFont(cellFontSize,
|
|
2365
|
+
ctx.font = getFont(cellFontSize, options, "bold");
|
|
2298
2366
|
ctx.fillText(data.cornerLabel, tableX + labelW / 2, tableY + cellH / 2, labelW - pad * 2);
|
|
2299
2367
|
data.columns.forEach((name, j) => {
|
|
2300
2368
|
const family = data.columnIsSymbol && !data.columnIsSymbol[j] ? "sans" : font;
|
|
2301
|
-
ctx.font = getFont(cellFontSize,
|
|
2369
|
+
ctx.font = getFont(cellFontSize, options, "bold", family);
|
|
2302
2370
|
ctx.fillText(name, columnX(j) + valueW / 2, tableY + cellH / 2, valueW - pad * 2);
|
|
2303
2371
|
});
|
|
2304
2372
|
data.rows.forEach((row, i) => {
|
|
2305
2373
|
const y = tableY + cellH * (i + 1);
|
|
2306
|
-
drawRowLabel(ctx, row, tableX + labelW / 2, y + cellH / 2, cellFontSize,
|
|
2307
|
-
ctx.font = getFont(cellFontSize,
|
|
2374
|
+
drawRowLabel(ctx, row, tableX + labelW / 2, y + cellH / 2, cellFontSize, options);
|
|
2375
|
+
ctx.font = getFont(cellFontSize, options, "bold");
|
|
2308
2376
|
ctx.textAlign = "right";
|
|
2309
2377
|
for (let j = 0; j < cols; j++) {
|
|
2310
2378
|
const v = row.values[j];
|
|
@@ -2330,9 +2398,10 @@ function renderDataTable(ctx, w, h, data, options) {
|
|
|
2330
2398
|
}
|
|
2331
2399
|
ctx.lineWidth = 2;
|
|
2332
2400
|
ctx.strokeRect(tableX, tableY, tableW, tableH);
|
|
2333
|
-
drawTitle({ ctx, plotX: tableX, plotW: tableW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
2401
|
+
drawTitle({ ctx, fonts: options, plotX: tableX, plotW: tableW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
2334
2402
|
drawSourceAndFootnote({
|
|
2335
2403
|
ctx,
|
|
2404
|
+
fonts: options,
|
|
2336
2405
|
plotX: tableX,
|
|
2337
2406
|
plotW: tableW,
|
|
2338
2407
|
height: h,
|
|
@@ -2348,30 +2417,30 @@ function line(ctx, x1, y1, x2, y2) {
|
|
|
2348
2417
|
ctx.lineTo(x2, y2);
|
|
2349
2418
|
ctx.stroke();
|
|
2350
2419
|
}
|
|
2351
|
-
function rowLabelWidth(ctx, row, size,
|
|
2352
|
-
ctx.font = getFont(size,
|
|
2420
|
+
function rowLabelWidth(ctx, row, size, options) {
|
|
2421
|
+
ctx.font = getFont(size, options, "bold");
|
|
2353
2422
|
const labelW = ctx.measureText(row.label).width;
|
|
2354
2423
|
if (!row.unit) return labelW;
|
|
2355
|
-
ctx.font = getFont(size * UNIT_RATIO,
|
|
2424
|
+
ctx.font = getFont(size * UNIT_RATIO, options, "bold");
|
|
2356
2425
|
return labelW + ctx.measureText(row.unit).width;
|
|
2357
2426
|
}
|
|
2358
|
-
function drawRowLabel(ctx, row, cx, cy, size,
|
|
2427
|
+
function drawRowLabel(ctx, row, cx, cy, size, options) {
|
|
2359
2428
|
ctx.fillStyle = "#000";
|
|
2360
2429
|
ctx.textBaseline = "middle";
|
|
2361
|
-
ctx.font = getFont(size,
|
|
2430
|
+
ctx.font = getFont(size, options, "bold");
|
|
2362
2431
|
const labelW = ctx.measureText(row.label).width;
|
|
2363
2432
|
if (!row.unit) {
|
|
2364
2433
|
ctx.textAlign = "center";
|
|
2365
2434
|
ctx.fillText(row.label, cx, cy);
|
|
2366
2435
|
return;
|
|
2367
2436
|
}
|
|
2368
|
-
ctx.font = getFont(size * UNIT_RATIO,
|
|
2437
|
+
ctx.font = getFont(size * UNIT_RATIO, options, "bold");
|
|
2369
2438
|
const unitW = ctx.measureText(row.unit).width;
|
|
2370
2439
|
const startX = cx - (labelW + unitW) / 2;
|
|
2371
2440
|
ctx.textAlign = "left";
|
|
2372
|
-
ctx.font = getFont(size,
|
|
2441
|
+
ctx.font = getFont(size, options, "bold");
|
|
2373
2442
|
ctx.fillText(row.label, startX, cy);
|
|
2374
|
-
ctx.font = getFont(size * UNIT_RATIO,
|
|
2443
|
+
ctx.font = getFont(size * UNIT_RATIO, options, "bold");
|
|
2375
2444
|
ctx.fillText(row.unit, startX + labelW, cy);
|
|
2376
2445
|
ctx.textAlign = "center";
|
|
2377
2446
|
}
|
|
@@ -2384,6 +2453,571 @@ function formatValue2(v, decimals = 1, groupThousands = true) {
|
|
|
2384
2453
|
return frac ? `${grouped}.${frac}` : grouped;
|
|
2385
2454
|
}
|
|
2386
2455
|
|
|
2456
|
+
// src/core/canvas/subscript.ts
|
|
2457
|
+
var SUB_SCALE = 0.65;
|
|
2458
|
+
var SUB_DROP = 0.16;
|
|
2459
|
+
var SUB_CHARS = /[0-9A-Za-z]/;
|
|
2460
|
+
var HAS_SUB = /_[0-9A-Za-z]/;
|
|
2461
|
+
function parseRich(text) {
|
|
2462
|
+
if (!HAS_SUB.test(text)) return [{ text, sub: false }];
|
|
2463
|
+
const runs = [];
|
|
2464
|
+
let plain = "";
|
|
2465
|
+
for (let i = 0; i < text.length; i++) {
|
|
2466
|
+
if (text[i] === "_" && i + 1 < text.length && SUB_CHARS.test(text[i + 1])) {
|
|
2467
|
+
let j = i + 1;
|
|
2468
|
+
while (j < text.length && SUB_CHARS.test(text[j])) j++;
|
|
2469
|
+
if (plain) {
|
|
2470
|
+
runs.push({ text: plain, sub: false });
|
|
2471
|
+
plain = "";
|
|
2472
|
+
}
|
|
2473
|
+
runs.push({ text: text.slice(i + 1, j), sub: true });
|
|
2474
|
+
i = j - 1;
|
|
2475
|
+
continue;
|
|
2476
|
+
}
|
|
2477
|
+
plain += text[i];
|
|
2478
|
+
}
|
|
2479
|
+
if (plain) runs.push({ text: plain, sub: false });
|
|
2480
|
+
return runs;
|
|
2481
|
+
}
|
|
2482
|
+
function isPlain(runs) {
|
|
2483
|
+
return runs.length === 1 && !runs[0].sub;
|
|
2484
|
+
}
|
|
2485
|
+
function richWidth(ctx, text, size, makeFont) {
|
|
2486
|
+
const runs = parseRich(text);
|
|
2487
|
+
const base = makeFont(size);
|
|
2488
|
+
if (isPlain(runs)) {
|
|
2489
|
+
ctx.font = base;
|
|
2490
|
+
return ctx.measureText(text).width;
|
|
2491
|
+
}
|
|
2492
|
+
const sub = makeFont(size * SUB_SCALE);
|
|
2493
|
+
let w = 0;
|
|
2494
|
+
for (const r of runs) {
|
|
2495
|
+
ctx.font = r.sub ? sub : base;
|
|
2496
|
+
w += ctx.measureText(r.text).width;
|
|
2497
|
+
}
|
|
2498
|
+
ctx.font = base;
|
|
2499
|
+
return w;
|
|
2500
|
+
}
|
|
2501
|
+
function richExtent(ctx, text, size, makeFont) {
|
|
2502
|
+
const runs = parseRich(text);
|
|
2503
|
+
const base = makeFont(size);
|
|
2504
|
+
if (isPlain(runs)) {
|
|
2505
|
+
ctx.font = base;
|
|
2506
|
+
return textExtent(ctx, text);
|
|
2507
|
+
}
|
|
2508
|
+
const subFont = makeFont(size * SUB_SCALE);
|
|
2509
|
+
const drop = size * SUB_DROP;
|
|
2510
|
+
let w = 0;
|
|
2511
|
+
let up = 0;
|
|
2512
|
+
let down = 0;
|
|
2513
|
+
for (const r of runs) {
|
|
2514
|
+
ctx.font = r.sub ? subFont : base;
|
|
2515
|
+
const e = textExtent(ctx, r.text);
|
|
2516
|
+
w += e.left + e.right;
|
|
2517
|
+
up = Math.max(up, r.sub ? e.up - drop : e.up);
|
|
2518
|
+
down = Math.max(down, r.sub ? e.down + drop : e.down);
|
|
2519
|
+
}
|
|
2520
|
+
ctx.font = base;
|
|
2521
|
+
const left = ctx.textAlign === "right" || ctx.textAlign === "end" ? w : ctx.textAlign === "center" ? w / 2 : 0;
|
|
2522
|
+
return { left, right: w - left, up, down };
|
|
2523
|
+
}
|
|
2524
|
+
function fillRich(ctx, text, x, y, size, makeFont) {
|
|
2525
|
+
const runs = parseRich(text);
|
|
2526
|
+
const base = makeFont(size);
|
|
2527
|
+
if (isPlain(runs)) {
|
|
2528
|
+
ctx.font = base;
|
|
2529
|
+
ctx.fillText(text, x, y);
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
const subFont = makeFont(size * SUB_SCALE);
|
|
2533
|
+
const drop = size * SUB_DROP;
|
|
2534
|
+
let total = 0;
|
|
2535
|
+
for (const r of runs) {
|
|
2536
|
+
ctx.font = r.sub ? subFont : base;
|
|
2537
|
+
total += ctx.measureText(r.text).width;
|
|
2538
|
+
}
|
|
2539
|
+
const align = ctx.textAlign;
|
|
2540
|
+
let cx = align === "right" || align === "end" ? x - total : align === "center" ? x - total / 2 : x;
|
|
2541
|
+
ctx.textAlign = "left";
|
|
2542
|
+
for (const r of runs) {
|
|
2543
|
+
ctx.font = r.sub ? subFont : base;
|
|
2544
|
+
ctx.fillText(r.text, cx, r.sub ? y + drop : y);
|
|
2545
|
+
cx += ctx.measureText(r.text).width;
|
|
2546
|
+
}
|
|
2547
|
+
ctx.textAlign = align;
|
|
2548
|
+
ctx.font = base;
|
|
2549
|
+
}
|
|
2550
|
+
function shrinkRichToWidth(ctx, texts, size, available, makeFont, minScale = MIN_SCALE) {
|
|
2551
|
+
if (available <= 0 || texts.length === 0) return size;
|
|
2552
|
+
const saved = ctx.font;
|
|
2553
|
+
const w = Math.max(...texts.map((t) => richWidth(ctx, t, size, makeFont)));
|
|
2554
|
+
ctx.font = saved;
|
|
2555
|
+
if (w <= available) return size;
|
|
2556
|
+
return Math.max(size * minScale, size * (available / w));
|
|
2557
|
+
}
|
|
2558
|
+
function nudgeRichInside(ctx, text, x, y, canvasW, canvasH, size, makeFont, margin = EDGE) {
|
|
2559
|
+
const e = richExtent(ctx, text, size, makeFont);
|
|
2560
|
+
let nx = x;
|
|
2561
|
+
let ny = y;
|
|
2562
|
+
if (x - e.left < margin) nx = margin + e.left;
|
|
2563
|
+
else if (x + e.right > canvasW - margin) nx = canvasW - margin - e.right;
|
|
2564
|
+
if (y - e.up < margin) ny = margin + e.up;
|
|
2565
|
+
else if (y + e.down > canvasH - margin) ny = canvasH - margin - e.down;
|
|
2566
|
+
return { x: nx, y: ny };
|
|
2567
|
+
}
|
|
2568
|
+
function drawFloatingRich(ctx, text, x, y, canvasW, canvasH, fontSize, makeFont) {
|
|
2569
|
+
if (!text) return;
|
|
2570
|
+
if (!HAS_SUB.test(text)) {
|
|
2571
|
+
drawFloatingLabel(ctx, text, x, y, canvasW, canvasH, fontSize, makeFont);
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
const size = shrinkRichToWidth(ctx, [text], fontSize, canvasW - EDGE * 2, makeFont);
|
|
2575
|
+
const at = nudgeRichInside(ctx, text, x, y, canvasW, canvasH, size, makeFont);
|
|
2576
|
+
fillRich(ctx, text, at.x, at.y, size, makeFont);
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
// src/core/graphs/EconPlane.ts
|
|
2580
|
+
var ARROW_EXT = 26;
|
|
2581
|
+
var AXIS_W = 2.5;
|
|
2582
|
+
var LINE_W = 3;
|
|
2583
|
+
var GUIDE_W = 1.5;
|
|
2584
|
+
var ARROW_W = 2.5;
|
|
2585
|
+
var DOT_R = 6;
|
|
2586
|
+
var HEAD_LEN = 14;
|
|
2587
|
+
var HEAD_HALF = 6;
|
|
2588
|
+
var LABEL_GAP = 13;
|
|
2589
|
+
var NAME_LINE_H = 1.15;
|
|
2590
|
+
var DASH_PATTERN = {
|
|
2591
|
+
dashed: [7, 5],
|
|
2592
|
+
dotted: [1.5, 4]
|
|
2593
|
+
};
|
|
2594
|
+
var THICK_DASH = [12, 8];
|
|
2595
|
+
var clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
2596
|
+
function formatTick3(v) {
|
|
2597
|
+
const s = Number.isInteger(v) ? String(v) : v.toFixed(1);
|
|
2598
|
+
return s.startsWith("-") ? "\u2212" + s.slice(1) : s;
|
|
2599
|
+
}
|
|
2600
|
+
function tickText(axis, i) {
|
|
2601
|
+
const given = Array.isArray(axis.tickLabels) ? axis.tickLabels[i] : void 0;
|
|
2602
|
+
return typeof given === "string" && given !== "" ? given : formatTick3(axis.ticks[i]);
|
|
2603
|
+
}
|
|
2604
|
+
function splitLines(text) {
|
|
2605
|
+
return text ? text.split(/\\n|\n/) : [""];
|
|
2606
|
+
}
|
|
2607
|
+
function span(axis) {
|
|
2608
|
+
const d = axis.max - axis.min;
|
|
2609
|
+
return Number.isFinite(d) && Math.abs(d) > 1e-9 ? d : 1;
|
|
2610
|
+
}
|
|
2611
|
+
function seriesOf(data) {
|
|
2612
|
+
return Array.isArray(data.series) ? data.series.filter((s) => s && Array.isArray(s.points)) : [];
|
|
2613
|
+
}
|
|
2614
|
+
function drawOrder(series) {
|
|
2615
|
+
return series.length > 1 ? [...series].reverse() : series;
|
|
2616
|
+
}
|
|
2617
|
+
function drawSeriesMarker(ctx, s, cx, cy) {
|
|
2618
|
+
ctx.beginPath();
|
|
2619
|
+
if (s.marker === "square") ctx.rect(cx - DOT_R, cy - DOT_R, DOT_R * 2, DOT_R * 2);
|
|
2620
|
+
else ctx.arc(cx, cy, DOT_R, 0, Math.PI * 2);
|
|
2621
|
+
ctx.fillStyle = s.hollow ? "#fff" : "#000";
|
|
2622
|
+
ctx.fill();
|
|
2623
|
+
if (s.hollow) {
|
|
2624
|
+
ctx.strokeStyle = "#000";
|
|
2625
|
+
ctx.lineWidth = 2;
|
|
2626
|
+
ctx.setLineDash([]);
|
|
2627
|
+
ctx.stroke();
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
function anchorOf(pos, gap) {
|
|
2631
|
+
const d = gap * 0.75;
|
|
2632
|
+
switch (pos) {
|
|
2633
|
+
case "left":
|
|
2634
|
+
return { dx: -gap, dy: 0, align: "right", baseline: "middle" };
|
|
2635
|
+
case "top":
|
|
2636
|
+
return { dx: 0, dy: -gap, align: "center", baseline: "bottom" };
|
|
2637
|
+
case "bottom":
|
|
2638
|
+
return { dx: 0, dy: gap, align: "center", baseline: "top" };
|
|
2639
|
+
case "top-left":
|
|
2640
|
+
return { dx: -d, dy: -d, align: "right", baseline: "bottom" };
|
|
2641
|
+
case "top-right":
|
|
2642
|
+
return { dx: d, dy: -d, align: "left", baseline: "bottom" };
|
|
2643
|
+
case "bottom-left":
|
|
2644
|
+
return { dx: -d, dy: d, align: "right", baseline: "top" };
|
|
2645
|
+
case "bottom-right":
|
|
2646
|
+
return { dx: d, dy: d, align: "left", baseline: "top" };
|
|
2647
|
+
default:
|
|
2648
|
+
return { dx: gap, dy: 0, align: "left", baseline: "middle" };
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
function drawArrowHead(ctx, x1, y1, x2, y2) {
|
|
2652
|
+
const dx = x2 - x1;
|
|
2653
|
+
const dy = y2 - y1;
|
|
2654
|
+
const len = Math.hypot(dx, dy);
|
|
2655
|
+
if (!(len > 0)) return;
|
|
2656
|
+
const ux = dx / len;
|
|
2657
|
+
const uy = dy / len;
|
|
2658
|
+
ctx.beginPath();
|
|
2659
|
+
ctx.moveTo(x2, y2);
|
|
2660
|
+
ctx.lineTo(x2 - ux * HEAD_LEN - uy * HEAD_HALF, y2 - uy * HEAD_LEN + ux * HEAD_HALF);
|
|
2661
|
+
ctx.lineTo(x2 - ux * HEAD_LEN + uy * HEAD_HALF, y2 - uy * HEAD_LEN - ux * HEAD_HALF);
|
|
2662
|
+
ctx.closePath();
|
|
2663
|
+
ctx.fill();
|
|
2664
|
+
}
|
|
2665
|
+
function drawBreakMark(ctx, x, y, vertical) {
|
|
2666
|
+
const half = 8;
|
|
2667
|
+
const gap = 7;
|
|
2668
|
+
ctx.save();
|
|
2669
|
+
ctx.translate(x, y);
|
|
2670
|
+
if (vertical) ctx.rotate(-Math.PI / 2);
|
|
2671
|
+
ctx.fillStyle = "#fff";
|
|
2672
|
+
ctx.fillRect(-half - 2, -gap, (half + 2) * 2, gap * 2);
|
|
2673
|
+
ctx.strokeStyle = "#000";
|
|
2674
|
+
ctx.lineWidth = 1.6;
|
|
2675
|
+
for (const dy of [-gap * 0.5, gap * 0.5]) {
|
|
2676
|
+
ctx.beginPath();
|
|
2677
|
+
ctx.moveTo(-half, dy + 2.5);
|
|
2678
|
+
ctx.quadraticCurveTo(-half / 2, dy - 4.5, 0, dy);
|
|
2679
|
+
ctx.quadraticCurveTo(half / 2, dy + 4.5, half, dy - 2.5);
|
|
2680
|
+
ctx.stroke();
|
|
2681
|
+
}
|
|
2682
|
+
ctx.restore();
|
|
2683
|
+
}
|
|
2684
|
+
function renderEconPlane(ctx, w, h, data, options) {
|
|
2685
|
+
clearCanvas(ctx, w, h);
|
|
2686
|
+
const fs = options.fontSize;
|
|
2687
|
+
const four = data.quadrants === "all";
|
|
2688
|
+
const nameFont = (size) => getFont(size, options);
|
|
2689
|
+
const tickFont = getFont(fs.tick, options);
|
|
2690
|
+
const series = seriesOf(data);
|
|
2691
|
+
ctx.font = tickFont;
|
|
2692
|
+
const yTickW = data.yAxis.ticks.length > 0 ? Math.max(...data.yAxis.ticks.map((_, i) => richWidth(ctx, tickText(data.yAxis, i), fs.tick, nameFont))) : 0;
|
|
2693
|
+
ctx.font = nameFont(fs.axisLabel);
|
|
2694
|
+
const yNameLines = splitLines(data.yAxis.label);
|
|
2695
|
+
const xNameW = data.xAxis.label ? richWidth(ctx, data.xAxis.label, fs.axisLabel, nameFont) : 0;
|
|
2696
|
+
const yNameW = data.yAxis.label ? Math.max(...yNameLines.map((l) => l ? richWidth(ctx, l, fs.axisLabel, nameFont) : 0)) : 0;
|
|
2697
|
+
const lineLabelW = data.lines.length > 0 ? Math.max(...data.lines.map((l) => l.label ? richWidth(ctx, l.label, fs.axisLabel, nameFont) : 0)) : 0;
|
|
2698
|
+
const notes = options.footnotes.filter((f) => f.trim()).length;
|
|
2699
|
+
const inlineSource = !!options.sourceInline && !!options.source && !options.sourceLeft && notes > 0;
|
|
2700
|
+
const hasSourceLine = (!!options.source || !!options.sourceLeft) && !inlineSource;
|
|
2701
|
+
const bottomText = (hasSourceLine ? fs.dataLabel + 6 : 0) + notes * (fs.dataLabel * 0.9 + 6) + 12;
|
|
2702
|
+
const yNameExtra = (yNameLines.length - 1) * fs.axisLabel * NAME_LINE_H;
|
|
2703
|
+
const padding = {
|
|
2704
|
+
top: (options.title ? fs.title + 40 : 18) + fs.axisLabel + yNameExtra + 10 + ARROW_EXT,
|
|
2705
|
+
// 세로축 이름은 축 **왼쪽 위**에 오른쪽 맞춤으로 선다(네 사분면이면 가운데).
|
|
2706
|
+
// 자리가 모자라면 글꼴을 줄이고 밀어 넣지만, 흔한 길이는 여기서 비워 둔다.
|
|
2707
|
+
// 34% 를 넘겨 비우지는 않는다 — 이름 하나 때문에 그림이 사라지면 안 된다.
|
|
2708
|
+
left: four ? Math.max(46, ARROW_EXT + 20) : Math.max(46, yTickW + 14, Math.min(yNameW + 8, w * 0.34)),
|
|
2709
|
+
right: Math.min(
|
|
2710
|
+
w * 0.45,
|
|
2711
|
+
Math.max(56, lineLabelW + 16, ARROW_EXT + 12 + xNameW + 6)
|
|
2712
|
+
),
|
|
2713
|
+
bottom: fs.tick + 24 + bottomText
|
|
2714
|
+
};
|
|
2715
|
+
const plotX = padding.left;
|
|
2716
|
+
const plotY = padding.top;
|
|
2717
|
+
const plotW = Math.max(40, w - padding.left - padding.right);
|
|
2718
|
+
const plotH = Math.max(40, h - padding.top - padding.bottom);
|
|
2719
|
+
const toX = (v) => plotX + (v - data.xAxis.min) / span(data.xAxis) * plotW;
|
|
2720
|
+
const toY = (v) => plotY + plotH - (v - data.yAxis.min) / span(data.yAxis) * plotH;
|
|
2721
|
+
const axX = four ? clamp(toX(0), plotX, plotX + plotW) : plotX;
|
|
2722
|
+
const axY = four ? clamp(toY(0), plotY, plotY + plotH) : plotY + plotH;
|
|
2723
|
+
const dash = DASH_PATTERN[data.dash] ?? DASH_PATTERN.dashed;
|
|
2724
|
+
if (data.grid) {
|
|
2725
|
+
const xEnd = data.xAxis.ticks.length > 0 ? toX(Math.max(...data.xAxis.ticks)) : plotX + plotW;
|
|
2726
|
+
const yEnd = data.yAxis.ticks.length > 0 ? toY(Math.max(...data.yAxis.ticks)) : plotY;
|
|
2727
|
+
ctx.save();
|
|
2728
|
+
ctx.strokeStyle = "#000";
|
|
2729
|
+
ctx.lineWidth = GUIDE_W;
|
|
2730
|
+
ctx.setLineDash(dash);
|
|
2731
|
+
for (const v of data.xAxis.ticks) {
|
|
2732
|
+
if (v === 0) continue;
|
|
2733
|
+
const x = toX(v);
|
|
2734
|
+
ctx.beginPath();
|
|
2735
|
+
ctx.moveTo(x, axY);
|
|
2736
|
+
ctx.lineTo(x, yEnd);
|
|
2737
|
+
ctx.stroke();
|
|
2738
|
+
}
|
|
2739
|
+
for (const v of data.yAxis.ticks) {
|
|
2740
|
+
if (v === 0) continue;
|
|
2741
|
+
const y = toY(v);
|
|
2742
|
+
ctx.beginPath();
|
|
2743
|
+
ctx.moveTo(axX, y);
|
|
2744
|
+
ctx.lineTo(xEnd, y);
|
|
2745
|
+
ctx.stroke();
|
|
2746
|
+
}
|
|
2747
|
+
ctx.restore();
|
|
2748
|
+
}
|
|
2749
|
+
ctx.save();
|
|
2750
|
+
ctx.strokeStyle = "#000";
|
|
2751
|
+
ctx.lineWidth = GUIDE_W;
|
|
2752
|
+
ctx.setLineDash(dash);
|
|
2753
|
+
for (const p of data.points) {
|
|
2754
|
+
if (p.guide === "none") continue;
|
|
2755
|
+
const px = toX(p.x);
|
|
2756
|
+
const py = toY(p.y);
|
|
2757
|
+
const cross = p.guide === "cross";
|
|
2758
|
+
if (cross || p.guide === "to-x" || p.guide === "both") {
|
|
2759
|
+
ctx.beginPath();
|
|
2760
|
+
ctx.moveTo(px, cross ? plotY : py);
|
|
2761
|
+
ctx.lineTo(px, cross ? plotY + plotH : axY);
|
|
2762
|
+
ctx.stroke();
|
|
2763
|
+
}
|
|
2764
|
+
if (cross || p.guide === "to-y" || p.guide === "both") {
|
|
2765
|
+
ctx.beginPath();
|
|
2766
|
+
ctx.moveTo(cross ? plotX : px, py);
|
|
2767
|
+
ctx.lineTo(cross ? plotX + plotW : axX, py);
|
|
2768
|
+
ctx.stroke();
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
if (data.seriesGuides && series.length > 0) {
|
|
2772
|
+
const highest = /* @__PURE__ */ new Map();
|
|
2773
|
+
for (const s of series) {
|
|
2774
|
+
for (const p of s.points) {
|
|
2775
|
+
const prev = highest.get(p.x);
|
|
2776
|
+
if (prev == null || p.y > prev) highest.set(p.x, p.y);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
for (const [x, y] of highest) {
|
|
2780
|
+
ctx.beginPath();
|
|
2781
|
+
ctx.moveTo(toX(x), toY(y));
|
|
2782
|
+
ctx.lineTo(toX(x), axY);
|
|
2783
|
+
ctx.stroke();
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
ctx.restore();
|
|
2787
|
+
ctx.save();
|
|
2788
|
+
ctx.strokeStyle = "#000";
|
|
2789
|
+
ctx.fillStyle = "#000";
|
|
2790
|
+
ctx.lineWidth = AXIS_W;
|
|
2791
|
+
ctx.lineCap = "butt";
|
|
2792
|
+
const xLeft = four ? plotX - ARROW_EXT : axX;
|
|
2793
|
+
const xRight = plotX + plotW + ARROW_EXT;
|
|
2794
|
+
ctx.beginPath();
|
|
2795
|
+
ctx.moveTo(xLeft, axY);
|
|
2796
|
+
ctx.lineTo(xRight, axY);
|
|
2797
|
+
ctx.stroke();
|
|
2798
|
+
drawArrowHead(ctx, xLeft, axY, xRight, axY);
|
|
2799
|
+
if (four) drawArrowHead(ctx, xRight, axY, xLeft, axY);
|
|
2800
|
+
const yBottom = four ? plotY + plotH + ARROW_EXT : axY;
|
|
2801
|
+
const yTop = plotY - ARROW_EXT;
|
|
2802
|
+
ctx.beginPath();
|
|
2803
|
+
ctx.moveTo(axX, yBottom);
|
|
2804
|
+
ctx.lineTo(axX, yTop);
|
|
2805
|
+
ctx.stroke();
|
|
2806
|
+
drawArrowHead(ctx, axX, yBottom, axX, yTop);
|
|
2807
|
+
if (four) drawArrowHead(ctx, axX, yTop, axX, yBottom);
|
|
2808
|
+
if (data.xAxis.broken) {
|
|
2809
|
+
const first = data.xAxis.ticks.find((v) => v !== 0);
|
|
2810
|
+
drawBreakMark(ctx, first == null ? axX + plotW * 0.12 : (axX + toX(first)) / 2, axY, false);
|
|
2811
|
+
}
|
|
2812
|
+
if (data.yAxis.broken) {
|
|
2813
|
+
const first = data.yAxis.ticks.find((v) => v !== 0);
|
|
2814
|
+
drawBreakMark(ctx, axX, first == null ? axY - plotH * 0.12 : (axY + toY(first)) / 2, true);
|
|
2815
|
+
}
|
|
2816
|
+
ctx.restore();
|
|
2817
|
+
ctx.save();
|
|
2818
|
+
ctx.strokeStyle = "#000";
|
|
2819
|
+
ctx.lineWidth = LINE_W;
|
|
2820
|
+
for (const line2 of data.lines) {
|
|
2821
|
+
ctx.lineCap = line2.dashed ? "butt" : "round";
|
|
2822
|
+
ctx.setLineDash(line2.dashed ? THICK_DASH : []);
|
|
2823
|
+
ctx.beginPath();
|
|
2824
|
+
ctx.moveTo(toX(line2.from.x), toY(line2.from.y));
|
|
2825
|
+
ctx.lineTo(toX(line2.to.x), toY(line2.to.y));
|
|
2826
|
+
ctx.stroke();
|
|
2827
|
+
}
|
|
2828
|
+
ctx.restore();
|
|
2829
|
+
ctx.save();
|
|
2830
|
+
ctx.strokeStyle = "#000";
|
|
2831
|
+
ctx.lineWidth = LINE_W;
|
|
2832
|
+
for (const s of drawOrder(series)) {
|
|
2833
|
+
if (s.points.length < 2) continue;
|
|
2834
|
+
ctx.lineCap = s.dashed ? "butt" : "round";
|
|
2835
|
+
ctx.setLineDash(s.dashed ? THICK_DASH : []);
|
|
2836
|
+
ctx.beginPath();
|
|
2837
|
+
s.points.forEach((p, i) => {
|
|
2838
|
+
if (i === 0) ctx.moveTo(toX(p.x), toY(p.y));
|
|
2839
|
+
else ctx.lineTo(toX(p.x), toY(p.y));
|
|
2840
|
+
});
|
|
2841
|
+
ctx.stroke();
|
|
2842
|
+
}
|
|
2843
|
+
ctx.restore();
|
|
2844
|
+
ctx.save();
|
|
2845
|
+
ctx.fillStyle = "#000";
|
|
2846
|
+
ctx.textAlign = "left";
|
|
2847
|
+
ctx.textBaseline = "middle";
|
|
2848
|
+
for (const line2 of data.lines) {
|
|
2849
|
+
if (!line2.label) continue;
|
|
2850
|
+
const end = line2.labelAt === "from" ? line2.from : line2.to;
|
|
2851
|
+
const lx = toX(end.x) + 12 + (line2.labelDx ?? 0);
|
|
2852
|
+
const raw = toY(end.y) + (line2.labelDy ?? 0);
|
|
2853
|
+
const ly = Math.min(raw, axY - 6 - fs.axisLabel * 0.5);
|
|
2854
|
+
drawFloatingRich(ctx, line2.label, lx, ly, w, h, fs.axisLabel, nameFont);
|
|
2855
|
+
}
|
|
2856
|
+
ctx.restore();
|
|
2857
|
+
ctx.save();
|
|
2858
|
+
ctx.strokeStyle = "#000";
|
|
2859
|
+
ctx.fillStyle = "#000";
|
|
2860
|
+
ctx.lineWidth = ARROW_W;
|
|
2861
|
+
for (const a of data.arrows) {
|
|
2862
|
+
const x1 = toX(a.from.x);
|
|
2863
|
+
const y1 = toY(a.from.y);
|
|
2864
|
+
const x2 = toX(a.to.x);
|
|
2865
|
+
const y2 = toY(a.to.y);
|
|
2866
|
+
const len = Math.hypot(x2 - x1, y2 - y1);
|
|
2867
|
+
if (!(len > 0)) continue;
|
|
2868
|
+
const ux = (x2 - x1) / len;
|
|
2869
|
+
const uy = (y2 - y1) / len;
|
|
2870
|
+
const ox = -uy * a.offset;
|
|
2871
|
+
const oy = ux * a.offset;
|
|
2872
|
+
const cut = Math.min(a.shorten, len * 0.4);
|
|
2873
|
+
const sx = x1 + ux * cut + ox;
|
|
2874
|
+
const sy = y1 + uy * cut + oy;
|
|
2875
|
+
const ex = x2 - ux * cut + ox;
|
|
2876
|
+
const ey = y2 - uy * cut + oy;
|
|
2877
|
+
ctx.beginPath();
|
|
2878
|
+
ctx.moveTo(sx, sy);
|
|
2879
|
+
ctx.lineTo(ex, ey);
|
|
2880
|
+
ctx.stroke();
|
|
2881
|
+
drawArrowHead(ctx, sx, sy, ex, ey);
|
|
2882
|
+
if (a.label) {
|
|
2883
|
+
const at = anchorOf(a.labelPos, LABEL_GAP + 4);
|
|
2884
|
+
ctx.textAlign = at.align;
|
|
2885
|
+
ctx.textBaseline = at.baseline;
|
|
2886
|
+
drawFloatingRich(ctx, a.label, (sx + ex) / 2 + at.dx, (sy + ey) / 2 + at.dy, w, h, fs.axisLabel, nameFont);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
ctx.restore();
|
|
2890
|
+
ctx.save();
|
|
2891
|
+
ctx.fillStyle = "#000";
|
|
2892
|
+
for (const p of data.points) {
|
|
2893
|
+
const px = toX(p.x);
|
|
2894
|
+
const py = toY(p.y);
|
|
2895
|
+
if (p.dot) {
|
|
2896
|
+
ctx.beginPath();
|
|
2897
|
+
ctx.arc(px, py, DOT_R, 0, Math.PI * 2);
|
|
2898
|
+
ctx.fill();
|
|
2899
|
+
}
|
|
2900
|
+
if (!p.label) continue;
|
|
2901
|
+
const at = anchorOf(p.labelPos, DOT_R + LABEL_GAP);
|
|
2902
|
+
ctx.textAlign = at.align;
|
|
2903
|
+
ctx.textBaseline = at.baseline;
|
|
2904
|
+
drawFloatingRich(ctx, p.label, px + at.dx, py + at.dy, w, h, fs.axisLabel, nameFont);
|
|
2905
|
+
}
|
|
2906
|
+
ctx.restore();
|
|
2907
|
+
ctx.save();
|
|
2908
|
+
for (const s of drawOrder(series)) {
|
|
2909
|
+
for (const p of s.points) drawSeriesMarker(ctx, s, toX(p.x), toY(p.y));
|
|
2910
|
+
}
|
|
2911
|
+
ctx.restore();
|
|
2912
|
+
const tickInk = (text, x, y) => {
|
|
2913
|
+
if (four) {
|
|
2914
|
+
const tw = richWidth(ctx, text, fs.tick, nameFont);
|
|
2915
|
+
const left = ctx.textAlign === "right" ? tw : ctx.textAlign === "center" ? tw / 2 : 0;
|
|
2916
|
+
const up = ctx.textBaseline === "top" ? 0 : fs.tick * 0.5;
|
|
2917
|
+
const rx = clamp(x - left - 3, 0, w);
|
|
2918
|
+
const ry = clamp(y - up - 2, 0, h);
|
|
2919
|
+
ctx.fillStyle = "#fff";
|
|
2920
|
+
ctx.fillRect(rx, ry, Math.min(tw + 6, w - rx), Math.min(fs.tick + 4, h - ry));
|
|
2921
|
+
ctx.fillStyle = "#000";
|
|
2922
|
+
}
|
|
2923
|
+
fillRich(ctx, text, x, y, fs.tick, nameFont);
|
|
2924
|
+
};
|
|
2925
|
+
ctx.save();
|
|
2926
|
+
ctx.fillStyle = "#000";
|
|
2927
|
+
ctx.font = tickFont;
|
|
2928
|
+
ctx.textAlign = "center";
|
|
2929
|
+
ctx.textBaseline = "top";
|
|
2930
|
+
data.xAxis.ticks.forEach((v, i) => {
|
|
2931
|
+
if (v === 0) return;
|
|
2932
|
+
const text = tickText(data.xAxis, i);
|
|
2933
|
+
const at = nudgeRichInside(ctx, text, toX(v), axY + 10, w, h, fs.tick, nameFont);
|
|
2934
|
+
tickInk(text, at.x, at.y);
|
|
2935
|
+
});
|
|
2936
|
+
ctx.textAlign = four ? "left" : "right";
|
|
2937
|
+
ctx.textBaseline = "middle";
|
|
2938
|
+
data.yAxis.ticks.forEach((v, i) => {
|
|
2939
|
+
if (v === 0) return;
|
|
2940
|
+
const text = tickText(data.yAxis, i);
|
|
2941
|
+
const at = nudgeRichInside(ctx, text, axX + (four ? 10 : -10), toY(v), w, h, fs.tick, nameFont);
|
|
2942
|
+
tickInk(text, at.x, at.y);
|
|
2943
|
+
});
|
|
2944
|
+
ctx.textAlign = four ? "left" : "center";
|
|
2945
|
+
ctx.textBaseline = "top";
|
|
2946
|
+
const zero = nudgeInside(ctx, "0", axX + (four ? 8 : 0), axY + 10, w, h);
|
|
2947
|
+
tickInk("0", zero.x, zero.y);
|
|
2948
|
+
ctx.restore();
|
|
2949
|
+
ctx.save();
|
|
2950
|
+
ctx.fillStyle = "#000";
|
|
2951
|
+
if (data.xAxis.label) {
|
|
2952
|
+
ctx.textAlign = "left";
|
|
2953
|
+
ctx.textBaseline = "middle";
|
|
2954
|
+
drawFloatingRich(ctx, data.xAxis.label, xRight + 10, axY + fs.tick * 0.6, w, h, fs.axisLabel, nameFont);
|
|
2955
|
+
}
|
|
2956
|
+
if (data.yAxis.label) {
|
|
2957
|
+
ctx.textAlign = four ? "center" : "right";
|
|
2958
|
+
ctx.textBaseline = "bottom";
|
|
2959
|
+
const baseY = yTop - (four ? 8 : -4);
|
|
2960
|
+
if (yNameLines.length === 1) {
|
|
2961
|
+
drawFloatingRich(ctx, data.yAxis.label, axX + (four ? 0 : -8), baseY, w, h, fs.axisLabel, nameFont);
|
|
2962
|
+
} else {
|
|
2963
|
+
const lh = fs.axisLabel * NAME_LINE_H;
|
|
2964
|
+
const cx = four ? axX : axX - 8 - yNameW / 2;
|
|
2965
|
+
ctx.textAlign = "center";
|
|
2966
|
+
yNameLines.forEach((lineText, i) => {
|
|
2967
|
+
const ly = baseY - (yNameLines.length - 1 - i) * lh;
|
|
2968
|
+
drawFloatingRich(ctx, lineText, cx, ly, w, h, fs.axisLabel, nameFont);
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
ctx.restore();
|
|
2973
|
+
if (data.legend && series.length > 0) {
|
|
2974
|
+
const items = series.map((s) => ({
|
|
2975
|
+
type: "line",
|
|
2976
|
+
fillStyle: "#000",
|
|
2977
|
+
strokeStyle: "#000",
|
|
2978
|
+
label: s.label,
|
|
2979
|
+
dash: s.dashed ? THICK_DASH : [],
|
|
2980
|
+
lineWidth: 2.5,
|
|
2981
|
+
marker: s.marker,
|
|
2982
|
+
hollow: s.hollow
|
|
2983
|
+
}));
|
|
2984
|
+
drawInsideLegend({
|
|
2985
|
+
ctx,
|
|
2986
|
+
items,
|
|
2987
|
+
corner: data.legend,
|
|
2988
|
+
plotX,
|
|
2989
|
+
plotY,
|
|
2990
|
+
plotW,
|
|
2991
|
+
plotH,
|
|
2992
|
+
canvasW: w,
|
|
2993
|
+
canvasH: h,
|
|
2994
|
+
fontSize: fs.axisLabel * 0.8,
|
|
2995
|
+
font: nameFont(fs.axisLabel * 0.8),
|
|
2996
|
+
// 기호가 상자에 덮이지 않게 꼭짓점 둘레를 피할 자리로 넘긴다
|
|
2997
|
+
avoid: series.flatMap((s) => s.points.map((p) => ({
|
|
2998
|
+
x0: toX(p.x) - DOT_R,
|
|
2999
|
+
y0: toY(p.y) - DOT_R,
|
|
3000
|
+
x1: toX(p.x) + DOT_R,
|
|
3001
|
+
y1: toY(p.y) + DOT_R
|
|
3002
|
+
})))
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: fs.title, fonts: options, canvasWidth: w });
|
|
3006
|
+
drawSourceAndFootnote({
|
|
3007
|
+
ctx,
|
|
3008
|
+
plotX,
|
|
3009
|
+
plotW,
|
|
3010
|
+
height: h,
|
|
3011
|
+
source: options.source,
|
|
3012
|
+
sourceLeft: options.sourceLeft,
|
|
3013
|
+
sourceInline: options.sourceInline,
|
|
3014
|
+
footnotes: options.footnotes,
|
|
3015
|
+
fontSize: fs.dataLabel,
|
|
3016
|
+
fonts: options,
|
|
3017
|
+
canvasWidth: w
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
|
|
2387
3021
|
// src/core/graphs/TreemapGraph.ts
|
|
2388
3022
|
var PAD_RATIO3 = 0.4;
|
|
2389
3023
|
var MIN_LABEL_W = 34;
|
|
@@ -2491,29 +3125,30 @@ function renderTreemapGraph(ctx, w, h, data, options) {
|
|
|
2491
3125
|
ctx.fillStyle = "#000";
|
|
2492
3126
|
ctx.textAlign = "center";
|
|
2493
3127
|
ctx.textBaseline = "middle";
|
|
2494
|
-
ctx.font = getFont(labelSize, options
|
|
3128
|
+
ctx.font = getFont(labelSize, options);
|
|
2495
3129
|
rects.forEach((r, i) => {
|
|
2496
3130
|
if (shouldOmitLabel(r, minLabelW, labelSize)) return;
|
|
2497
3131
|
const avail = r.w - pad * 2;
|
|
2498
|
-
ctx.font = getFont(labelSize, options
|
|
3132
|
+
ctx.font = getFont(labelSize, options);
|
|
2499
3133
|
const layout = pickLabelLayout(ctx, data.cells[i].label, avail, r.h, labelSize);
|
|
2500
3134
|
if (shouldOmitLayout(layout)) return;
|
|
2501
3135
|
const drawSize = resolveDrawSize(labelSize, layout);
|
|
2502
3136
|
if (drawSize !== labelSize) {
|
|
2503
|
-
ctx.font = getFont(drawSize, options
|
|
3137
|
+
ctx.font = getFont(drawSize, options);
|
|
2504
3138
|
}
|
|
2505
3139
|
const startY = r.y + r.h / 2 - (layout.lines.length - 1) * drawSize * 1.2 / 2;
|
|
2506
3140
|
layout.lines.forEach((line2, k) => {
|
|
2507
3141
|
ctx.fillText(line2, r.x + r.w / 2, startY + k * drawSize * 1.2, avail);
|
|
2508
3142
|
});
|
|
2509
3143
|
if (drawSize !== labelSize) {
|
|
2510
|
-
ctx.font = getFont(labelSize, options
|
|
3144
|
+
ctx.font = getFont(labelSize, options);
|
|
2511
3145
|
}
|
|
2512
3146
|
});
|
|
2513
3147
|
ctx.lineWidth = 2;
|
|
2514
3148
|
ctx.strokeRect(frame.x, frame.y, frame.w, frame.h);
|
|
2515
3149
|
drawTitle({
|
|
2516
3150
|
ctx,
|
|
3151
|
+
fonts: options,
|
|
2517
3152
|
plotX: frame.x,
|
|
2518
3153
|
plotW: frame.w,
|
|
2519
3154
|
title: options.title,
|
|
@@ -2522,6 +3157,7 @@ function renderTreemapGraph(ctx, w, h, data, options) {
|
|
|
2522
3157
|
});
|
|
2523
3158
|
drawSourceAndFootnote({
|
|
2524
3159
|
ctx,
|
|
3160
|
+
fonts: options,
|
|
2525
3161
|
plotX: frame.x,
|
|
2526
3162
|
plotW: frame.w,
|
|
2527
3163
|
height: h,
|
|
@@ -2547,12 +3183,13 @@ function renderClimateGraph(ctx, w, h, data, options) {
|
|
|
2547
3183
|
options.legendLabel1 || data.precipLabel,
|
|
2548
3184
|
options.legendLabel2 || data.tempLabel
|
|
2549
3185
|
];
|
|
2550
|
-
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5) : 0;
|
|
3186
|
+
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5, options) : 0;
|
|
2551
3187
|
const legendReserve = showLegend && legendPos === "bottom" ? measureBottomLegend(
|
|
2552
3188
|
ctx,
|
|
2553
3189
|
legendLabels,
|
|
2554
3190
|
options.fontSize.dataLabel * 0.85 + 5,
|
|
2555
3191
|
w - 130 - (130 + legendW),
|
|
3192
|
+
options,
|
|
2556
3193
|
["rect", data.monthInterval === 12 ? "line" : "circle"]
|
|
2557
3194
|
) : 0;
|
|
2558
3195
|
const padding = {
|
|
@@ -2585,8 +3222,6 @@ function renderClimateGraph(ctx, w, h, data, options) {
|
|
|
2585
3222
|
step: data.precipRange.step || Math.max(1, Math.round((data.precipRange.max - data.precipRange.min) / 6))
|
|
2586
3223
|
};
|
|
2587
3224
|
if (precipAxis.min < 0) precipAxis.min = 0;
|
|
2588
|
-
const font = options.fontFamily;
|
|
2589
|
-
const customFont = options.customFont;
|
|
2590
3225
|
const indices = INTERVAL_INDICES[data.monthInterval] ?? INTERVAL_INDICES[12];
|
|
2591
3226
|
drawYAxis({
|
|
2592
3227
|
ctx,
|
|
@@ -2598,8 +3233,7 @@ function renderClimateGraph(ctx, w, h, data, options) {
|
|
|
2598
3233
|
step: tempAxis.step,
|
|
2599
3234
|
label: data.tempLabel,
|
|
2600
3235
|
side: "left",
|
|
2601
|
-
|
|
2602
|
-
customFont,
|
|
3236
|
+
fonts: options,
|
|
2603
3237
|
tickFontSize: options.fontSize.tick,
|
|
2604
3238
|
labelFontSize: options.fontSize.axisLabel,
|
|
2605
3239
|
drawGrid: true
|
|
@@ -2614,8 +3248,7 @@ function renderClimateGraph(ctx, w, h, data, options) {
|
|
|
2614
3248
|
step: precipAxis.step,
|
|
2615
3249
|
label: data.precipLabel,
|
|
2616
3250
|
side: "right",
|
|
2617
|
-
|
|
2618
|
-
customFont,
|
|
3251
|
+
fonts: options,
|
|
2619
3252
|
tickFontSize: options.fontSize.tick,
|
|
2620
3253
|
labelFontSize: options.fontSize.axisLabel
|
|
2621
3254
|
});
|
|
@@ -2627,8 +3260,7 @@ function renderClimateGraph(ctx, w, h, data, options) {
|
|
|
2627
3260
|
height: h,
|
|
2628
3261
|
labels: MONTH_LABELS,
|
|
2629
3262
|
indices,
|
|
2630
|
-
|
|
2631
|
-
customFont,
|
|
3263
|
+
fonts: options,
|
|
2632
3264
|
tickFontSize: options.fontSize.tick,
|
|
2633
3265
|
labelFontSize: options.fontSize.axisLabel
|
|
2634
3266
|
});
|
|
@@ -2640,8 +3272,7 @@ function renderClimateGraph(ctx, w, h, data, options) {
|
|
|
2640
3272
|
width: w,
|
|
2641
3273
|
height: h,
|
|
2642
3274
|
labels: filteredLabels,
|
|
2643
|
-
|
|
2644
|
-
customFont,
|
|
3275
|
+
fonts: options,
|
|
2645
3276
|
tickFontSize: options.fontSize.tick,
|
|
2646
3277
|
labelFontSize: options.fontSize.axisLabel
|
|
2647
3278
|
});
|
|
@@ -2666,7 +3297,7 @@ function renderClimateGraph(ctx, w, h, data, options) {
|
|
|
2666
3297
|
}
|
|
2667
3298
|
if (options.showDataLabels) {
|
|
2668
3299
|
ctx.fillStyle = "#000";
|
|
2669
|
-
ctx.font = getFont(options.fontSize.dataLabel,
|
|
3300
|
+
ctx.font = getFont(options.fontSize.dataLabel, options, "bold");
|
|
2670
3301
|
ctx.textAlign = "center";
|
|
2671
3302
|
ctx.textBaseline = "bottom";
|
|
2672
3303
|
for (let s = 0; s < indices.length; s++) {
|
|
@@ -2701,21 +3332,22 @@ function renderClimateGraph(ctx, w, h, data, options) {
|
|
|
2701
3332
|
ctx.fill();
|
|
2702
3333
|
if (options.showDataLabels) {
|
|
2703
3334
|
ctx.fillStyle = "#000";
|
|
2704
|
-
ctx.font = getFont(options.fontSize.dataLabel,
|
|
3335
|
+
ctx.font = getFont(options.fontSize.dataLabel, options, "bold");
|
|
2705
3336
|
ctx.textAlign = "center";
|
|
2706
3337
|
ctx.textBaseline = "bottom";
|
|
2707
3338
|
ctx.fillText(String(data.months[i].temp), cx, y - 8);
|
|
2708
3339
|
}
|
|
2709
3340
|
}
|
|
2710
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
3341
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
2711
3342
|
ctx.fillStyle = "#000";
|
|
2712
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
3343
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
2713
3344
|
ctx.textAlign = "center";
|
|
2714
3345
|
ctx.textBaseline = "top";
|
|
2715
3346
|
ctx.fillText("(\uC6D4)", plotX + plotW + 30, plotY + plotH + 12);
|
|
2716
3347
|
if (showLegend) {
|
|
2717
3348
|
drawLegend({
|
|
2718
3349
|
ctx,
|
|
3350
|
+
fonts: options,
|
|
2719
3351
|
items: [
|
|
2720
3352
|
{ type: "rect", fillStyle: "#AAA", strokeStyle: "#666", label: legendLabels[0] },
|
|
2721
3353
|
{ type: data.monthInterval === 12 ? "line" : "circle", fillStyle: "#000", label: legendLabels[1] }
|
|
@@ -2731,7 +3363,7 @@ function renderClimateGraph(ctx, w, h, data, options) {
|
|
|
2731
3363
|
rightGap: 80
|
|
2732
3364
|
});
|
|
2733
3365
|
}
|
|
2734
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel });
|
|
3366
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel });
|
|
2735
3367
|
}
|
|
2736
3368
|
|
|
2737
3369
|
// src/core/graphs/CubeGraph.ts
|
|
@@ -2783,8 +3415,6 @@ var EDGES = [
|
|
|
2783
3415
|
];
|
|
2784
3416
|
function renderCubeGraph(ctx, w, h, data, options) {
|
|
2785
3417
|
clearCanvas(ctx, w, h);
|
|
2786
|
-
const font = options.fontFamily;
|
|
2787
|
-
const cf = options.customFont;
|
|
2788
3418
|
const fs = options.fontSize;
|
|
2789
3419
|
const topPad = options.title ? 100 : 60;
|
|
2790
3420
|
let bottomPad = 40;
|
|
@@ -2801,8 +3431,7 @@ function renderCubeGraph(ctx, w, h, data, options) {
|
|
|
2801
3431
|
availH,
|
|
2802
3432
|
Math.min(availW * 0.5, availH * 0.55),
|
|
2803
3433
|
fs,
|
|
2804
|
-
|
|
2805
|
-
cf
|
|
3434
|
+
options
|
|
2806
3435
|
);
|
|
2807
3436
|
const scale = fit.scale;
|
|
2808
3437
|
const cubeCenter = project(0.5, 0.5, 0.5, 0, 0, scale);
|
|
@@ -2830,7 +3459,7 @@ function renderCubeGraph(ctx, w, h, data, options) {
|
|
|
2830
3459
|
ctx.lineTo(v2[b][0], v2[b][1]);
|
|
2831
3460
|
ctx.stroke();
|
|
2832
3461
|
}
|
|
2833
|
-
drawAxes(ctx, data, cx, cy, scale, w, h,
|
|
3462
|
+
drawAxes(ctx, data, cx, cy, scale, w, h, options, fs, fit.names, fit.nameSize);
|
|
2834
3463
|
const cubeCenter2D = project(0.5, 0.5, 0.5, cx, cy, scale);
|
|
2835
3464
|
for (const pt of data.points) {
|
|
2836
3465
|
const [px, py] = project(pt.z, pt.y, pt.x, cx, cy, scale);
|
|
@@ -2845,7 +3474,7 @@ function renderCubeGraph(ctx, w, h, data, options) {
|
|
|
2845
3474
|
autoDy = autoDy / len * 40;
|
|
2846
3475
|
const dx = autoDx + pt.labelDx;
|
|
2847
3476
|
const dy = autoDy + pt.labelDy;
|
|
2848
|
-
ctx.font = getFont(fs.dataLabel + 10,
|
|
3477
|
+
ctx.font = getFont(fs.dataLabel + 10, options, "bold");
|
|
2849
3478
|
ctx.textAlign = dx >= 0 ? "left" : "right";
|
|
2850
3479
|
ctx.textBaseline = "middle";
|
|
2851
3480
|
const anchor = nudgeInside(ctx, pt.label, px + dx + (dx >= 0 ? 4 : -4), py + dy, w, h);
|
|
@@ -2866,9 +3495,10 @@ function renderCubeGraph(ctx, w, h, data, options) {
|
|
|
2866
3495
|
const plotX = cubeLeft;
|
|
2867
3496
|
const plotW = cubeRight - cubeLeft;
|
|
2868
3497
|
if (options.title) {
|
|
3498
|
+
const titleFont = sansFont(options);
|
|
2869
3499
|
const yAxisTop = project(0, 1.25, 0, cx, cy, scale);
|
|
2870
3500
|
ctx.fillStyle = "#000";
|
|
2871
|
-
ctx.font = `bold ${fs.title}px
|
|
3501
|
+
ctx.font = `bold ${fs.title}px ${titleFont}`;
|
|
2872
3502
|
ctx.textAlign = "center";
|
|
2873
3503
|
ctx.textBaseline = "bottom";
|
|
2874
3504
|
drawFloatingLabel(
|
|
@@ -2879,13 +3509,13 @@ function renderCubeGraph(ctx, w, h, data, options) {
|
|
|
2879
3509
|
w,
|
|
2880
3510
|
h,
|
|
2881
3511
|
fs.title,
|
|
2882
|
-
(size) => `bold ${size}px
|
|
3512
|
+
(size) => `bold ${size}px ${titleFont}`
|
|
2883
3513
|
);
|
|
2884
3514
|
}
|
|
2885
3515
|
const zAxisEnd = project(1.25, 0, 0, cx, cy, scale);
|
|
2886
3516
|
const zHighRef = project(1, 0, 0, cx, cy, scale);
|
|
2887
3517
|
const sourceY = Math.max(zAxisEnd[1] + 20 + fs.axisLabel, zHighRef[1] + 10 + fs.axisLabel * 0.9) + 47;
|
|
2888
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: sourceY, source: options.source, footnotes: options.footnotes, fontSize: fs.dataLabel });
|
|
3518
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: sourceY, source: options.source, footnotes: options.footnotes, fontSize: fs.dataLabel });
|
|
2889
3519
|
}
|
|
2890
3520
|
function drawArrow(ctx, x1, y1, x2, y2) {
|
|
2891
3521
|
const headLen = 10;
|
|
@@ -2932,11 +3562,11 @@ function centerAt(w, topPad, availH, s) {
|
|
|
2932
3562
|
const c = project(0.5, 0.5, 0.5, 0, 0, s);
|
|
2933
3563
|
return [w / 2 - c[0], topPad + availH / 2 - c[1]];
|
|
2934
3564
|
}
|
|
2935
|
-
function fitCubeScale(ctx, data, w, h, topPad, availH, maxScale, fs,
|
|
3565
|
+
function fitCubeScale(ctx, data, w, h, topPad, availH, maxScale, fs, options) {
|
|
2936
3566
|
ctx.save();
|
|
2937
3567
|
let nameSize = fs.axisLabel;
|
|
2938
|
-
const makeNameFont = (size) => getFont(size,
|
|
2939
|
-
const dirFont = getFont(fs.axisLabel * 0.9,
|
|
3568
|
+
const makeNameFont = (size) => getFont(size, options, "bold");
|
|
3569
|
+
const dirFont = getFont(fs.axisLabel * 0.9, options, "normal");
|
|
2940
3570
|
let names = { x: [data.xAxis.name], y: [data.yAxis.name], z: [data.zAxis.name] };
|
|
2941
3571
|
const fits = (s) => {
|
|
2942
3572
|
const [cx, cy] = centerAt(w, topPad, availH, s);
|
|
@@ -2976,7 +3606,7 @@ function fitCubeScale(ctx, data, w, h, topPad, availH, maxScale, fs, font, cf) {
|
|
|
2976
3606
|
ctx.restore();
|
|
2977
3607
|
return { scale, names, nameSize };
|
|
2978
3608
|
}
|
|
2979
|
-
function drawAxes(ctx, data, cx, cy, scale, w, h,
|
|
3609
|
+
function drawAxes(ctx, data, cx, cy, scale, w, h, options, fs, names, nameSize) {
|
|
2980
3610
|
const ext = 1.25;
|
|
2981
3611
|
ctx.strokeStyle = "#000";
|
|
2982
3612
|
ctx.fillStyle = "#000";
|
|
@@ -2990,8 +3620,8 @@ function drawAxes(ctx, data, cx, cy, scale, w, h, font, cf, fs, names, nameSize)
|
|
|
2990
3620
|
const zStart = project(0, 0, 1, cx, cy, scale);
|
|
2991
3621
|
const zEnd = project(0, 0, ext, cx, cy, scale);
|
|
2992
3622
|
drawArrow(ctx, zStart[0], zStart[1], zEnd[0], zEnd[1]);
|
|
2993
|
-
const nameFont = getFont(nameSize,
|
|
2994
|
-
const dirFont = getFont(fs.axisLabel * 0.9,
|
|
3623
|
+
const nameFont = getFont(nameSize, options, "bold");
|
|
3624
|
+
const dirFont = getFont(fs.axisLabel * 0.9, options, "normal");
|
|
2995
3625
|
ctx.fillStyle = "#000";
|
|
2996
3626
|
for (const t of axisTexts(data, cx, cy, scale, names)) {
|
|
2997
3627
|
if (t.lines.every((l) => !l)) continue;
|
|
@@ -3019,11 +3649,11 @@ function renderDeviationAGraph(ctx, w, h, data, options) {
|
|
|
3019
3649
|
options.legendLabel1 || data.precipLabel,
|
|
3020
3650
|
options.legendLabel2 || data.tempLabel
|
|
3021
3651
|
];
|
|
3022
|
-
const legendW = showLegend && legendPos === "right" && !data.insideLegend ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5) : 0;
|
|
3652
|
+
const legendW = showLegend && legendPos === "right" && !data.insideLegend ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5, options) : 0;
|
|
3023
3653
|
let nameW = 0;
|
|
3024
3654
|
if (data.tempAxisName || data.precipAxisName) {
|
|
3025
3655
|
ctx.save();
|
|
3026
|
-
ctx.font = getFont(options.fontSize.axisLabel, options
|
|
3656
|
+
ctx.font = getFont(options.fontSize.axisLabel, options, "bold");
|
|
3027
3657
|
nameW = ctx.measureText("\uAC00").width + 12;
|
|
3028
3658
|
ctx.restore();
|
|
3029
3659
|
}
|
|
@@ -3034,6 +3664,7 @@ function renderDeviationAGraph(ctx, w, h, data, options) {
|
|
|
3034
3664
|
legendLabels,
|
|
3035
3665
|
options.fontSize.dataLabel * 0.85 + 5,
|
|
3036
3666
|
w - padLeft - padRight,
|
|
3667
|
+
options,
|
|
3037
3668
|
["rect", data.monthInterval === 12 ? "line" : "circle"]
|
|
3038
3669
|
) : 0;
|
|
3039
3670
|
const padding = {
|
|
@@ -3053,8 +3684,6 @@ function renderDeviationAGraph(ctx, w, h, data, options) {
|
|
|
3053
3684
|
const plotY = padding.top;
|
|
3054
3685
|
const plotW = w - padding.left - padding.right;
|
|
3055
3686
|
const plotH = h - padding.top - padding.bottom;
|
|
3056
|
-
const font = options.fontFamily;
|
|
3057
|
-
const customFont = options.customFont;
|
|
3058
3687
|
const indices = INTERVAL_INDICES2[data.monthInterval] ?? INTERVAL_INDICES2[12];
|
|
3059
3688
|
const tempDevs = data.months.map((m, i) => m.temp - data.baseMonths[i].temp);
|
|
3060
3689
|
const precipDevs = data.months.map((m, i) => m.precip - data.baseMonths[i].precip);
|
|
@@ -3077,8 +3706,8 @@ function renderDeviationAGraph(ctx, w, h, data, options) {
|
|
|
3077
3706
|
if (tempAxis.step === 0) {
|
|
3078
3707
|
tempAxis.step = Math.max(0.5, Math.round((tempAxis.max - tempAxis.min) / 6 * 10) / 10);
|
|
3079
3708
|
}
|
|
3080
|
-
const tempTickW = drawDeviationYAxis(ctx, padding, w, h, tempAxis, "left", data.tempLabel,
|
|
3081
|
-
const precipTickW = drawDeviationYAxis(ctx, padding, w, h, precipAxis, "right", data.precipLabel,
|
|
3709
|
+
const tempTickW = drawDeviationYAxis(ctx, padding, w, h, tempAxis, "left", data.tempLabel, options, options.fontSize);
|
|
3710
|
+
const precipTickW = drawDeviationYAxis(ctx, padding, w, h, precipAxis, "right", data.precipLabel, options, options.fontSize);
|
|
3082
3711
|
const totalSlots = indices.length;
|
|
3083
3712
|
const slotW = plotW / totalSlots;
|
|
3084
3713
|
ctx.strokeStyle = "#000";
|
|
@@ -3094,7 +3723,7 @@ function renderDeviationAGraph(ctx, w, h, data, options) {
|
|
|
3094
3723
|
ctx.stroke();
|
|
3095
3724
|
}
|
|
3096
3725
|
ctx.fillStyle = "#000";
|
|
3097
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
3726
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
3098
3727
|
ctx.textAlign = "center";
|
|
3099
3728
|
ctx.textBaseline = "top";
|
|
3100
3729
|
for (let s = 0; s < totalSlots; s++) {
|
|
@@ -3164,7 +3793,7 @@ function renderDeviationAGraph(ctx, w, h, data, options) {
|
|
|
3164
3793
|
inkRects.push({ x0: cx - 6, y0: y - 6, x1: cx + 6, y1: y + 6 });
|
|
3165
3794
|
}
|
|
3166
3795
|
ctx.fillStyle = "#000";
|
|
3167
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
3796
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
3168
3797
|
ctx.textAlign = "center";
|
|
3169
3798
|
ctx.textBaseline = "top";
|
|
3170
3799
|
ctx.fillText("(\uC6D4)", plotX + plotW + 30, plotY + plotH + 12);
|
|
@@ -3193,12 +3822,13 @@ function renderDeviationAGraph(ctx, w, h, data, options) {
|
|
|
3193
3822
|
canvasW: w,
|
|
3194
3823
|
canvasH: h,
|
|
3195
3824
|
fontSize: options.fontSize.dataLabel * 0.9,
|
|
3196
|
-
font: getFont(options.fontSize.dataLabel * 0.9,
|
|
3825
|
+
font: getFont(options.fontSize.dataLabel * 0.9, options, "bold"),
|
|
3197
3826
|
avoid: inkRects
|
|
3198
3827
|
});
|
|
3199
3828
|
} else if (showLegend) {
|
|
3200
3829
|
drawLegend({
|
|
3201
3830
|
ctx,
|
|
3831
|
+
fonts: options,
|
|
3202
3832
|
items: [
|
|
3203
3833
|
{ type: "rect", fillStyle: "#888", strokeStyle: "#444", label: legendLabels[0] },
|
|
3204
3834
|
{ type: data.monthInterval === 12 ? "line" : "circle", fillStyle: "#000", label: legendLabels[1] }
|
|
@@ -3223,8 +3853,7 @@ function renderDeviationAGraph(ctx, w, h, data, options) {
|
|
|
3223
3853
|
plotH,
|
|
3224
3854
|
h,
|
|
3225
3855
|
options.fontSize.axisLabel,
|
|
3226
|
-
|
|
3227
|
-
customFont
|
|
3856
|
+
options
|
|
3228
3857
|
);
|
|
3229
3858
|
}
|
|
3230
3859
|
if (data.precipAxisName) {
|
|
@@ -3236,14 +3865,13 @@ function renderDeviationAGraph(ctx, w, h, data, options) {
|
|
|
3236
3865
|
plotH,
|
|
3237
3866
|
h,
|
|
3238
3867
|
options.fontSize.axisLabel,
|
|
3239
|
-
|
|
3240
|
-
customFont
|
|
3868
|
+
options
|
|
3241
3869
|
);
|
|
3242
3870
|
}
|
|
3243
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
3244
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
3871
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
3872
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
3245
3873
|
}
|
|
3246
|
-
function drawDeviationYAxis(ctx, padding, width, height, axis, side, label,
|
|
3874
|
+
function drawDeviationYAxis(ctx, padding, width, height, axis, side, label, fonts, fontSize) {
|
|
3247
3875
|
const plotX = padding.left;
|
|
3248
3876
|
const plotY = padding.top;
|
|
3249
3877
|
const plotW = width - padding.left - padding.right;
|
|
@@ -3256,7 +3884,7 @@ function drawDeviationYAxis(ctx, padding, width, height, axis, side, label, font
|
|
|
3256
3884
|
ctx.lineTo(x, plotY + plotH);
|
|
3257
3885
|
ctx.stroke();
|
|
3258
3886
|
ctx.fillStyle = "#000";
|
|
3259
|
-
ctx.font = getFont(fontSize.tick,
|
|
3887
|
+
ctx.font = getFont(fontSize.tick, fonts, "bold");
|
|
3260
3888
|
ctx.textBaseline = "middle";
|
|
3261
3889
|
ctx.textAlign = side === "left" ? "right" : "left";
|
|
3262
3890
|
let maxTickW = 0;
|
|
@@ -3281,7 +3909,7 @@ function drawDeviationYAxis(ctx, padding, width, height, axis, side, label, font
|
|
|
3281
3909
|
}
|
|
3282
3910
|
if (label) {
|
|
3283
3911
|
ctx.save();
|
|
3284
|
-
const makeFont = (size) => getFont(size,
|
|
3912
|
+
const makeFont = (size) => getFont(size, fonts, "bold");
|
|
3285
3913
|
ctx.fillStyle = "#000";
|
|
3286
3914
|
ctx.textBaseline = "bottom";
|
|
3287
3915
|
ctx.textAlign = side === "left" ? "right" : "left";
|
|
@@ -3293,7 +3921,7 @@ function drawDeviationYAxis(ctx, padding, width, height, axis, side, label, font
|
|
|
3293
3921
|
}
|
|
3294
3922
|
return maxTickW;
|
|
3295
3923
|
}
|
|
3296
|
-
function drawVerticalAxisName(ctx, name, cx, plotY, plotH, canvasH, fontSize,
|
|
3924
|
+
function drawVerticalAxisName(ctx, name, cx, plotY, plotH, canvasH, fontSize, fonts) {
|
|
3297
3925
|
const chars = [...name].filter((c) => c.trim() !== "");
|
|
3298
3926
|
if (chars.length === 0) return;
|
|
3299
3927
|
const stack = (size2) => (chars.length - 1) * size2 * 1.15 + size2;
|
|
@@ -3303,7 +3931,7 @@ function drawVerticalAxisName(ctx, name, cx, plotY, plotH, canvasH, fontSize, fo
|
|
|
3303
3931
|
}
|
|
3304
3932
|
if (stack(size) > canvasH - EDGE * 2) size = (canvasH - EDGE * 2) / stack(1);
|
|
3305
3933
|
ctx.save();
|
|
3306
|
-
ctx.font = getFont(size,
|
|
3934
|
+
ctx.font = getFont(size, fonts, "bold");
|
|
3307
3935
|
ctx.fillStyle = "#000";
|
|
3308
3936
|
ctx.textAlign = "center";
|
|
3309
3937
|
ctx.textBaseline = "middle";
|
|
@@ -3331,12 +3959,13 @@ function renderDeviationBGraph(ctx, w, h, data, options) {
|
|
|
3331
3959
|
options.legendLabel1 || data.precipDiffLabel,
|
|
3332
3960
|
options.legendLabel2 || data.tempDiffLabel
|
|
3333
3961
|
];
|
|
3334
|
-
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5) : 0;
|
|
3962
|
+
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5, options) : 0;
|
|
3335
3963
|
const legendReserve = showLegend && legendPos === "bottom" ? measureBottomLegend(
|
|
3336
3964
|
ctx,
|
|
3337
3965
|
legendLabels,
|
|
3338
3966
|
options.fontSize.dataLabel * 0.85 + 5,
|
|
3339
3967
|
w - 130 - (130 + legendW),
|
|
3968
|
+
options,
|
|
3340
3969
|
["rect", "circle"]
|
|
3341
3970
|
) : 0;
|
|
3342
3971
|
const padding = {
|
|
@@ -3356,8 +3985,6 @@ function renderDeviationBGraph(ctx, w, h, data, options) {
|
|
|
3356
3985
|
const plotY = padding.top;
|
|
3357
3986
|
const plotW = w - padding.left - padding.right;
|
|
3358
3987
|
const plotH = h - padding.top - padding.bottom;
|
|
3359
|
-
const font = options.fontFamily;
|
|
3360
|
-
const customFont = options.customFont;
|
|
3361
3988
|
const regions = data.regions;
|
|
3362
3989
|
const n = regions.length;
|
|
3363
3990
|
const precipDiffs = regions.map((r) => r.precip - data.basePrecip);
|
|
@@ -3378,8 +4005,8 @@ function renderDeviationBGraph(ctx, w, h, data, options) {
|
|
|
3378
4005
|
tempAxis.min = -tempAbs;
|
|
3379
4006
|
tempAxis.max = tempAbs;
|
|
3380
4007
|
}
|
|
3381
|
-
drawDevBYAxis(ctx, padding, w, h, tempAxis, "left", data.tempUnit,
|
|
3382
|
-
drawDevBYAxis(ctx, padding, w, h, precipAxis, "right", data.precipUnit,
|
|
4008
|
+
drawDevBYAxis(ctx, padding, w, h, tempAxis, "left", data.tempUnit, options, options.fontSize);
|
|
4009
|
+
drawDevBYAxis(ctx, padding, w, h, precipAxis, "right", data.precipUnit, options, options.fontSize);
|
|
3383
4010
|
ctx.strokeStyle = "#000";
|
|
3384
4011
|
ctx.lineWidth = 2;
|
|
3385
4012
|
ctx.beginPath();
|
|
@@ -3396,10 +4023,10 @@ function renderDeviationBGraph(ctx, w, h, data, options) {
|
|
|
3396
4023
|
const slotW = plotW / n;
|
|
3397
4024
|
const barWidth = Math.min(slotW * 0.5, 80);
|
|
3398
4025
|
ctx.fillStyle = "#000";
|
|
3399
|
-
ctx.font = getFont(options.fontSize.tick * 1.2,
|
|
4026
|
+
ctx.font = getFont(options.fontSize.tick * 1.2, options, "bold");
|
|
3400
4027
|
ctx.textAlign = "center";
|
|
3401
4028
|
ctx.textBaseline = "top";
|
|
3402
|
-
const regionFont = (size) => getFont(size,
|
|
4029
|
+
const regionFont = (size) => getFont(size, options, "bold");
|
|
3403
4030
|
for (let i = 0; i < n; i++) {
|
|
3404
4031
|
const cx = plotX + slotW * i + slotW / 2;
|
|
3405
4032
|
drawFloatingLabel(
|
|
@@ -3435,7 +4062,7 @@ function renderDeviationBGraph(ctx, w, h, data, options) {
|
|
|
3435
4062
|
ctx.fill();
|
|
3436
4063
|
}
|
|
3437
4064
|
if (options.showDataLabels) {
|
|
3438
|
-
ctx.font = getFont(options.fontSize.dataLabel,
|
|
4065
|
+
ctx.font = getFont(options.fontSize.dataLabel, options, "bold");
|
|
3439
4066
|
ctx.textAlign = "center";
|
|
3440
4067
|
for (let i = 0; i < n; i++) {
|
|
3441
4068
|
const cx = plotX + slotW * i + slotW / 2;
|
|
@@ -3453,6 +4080,7 @@ function renderDeviationBGraph(ctx, w, h, data, options) {
|
|
|
3453
4080
|
if (showLegend) {
|
|
3454
4081
|
drawLegend({
|
|
3455
4082
|
ctx,
|
|
4083
|
+
fonts: options,
|
|
3456
4084
|
items: [
|
|
3457
4085
|
{ type: "rect", fillStyle: "#888", strokeStyle: "#444", label: legendLabels[0] },
|
|
3458
4086
|
{ type: "circle", fillStyle: "#000", label: legendLabels[1] }
|
|
@@ -3468,10 +4096,10 @@ function renderDeviationBGraph(ctx, w, h, data, options) {
|
|
|
3468
4096
|
rightGap: 80
|
|
3469
4097
|
});
|
|
3470
4098
|
}
|
|
3471
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
3472
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel });
|
|
4099
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
4100
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel });
|
|
3473
4101
|
}
|
|
3474
|
-
function drawDevBYAxis(ctx, padding, width, height, axis, side, label,
|
|
4102
|
+
function drawDevBYAxis(ctx, padding, width, height, axis, side, label, fonts, fontSize) {
|
|
3475
4103
|
const plotX = padding.left;
|
|
3476
4104
|
const plotY = padding.top;
|
|
3477
4105
|
const plotW = width - padding.left - padding.right;
|
|
@@ -3484,7 +4112,7 @@ function drawDevBYAxis(ctx, padding, width, height, axis, side, label, fontFamil
|
|
|
3484
4112
|
ctx.lineTo(x, plotY + plotH);
|
|
3485
4113
|
ctx.stroke();
|
|
3486
4114
|
ctx.fillStyle = "#000";
|
|
3487
|
-
ctx.font = getFont(fontSize.tick,
|
|
4115
|
+
ctx.font = getFont(fontSize.tick, fonts, "bold");
|
|
3488
4116
|
ctx.textBaseline = "middle";
|
|
3489
4117
|
ctx.textAlign = side === "left" ? "right" : "left";
|
|
3490
4118
|
const tickCount = Math.round((axis.max - axis.min) / axis.step);
|
|
@@ -3506,7 +4134,7 @@ function drawDevBYAxis(ctx, padding, width, height, axis, side, label, fontFamil
|
|
|
3506
4134
|
ctx.fillText(valStr, tx, y);
|
|
3507
4135
|
}
|
|
3508
4136
|
ctx.save();
|
|
3509
|
-
ctx.font = getFont(fontSize.axisLabel,
|
|
4137
|
+
ctx.font = getFont(fontSize.axisLabel, fonts, "bold");
|
|
3510
4138
|
ctx.fillStyle = "#000";
|
|
3511
4139
|
ctx.textBaseline = "bottom";
|
|
3512
4140
|
const labelX = side === "left" ? x - 12 : x + 12;
|
|
@@ -3572,19 +4200,18 @@ function drawMarkerLegendIcon(ctx, type, cx, cy) {
|
|
|
3572
4200
|
}
|
|
3573
4201
|
function renderHythergraph(ctx, w, h, data, options) {
|
|
3574
4202
|
clearCanvas(ctx, w, h);
|
|
3575
|
-
const font = options.fontFamily;
|
|
3576
|
-
const cf = options.customFont;
|
|
3577
4203
|
const fs = options.fontSize;
|
|
3578
4204
|
const showLegend = options.showLegend && data.series.length > 0;
|
|
3579
4205
|
const legendPos = options.legendPosition;
|
|
3580
4206
|
const legendLabels = data.series.map((s) => s.label);
|
|
3581
|
-
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, legendLabels, fs.dataLabel * 0.85 + 5) : 0;
|
|
4207
|
+
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, legendLabels, fs.dataLabel * 0.85 + 5, options) : 0;
|
|
3582
4208
|
const legendReserve = showLegend && legendPos === "bottom" ? 65 + layoutBottomLegend(
|
|
3583
4209
|
ctx,
|
|
3584
4210
|
legendLabels,
|
|
3585
4211
|
data.series.map(() => 36),
|
|
3586
4212
|
fs.dataLabel * 0.85 + 5,
|
|
3587
4213
|
w - 80 - (80 + legendW),
|
|
4214
|
+
options,
|
|
3588
4215
|
{ iconGap: 8 }
|
|
3589
4216
|
).boxH + 2 : 0;
|
|
3590
4217
|
const padding = {
|
|
@@ -3643,7 +4270,7 @@ function renderHythergraph(ctx, w, h, data, options) {
|
|
|
3643
4270
|
ctx.lineTo(plotX + plotW, plotY + plotH);
|
|
3644
4271
|
ctx.stroke();
|
|
3645
4272
|
ctx.fillStyle = "#000";
|
|
3646
|
-
ctx.font = getFont(fs.tick,
|
|
4273
|
+
ctx.font = getFont(fs.tick, options, "bold");
|
|
3647
4274
|
ctx.textAlign = "center";
|
|
3648
4275
|
ctx.textBaseline = "top";
|
|
3649
4276
|
for (let v = xMin; v <= xMax + xStep * 0.01; v += xStep) {
|
|
@@ -3655,7 +4282,7 @@ function renderHythergraph(ctx, w, h, data, options) {
|
|
|
3655
4282
|
ctx.stroke();
|
|
3656
4283
|
const isLast = v + xStep > xMax + xStep * 0.01;
|
|
3657
4284
|
if (!(isLast && data.xUnit)) {
|
|
3658
|
-
ctx.fillText(
|
|
4285
|
+
ctx.fillText(formatTick4(v), x, plotY + plotH + 10);
|
|
3659
4286
|
}
|
|
3660
4287
|
}
|
|
3661
4288
|
ctx.textAlign = "right";
|
|
@@ -3669,12 +4296,12 @@ function renderHythergraph(ctx, w, h, data, options) {
|
|
|
3669
4296
|
ctx.stroke();
|
|
3670
4297
|
const isLast = v + yStep > yMax + yStep * 0.01;
|
|
3671
4298
|
if (!(isLast && data.yUnit)) {
|
|
3672
|
-
ctx.fillText(
|
|
4299
|
+
ctx.fillText(formatTick4(v), plotX - 10, y);
|
|
3673
4300
|
}
|
|
3674
4301
|
}
|
|
3675
|
-
ctx.font = getFont(fs.axisLabel,
|
|
4302
|
+
ctx.font = getFont(fs.axisLabel, options, "bold");
|
|
3676
4303
|
ctx.fillStyle = "#000";
|
|
3677
|
-
const unitFont = (size) => getFont(size,
|
|
4304
|
+
const unitFont = (size) => getFont(size, options, "bold");
|
|
3678
4305
|
if (data.xUnit) {
|
|
3679
4306
|
ctx.textAlign = "right";
|
|
3680
4307
|
ctx.textBaseline = "top";
|
|
@@ -3716,7 +4343,7 @@ function renderHythergraph(ctx, w, h, data, options) {
|
|
|
3716
4343
|
drawMarker3(ctx, marker, cx, cy, 5);
|
|
3717
4344
|
{
|
|
3718
4345
|
ctx.fillStyle = "#000";
|
|
3719
|
-
ctx.font = getFont(fs.dataLabel,
|
|
4346
|
+
ctx.font = getFont(fs.dataLabel, options, "bold");
|
|
3720
4347
|
ctx.textAlign = "left";
|
|
3721
4348
|
ctx.textBaseline = "bottom";
|
|
3722
4349
|
ctx.fillText(mLabels[i], cx + 9, cy - 5);
|
|
@@ -3726,9 +4353,9 @@ function renderHythergraph(ctx, w, h, data, options) {
|
|
|
3726
4353
|
}
|
|
3727
4354
|
if (showLegend) {
|
|
3728
4355
|
const lfSize = fs.dataLabel * 0.85 + 5;
|
|
3729
|
-
const
|
|
4356
|
+
const LEGEND_FONT = sansFont(options);
|
|
3730
4357
|
ctx.save();
|
|
3731
|
-
ctx.font = `bold ${lfSize}px ${
|
|
4358
|
+
ctx.font = `bold ${lfSize}px ${LEGEND_FONT}`;
|
|
3732
4359
|
const iconW = 36;
|
|
3733
4360
|
const iconGap = 8;
|
|
3734
4361
|
const pad = 12;
|
|
@@ -3744,6 +4371,7 @@ function renderHythergraph(ctx, w, h, data, options) {
|
|
|
3744
4371
|
data.series.map(() => iconW),
|
|
3745
4372
|
lfSize,
|
|
3746
4373
|
boxW,
|
|
4374
|
+
options,
|
|
3747
4375
|
{ iconGap, padding: pad, spacing }
|
|
3748
4376
|
);
|
|
3749
4377
|
const boxH = layout.boxH;
|
|
@@ -3772,7 +4400,7 @@ function renderHythergraph(ctx, w, h, data, options) {
|
|
|
3772
4400
|
ctx.stroke();
|
|
3773
4401
|
ctx.setLineDash([]);
|
|
3774
4402
|
drawMarkerLegendIcon(ctx, MARKERS[i % MARKERS.length], cx + iconW / 2, cy);
|
|
3775
|
-
ctx.font = `bold ${layout.fontSize}px ${
|
|
4403
|
+
ctx.font = `bold ${layout.fontSize}px ${LEGEND_FONT}`;
|
|
3776
4404
|
ctx.fillStyle = "#000";
|
|
3777
4405
|
ctx.textAlign = "left";
|
|
3778
4406
|
ctx.textBaseline = "middle";
|
|
@@ -3807,7 +4435,7 @@ function renderHythergraph(ctx, w, h, data, options) {
|
|
|
3807
4435
|
ctx.stroke();
|
|
3808
4436
|
ctx.setLineDash([]);
|
|
3809
4437
|
drawMarkerLegendIcon(ctx, MARKERS[i % MARKERS.length], ix + iconW / 2, cy);
|
|
3810
|
-
ctx.font = `bold ${lfSize}px ${
|
|
4438
|
+
ctx.font = `bold ${lfSize}px ${LEGEND_FONT}`;
|
|
3811
4439
|
ctx.fillStyle = "#000";
|
|
3812
4440
|
ctx.textAlign = "left";
|
|
3813
4441
|
ctx.textBaseline = "middle";
|
|
@@ -3817,10 +4445,10 @@ function renderHythergraph(ctx, w, h, data, options) {
|
|
|
3817
4445
|
}
|
|
3818
4446
|
ctx.restore();
|
|
3819
4447
|
}
|
|
3820
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });
|
|
3821
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });
|
|
4448
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });
|
|
4449
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });
|
|
3822
4450
|
}
|
|
3823
|
-
function
|
|
4451
|
+
function formatTick4(v) {
|
|
3824
4452
|
return Math.abs(v) < 1e-4 ? "0" : Number(v.toFixed(2)).toString();
|
|
3825
4453
|
}
|
|
3826
4454
|
|
|
@@ -3851,12 +4479,13 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
3851
4479
|
options.legendLabel1 || data.maleLabel,
|
|
3852
4480
|
options.legendLabel2 || data.femaleLabel
|
|
3853
4481
|
];
|
|
3854
|
-
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5) : 0;
|
|
4482
|
+
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, legendLabels, options.fontSize.dataLabel * 0.85 + 5, options) : 0;
|
|
3855
4483
|
const legendReserve = showLegend && legendPos === "bottom" ? measureBottomLegend(
|
|
3856
4484
|
ctx,
|
|
3857
4485
|
legendLabels,
|
|
3858
4486
|
options.fontSize.dataLabel * 0.85 + 5,
|
|
3859
|
-
w - 60 - (80 + legendW)
|
|
4487
|
+
w - 60 - (80 + legendW),
|
|
4488
|
+
options
|
|
3860
4489
|
) : 0;
|
|
3861
4490
|
const padding = {
|
|
3862
4491
|
top: options.title ? 100 : 50,
|
|
@@ -3875,8 +4504,6 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
3875
4504
|
const plotY = padding.top;
|
|
3876
4505
|
const plotW = w - padding.left - padding.right;
|
|
3877
4506
|
const plotH = h - padding.top - padding.bottom;
|
|
3878
|
-
const font = options.fontFamily;
|
|
3879
|
-
const customFont = options.customFont;
|
|
3880
4507
|
const displayAges = data.ages;
|
|
3881
4508
|
const n = data.numericAgeAxis && displayAges.length > 0 ? displayAges.length : AGE_GROUPS.length;
|
|
3882
4509
|
const isPercent = data.unit === "percent";
|
|
@@ -3921,7 +4548,7 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
3921
4548
|
ctx.lineTo(plotX + plotW, plotY + plotH);
|
|
3922
4549
|
ctx.stroke();
|
|
3923
4550
|
ctx.save();
|
|
3924
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
4551
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
3925
4552
|
const tickStep = pickTickStep(maxVal, halfW, ctx.measureText(fmtTick(maxVal)).width);
|
|
3926
4553
|
ctx.restore();
|
|
3927
4554
|
for (let v = tickStep; v <= maxVal; v += tickStep) {
|
|
@@ -3958,7 +4585,7 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
3958
4585
|
ctx.fillStyle = "#000";
|
|
3959
4586
|
ctx.textBaseline = "middle";
|
|
3960
4587
|
if (data.numericAgeAxis) {
|
|
3961
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
4588
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
3962
4589
|
ctx.textAlign = "right";
|
|
3963
4590
|
const ageStep = 20;
|
|
3964
4591
|
const topAge = n * 5;
|
|
@@ -3982,13 +4609,13 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
3982
4609
|
w,
|
|
3983
4610
|
h,
|
|
3984
4611
|
options.fontSize.tick,
|
|
3985
|
-
(size) => getFont(size,
|
|
4612
|
+
(size) => getFont(size, options, "bold")
|
|
3986
4613
|
);
|
|
3987
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
4614
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
3988
4615
|
ctx.textBaseline = "middle";
|
|
3989
4616
|
}
|
|
3990
4617
|
} else {
|
|
3991
|
-
ctx.font = getFont(options.fontSize.tick * 0.75,
|
|
4618
|
+
ctx.font = getFont(options.fontSize.tick * 0.75, options, "bold");
|
|
3992
4619
|
for (let i = 0; i < n; i++) {
|
|
3993
4620
|
const y = plotY + plotH - (i + 1) * barH + barH / 2;
|
|
3994
4621
|
if (side === "center") {
|
|
@@ -4003,7 +4630,7 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
4003
4630
|
}
|
|
4004
4631
|
}
|
|
4005
4632
|
}
|
|
4006
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
4633
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
4007
4634
|
ctx.textBaseline = "top";
|
|
4008
4635
|
ctx.fillStyle = "#000";
|
|
4009
4636
|
const xTickValues = [];
|
|
@@ -4045,7 +4672,7 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
4045
4672
|
ctx.fillText(fmtTick(rv), rx, plotY + plotH + 10);
|
|
4046
4673
|
}
|
|
4047
4674
|
}
|
|
4048
|
-
ctx.font = getFont(options.fontSize.axisLabel,
|
|
4675
|
+
ctx.font = getFont(options.fontSize.axisLabel, options, "bold");
|
|
4049
4676
|
ctx.textBaseline = "bottom";
|
|
4050
4677
|
ctx.fillStyle = "#000";
|
|
4051
4678
|
ctx.textAlign = "center";
|
|
@@ -4053,7 +4680,7 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
4053
4680
|
ctx.fillText(data.femaleLabel, centerX + halfW / 2, plotY - 16);
|
|
4054
4681
|
ctx.save();
|
|
4055
4682
|
if (data.axisLabelInline) {
|
|
4056
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
4683
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
4057
4684
|
const lastTickHalfW = ctx.measureText(fmtTick(maxVal)).width / 2;
|
|
4058
4685
|
ctx.textAlign = "left";
|
|
4059
4686
|
ctx.textBaseline = "top";
|
|
@@ -4065,10 +4692,10 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
4065
4692
|
w,
|
|
4066
4693
|
h,
|
|
4067
4694
|
options.fontSize.tick,
|
|
4068
|
-
(size) => getFont(size,
|
|
4695
|
+
(size) => getFont(size, options, "bold")
|
|
4069
4696
|
);
|
|
4070
4697
|
} else {
|
|
4071
|
-
ctx.font = getFont(options.fontSize.axisLabel * 0.85,
|
|
4698
|
+
ctx.font = getFont(options.fontSize.axisLabel * 0.85, options, "bold");
|
|
4072
4699
|
const unitY = plotY + plotH + 10 + options.fontSize.tick + 22;
|
|
4073
4700
|
ctx.textAlign = "left";
|
|
4074
4701
|
drawFloatingLabel(
|
|
@@ -4079,12 +4706,12 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
4079
4706
|
w,
|
|
4080
4707
|
h,
|
|
4081
4708
|
options.fontSize.axisLabel * 0.85,
|
|
4082
|
-
(size) => getFont(size,
|
|
4709
|
+
(size) => getFont(size, options, "bold")
|
|
4083
4710
|
);
|
|
4084
4711
|
}
|
|
4085
4712
|
ctx.restore();
|
|
4086
4713
|
if (options.showDataLabels) {
|
|
4087
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
4714
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options, "bold");
|
|
4088
4715
|
ctx.fillStyle = "#000";
|
|
4089
4716
|
for (let i = 0; i < n; i++) {
|
|
4090
4717
|
const y = plotY + plotH - (i + 1) * barH + barH / 2;
|
|
@@ -4102,10 +4729,11 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
4102
4729
|
}
|
|
4103
4730
|
}
|
|
4104
4731
|
}
|
|
4105
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
4732
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
4106
4733
|
if (showLegend) {
|
|
4107
4734
|
drawLegend({
|
|
4108
4735
|
ctx,
|
|
4736
|
+
fonts: options,
|
|
4109
4737
|
items: [
|
|
4110
4738
|
{ type: "rect", fillStyle: "#666", strokeStyle: "#444", label: legendLabels[0] },
|
|
4111
4739
|
{ type: "rect", fillStyle: "#BBB", strokeStyle: "#888", label: legendLabels[1] }
|
|
@@ -4120,7 +4748,7 @@ function renderPyramidGraph(ctx, w, h, data, options) {
|
|
|
4120
4748
|
fontSize: options.fontSize.dataLabel * 0.85 + 5
|
|
4121
4749
|
});
|
|
4122
4750
|
}
|
|
4123
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
4751
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
4124
4752
|
}
|
|
4125
4753
|
|
|
4126
4754
|
// src/core/graphs/RadarChart.ts
|
|
@@ -4134,15 +4762,13 @@ var LINE_STYLES2 = [
|
|
|
4134
4762
|
var GRAY_SHADES = ["#000", "#444", "#777", "#AAA", "#CCC"];
|
|
4135
4763
|
function renderRadarChart(ctx, w, h, data, options) {
|
|
4136
4764
|
clearCanvas(ctx, w, h);
|
|
4137
|
-
const font = options.fontFamily;
|
|
4138
|
-
const cf = options.customFont;
|
|
4139
4765
|
const fs = options.fontSize;
|
|
4140
4766
|
const n = data.axisLabels.length;
|
|
4141
4767
|
if (n < 3) return;
|
|
4142
4768
|
const showLegend = options.showLegend && data.series.length > 1;
|
|
4143
4769
|
const legendPos = options.legendPosition;
|
|
4144
4770
|
const legendLabels = data.series.map((s) => s.label);
|
|
4145
|
-
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, legendLabels, fs.dataLabel * 0.85 + 5, "line") : 0;
|
|
4771
|
+
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, legendLabels, fs.dataLabel * 0.85 + 5, options, "line") : 0;
|
|
4146
4772
|
const topPad = options.title ? 60 : 10;
|
|
4147
4773
|
const rightPad = 60 + legendW;
|
|
4148
4774
|
const leftPad = 60;
|
|
@@ -4154,6 +4780,7 @@ function renderRadarChart(ctx, w, h, data, options) {
|
|
|
4154
4780
|
legendLabels,
|
|
4155
4781
|
fs.dataLabel * 0.85 + 5,
|
|
4156
4782
|
w - leftPad - rightPad,
|
|
4783
|
+
options,
|
|
4157
4784
|
"line",
|
|
4158
4785
|
30
|
|
4159
4786
|
));
|
|
@@ -4166,7 +4793,7 @@ function renderRadarChart(ctx, w, h, data, options) {
|
|
|
4166
4793
|
const cy = topPad + availH / 2;
|
|
4167
4794
|
const angles = Array.from({ length: n }, (_, i) => -Math.PI / 2 + 2 * Math.PI * i / n);
|
|
4168
4795
|
const labelFontSize = fs.axisLabel * 0.85;
|
|
4169
|
-
const makeLabelFont = (size) => getFont(size,
|
|
4796
|
+
const makeLabelFont = (size) => getFont(size, options, "bold");
|
|
4170
4797
|
const { radius, labelLines, labelSize } = fitAxisLabels(
|
|
4171
4798
|
ctx,
|
|
4172
4799
|
data.axisLabels,
|
|
@@ -4210,13 +4837,13 @@ function renderRadarChart(ctx, w, h, data, options) {
|
|
|
4210
4837
|
ctx.stroke();
|
|
4211
4838
|
}
|
|
4212
4839
|
ctx.fillStyle = "#888";
|
|
4213
|
-
ctx.font = getFont(fs.tick * 0.8,
|
|
4840
|
+
ctx.font = getFont(fs.tick * 0.8, options, "normal");
|
|
4214
4841
|
ctx.textAlign = "left";
|
|
4215
4842
|
ctx.textBaseline = "middle";
|
|
4216
4843
|
for (let step = 1; step <= data.gridSteps; step++) {
|
|
4217
4844
|
const val = maxVal / data.gridSteps * step;
|
|
4218
4845
|
const r = step / data.gridSteps * radius;
|
|
4219
|
-
ctx.fillText(
|
|
4846
|
+
ctx.fillText(formatTick5(val), cx + 4, cy - r - 2);
|
|
4220
4847
|
}
|
|
4221
4848
|
ctx.fillStyle = "#000";
|
|
4222
4849
|
ctx.font = makeLabelFont(labelSize);
|
|
@@ -4295,6 +4922,7 @@ function renderRadarChart(ctx, w, h, data, options) {
|
|
|
4295
4922
|
});
|
|
4296
4923
|
drawLegend({
|
|
4297
4924
|
ctx,
|
|
4925
|
+
fonts: options,
|
|
4298
4926
|
items,
|
|
4299
4927
|
position: legendPos,
|
|
4300
4928
|
plotX: plotX2,
|
|
@@ -4309,10 +4937,10 @@ function renderRadarChart(ctx, w, h, data, options) {
|
|
|
4309
4937
|
}
|
|
4310
4938
|
const plotX = leftPad;
|
|
4311
4939
|
const plotW = availW;
|
|
4312
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });
|
|
4313
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });
|
|
4940
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });
|
|
4941
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });
|
|
4314
4942
|
}
|
|
4315
|
-
function
|
|
4943
|
+
function formatTick5(v) {
|
|
4316
4944
|
if (Number.isInteger(v)) return v.toString();
|
|
4317
4945
|
return v.toFixed(1);
|
|
4318
4946
|
}
|
|
@@ -4378,17 +5006,15 @@ function renderScatterGraph(ctx, w, h, data, options) {
|
|
|
4378
5006
|
}
|
|
4379
5007
|
}
|
|
4380
5008
|
function renderNormal(ctx, w, h, data, options) {
|
|
4381
|
-
const font = options.fontFamily;
|
|
4382
|
-
const cf = options.customFont;
|
|
4383
5009
|
const fs = options.fontSize;
|
|
4384
5010
|
const outsideLegend = data.bubbleLegendPosition === "outside-right" && data.showBubble && data.points.length > 0;
|
|
4385
|
-
const bubbleM = outsideLegend ? bubbleLegendMetrics(ctx, data, fs,
|
|
4386
|
-
const fillM = outsideLegend ? measureFillLegend(ctx, data, fs,
|
|
5011
|
+
const bubbleM = outsideLegend ? bubbleLegendMetrics(ctx, data, fs, options) : null;
|
|
5012
|
+
const fillM = outsideLegend ? measureFillLegend(ctx, data, fs, options) : { boxW: 0, boxH: 0 };
|
|
4387
5013
|
const legendW = Math.max(bubbleM?.boxW ?? 0, fillM.boxW);
|
|
4388
5014
|
const examUnitW = (() => {
|
|
4389
5015
|
if (data.examFrame !== true || !data.xUnit) return 0;
|
|
4390
5016
|
ctx.save();
|
|
4391
|
-
ctx.font = getFont(fs.tick,
|
|
5017
|
+
ctx.font = getFont(fs.tick, options, "bold");
|
|
4392
5018
|
const width = ctx.measureText(data.xUnit).width;
|
|
4393
5019
|
ctx.restore();
|
|
4394
5020
|
return width;
|
|
@@ -4403,7 +5029,7 @@ function renderNormal(ctx, w, h, data, options) {
|
|
|
4403
5029
|
const yMax = data.yRange.auto ? yAuto.max : data.yRange.max;
|
|
4404
5030
|
const xStep = data.xRange.step ?? (data.xRange.auto ? xAuto.step : (xMax - xMin) / 5);
|
|
4405
5031
|
const yStep = data.yRange.step ?? (data.yRange.auto ? yAuto.step : (yMax - yMin) / 5);
|
|
4406
|
-
const yName = measureYAxisName(ctx, data, w, yMin, yMax, yStep, fs,
|
|
5032
|
+
const yName = measureYAxisName(ctx, data, w, yMin, yMax, yStep, fs, options);
|
|
4407
5033
|
const padding = {
|
|
4408
5034
|
top: options.title ? 100 : 50,
|
|
4409
5035
|
right: legendW > 0 ? legendW + 40 : Math.max(60, examUnitW + 34),
|
|
@@ -4457,14 +5083,14 @@ function renderNormal(ctx, w, h, data, options) {
|
|
|
4457
5083
|
ctx.stroke();
|
|
4458
5084
|
}
|
|
4459
5085
|
ctx.fillStyle = "#000";
|
|
4460
|
-
ctx.font = getFont(fs.tick,
|
|
5086
|
+
ctx.font = getFont(fs.tick, options, "bold");
|
|
4461
5087
|
ctx.textAlign = "center";
|
|
4462
5088
|
ctx.textBaseline = "top";
|
|
4463
5089
|
const xTicks = [];
|
|
4464
5090
|
for (let v = xMin; v <= xMax + xStep * 0.01; v += xStep) xTicks.push(v);
|
|
4465
5091
|
const xStride = labelStride(
|
|
4466
5092
|
plotW / Math.max(1, xTicks.length - 1),
|
|
4467
|
-
widestLabel(ctx, xTicks.map(
|
|
5093
|
+
widestLabel(ctx, xTicks.map(formatTick6))
|
|
4468
5094
|
);
|
|
4469
5095
|
let lastXLabelRight = plotX + plotW;
|
|
4470
5096
|
xTicks.forEach((v, i) => {
|
|
@@ -4475,7 +5101,7 @@ function renderNormal(ctx, w, h, data, options) {
|
|
|
4475
5101
|
ctx.lineTo(x, plotY + plotH + 6);
|
|
4476
5102
|
ctx.stroke();
|
|
4477
5103
|
if (i % xStride === 0) {
|
|
4478
|
-
const text =
|
|
5104
|
+
const text = formatTick6(v);
|
|
4479
5105
|
ctx.fillText(text, x, plotY + plotH + 10);
|
|
4480
5106
|
lastXLabelRight = Math.max(lastXLabelRight, x + ctx.measureText(text).width / 2);
|
|
4481
5107
|
}
|
|
@@ -4493,9 +5119,9 @@ function renderNormal(ctx, w, h, data, options) {
|
|
|
4493
5119
|
ctx.moveTo(plotX - 6, y);
|
|
4494
5120
|
ctx.lineTo(plotX, y);
|
|
4495
5121
|
ctx.stroke();
|
|
4496
|
-
if (i % yStride === 0) ctx.fillText(
|
|
5122
|
+
if (i % yStride === 0) ctx.fillText(formatTick6(v), plotX - 10, y);
|
|
4497
5123
|
});
|
|
4498
|
-
ctx.font = getFont(fs.axisLabel,
|
|
5124
|
+
ctx.font = getFont(fs.axisLabel, options, "bold");
|
|
4499
5125
|
ctx.textAlign = "center";
|
|
4500
5126
|
ctx.textBaseline = "top";
|
|
4501
5127
|
drawFloatingLabel(
|
|
@@ -4506,16 +5132,16 @@ function renderNormal(ctx, w, h, data, options) {
|
|
|
4506
5132
|
w,
|
|
4507
5133
|
h,
|
|
4508
5134
|
fs.axisLabel,
|
|
4509
|
-
(size) => getFont(size,
|
|
5135
|
+
(size) => getFont(size, options, "bold")
|
|
4510
5136
|
);
|
|
4511
|
-
ctx.font = getFont(fs.axisLabel,
|
|
5137
|
+
ctx.font = getFont(fs.axisLabel, options, "bold");
|
|
4512
5138
|
if (data.xUnit) {
|
|
4513
5139
|
if (exam) {
|
|
4514
|
-
ctx.font = getFont(fs.tick,
|
|
5140
|
+
ctx.font = getFont(fs.tick, options, "bold");
|
|
4515
5141
|
ctx.textAlign = "left";
|
|
4516
5142
|
ctx.textBaseline = "top";
|
|
4517
5143
|
ctx.fillText(data.xUnit, lastXLabelRight + 6, plotY + plotH + 10);
|
|
4518
|
-
ctx.font = getFont(fs.axisLabel,
|
|
5144
|
+
ctx.font = getFont(fs.axisLabel, options, "bold");
|
|
4519
5145
|
} else {
|
|
4520
5146
|
ctx.textAlign = "right";
|
|
4521
5147
|
ctx.textBaseline = "top";
|
|
@@ -4533,10 +5159,10 @@ function renderNormal(ctx, w, h, data, options) {
|
|
|
4533
5159
|
w,
|
|
4534
5160
|
h,
|
|
4535
5161
|
fs.axisLabel,
|
|
4536
|
-
(size) => getFont(size,
|
|
5162
|
+
(size) => getFont(size, options, "bold")
|
|
4537
5163
|
);
|
|
4538
5164
|
}
|
|
4539
|
-
ctx.font = getFont(yName.size,
|
|
5165
|
+
ctx.font = getFont(yName.size, options, "bold");
|
|
4540
5166
|
ctx.textAlign = "right";
|
|
4541
5167
|
ctx.textBaseline = "middle";
|
|
4542
5168
|
const yNameLineH = yName.size * 1.3;
|
|
@@ -4547,15 +5173,14 @@ function renderNormal(ctx, w, h, data, options) {
|
|
|
4547
5173
|
clampLinesMiddle(ctx, yName.lines, plotY + plotH / 2, yNameLineH, h),
|
|
4548
5174
|
yNameLineH
|
|
4549
5175
|
);
|
|
4550
|
-
ctx.font = getFont(fs.axisLabel,
|
|
5176
|
+
ctx.font = getFont(fs.axisLabel, options, "bold");
|
|
4551
5177
|
drawPoints(
|
|
4552
5178
|
ctx,
|
|
4553
5179
|
data,
|
|
4554
5180
|
toCanvasX,
|
|
4555
5181
|
toCanvasY,
|
|
4556
5182
|
fs,
|
|
4557
|
-
|
|
4558
|
-
cf,
|
|
5183
|
+
options,
|
|
4559
5184
|
options.showDataLabels,
|
|
4560
5185
|
{ left: plotX, right: plotX + plotW, top: plotY, bottom: plotY + plotH },
|
|
4561
5186
|
w,
|
|
@@ -4565,22 +5190,20 @@ function renderNormal(ctx, w, h, data, options) {
|
|
|
4565
5190
|
const x = plotX + plotW + 20;
|
|
4566
5191
|
let y = plotY;
|
|
4567
5192
|
if (bubbleM) {
|
|
4568
|
-
drawBubbleLegendAt(ctx, bubbleM, x, y,
|
|
5193
|
+
drawBubbleLegendAt(ctx, bubbleM, x, y, options);
|
|
4569
5194
|
y += bubbleM.boxH + 8;
|
|
4570
5195
|
}
|
|
4571
|
-
if (fillM.boxH > 0) drawFillLegendAt(ctx, data, x, y, fillM, fs,
|
|
5196
|
+
if (fillM.boxH > 0) drawFillLegendAt(ctx, data, x, y, fillM, fs, options);
|
|
4572
5197
|
} else if (data.showBubble && data.points.length > 0) {
|
|
4573
5198
|
const avoid = bubbleRects(data, toCanvasX, toCanvasY);
|
|
4574
|
-
const fill = measureFillLegend(ctx, data, fs,
|
|
4575
|
-
const used = drawBubbleLegend(ctx, data, plotX, plotY, plotW, plotH, fs,
|
|
4576
|
-
drawFillLegend(ctx, data, plotX, plotY, plotW, plotH, used.bottom, fs,
|
|
5199
|
+
const fill = measureFillLegend(ctx, data, fs, options);
|
|
5200
|
+
const used = drawBubbleLegend(ctx, data, plotX, plotY, plotW, plotH, fs, options, avoid, fill.boxH);
|
|
5201
|
+
drawFillLegend(ctx, data, plotX, plotY, plotW, plotH, used.bottom, fs, options, used.corner);
|
|
4577
5202
|
}
|
|
4578
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });
|
|
4579
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, sourceInline: options.sourceInline, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });
|
|
5203
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });
|
|
5204
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, sourceInline: options.sourceInline, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });
|
|
4580
5205
|
}
|
|
4581
5206
|
function renderDeviation(ctx, w, h, data, options) {
|
|
4582
|
-
const font = options.fontFamily;
|
|
4583
|
-
const cf = options.customFont;
|
|
4584
5207
|
const fs = options.fontSize;
|
|
4585
5208
|
const padding = {
|
|
4586
5209
|
// 상자 축이름은 그래프 위에 놓이고 그 아래에 y 단위가 들어가므로 자리를 더 준다
|
|
@@ -4657,7 +5280,7 @@ function renderDeviation(ctx, w, h, data, options) {
|
|
|
4657
5280
|
ctx.lineTo(plotX + plotW, originY);
|
|
4658
5281
|
ctx.stroke();
|
|
4659
5282
|
ctx.fillStyle = "#000";
|
|
4660
|
-
ctx.font = getFont(fs.tick,
|
|
5283
|
+
ctx.font = getFont(fs.tick, options, "bold");
|
|
4661
5284
|
ctx.textAlign = "right";
|
|
4662
5285
|
ctx.textBaseline = "top";
|
|
4663
5286
|
ctx.fillText("0", originX - 6, originY + 4);
|
|
@@ -4669,7 +5292,7 @@ function renderDeviation(ctx, w, h, data, options) {
|
|
|
4669
5292
|
for (let v = -xLimit; v <= xLimit + xStep * 0.01; v += xStep) devXTicks.push(v);
|
|
4670
5293
|
const devXStride = labelStride(
|
|
4671
5294
|
plotW / Math.max(1, devXTicks.length - 1),
|
|
4672
|
-
widestLabel(ctx, devXTicks.map(
|
|
5295
|
+
widestLabel(ctx, devXTicks.map(formatTick6))
|
|
4673
5296
|
);
|
|
4674
5297
|
devXTicks.forEach((v, i) => {
|
|
4675
5298
|
if (Math.abs(v) < xStep * 0.01) return;
|
|
@@ -4679,14 +5302,14 @@ function renderDeviation(ctx, w, h, data, options) {
|
|
|
4679
5302
|
ctx.moveTo(x, xTickBase);
|
|
4680
5303
|
ctx.lineTo(x, xTickBase + 6);
|
|
4681
5304
|
ctx.stroke();
|
|
4682
|
-
if (i % devXStride === 0) ctx.fillText(
|
|
5305
|
+
if (i % devXStride === 0) ctx.fillText(formatTick6(v), x, xTickBase + 10);
|
|
4683
5306
|
});
|
|
4684
5307
|
ctx.textAlign = "right";
|
|
4685
5308
|
ctx.textBaseline = "middle";
|
|
4686
5309
|
const devYTicks = [];
|
|
4687
5310
|
for (let v = -yLimit; v <= yLimit + yStep * 0.01; v += yStep) devYTicks.push(v);
|
|
4688
5311
|
const devYStride = labelStride(plotH / Math.max(1, devYTicks.length - 1), fs.tick * 1.1);
|
|
4689
|
-
const yTickTextW = widestLabel(ctx, devYTicks.map(
|
|
5312
|
+
const yTickTextW = widestLabel(ctx, devYTicks.map(formatTick6));
|
|
4690
5313
|
devYTicks.forEach((v, i) => {
|
|
4691
5314
|
if (Math.abs(v) < yStep * 0.01) return;
|
|
4692
5315
|
const y = toCanvasY(v);
|
|
@@ -4695,9 +5318,9 @@ function renderDeviation(ctx, w, h, data, options) {
|
|
|
4695
5318
|
ctx.moveTo(yTickBase - 6, y);
|
|
4696
5319
|
ctx.lineTo(yTickBase, y);
|
|
4697
5320
|
ctx.stroke();
|
|
4698
|
-
if (i % devYStride === 0) ctx.fillText(
|
|
5321
|
+
if (i % devYStride === 0) ctx.fillText(formatTick6(v), yTickBase - 10, y);
|
|
4699
5322
|
});
|
|
4700
|
-
ctx.font = getFont(fs.axisLabel,
|
|
5323
|
+
ctx.font = getFont(fs.axisLabel, options, "bold");
|
|
4701
5324
|
if (!data.boxedAxisLabels) {
|
|
4702
5325
|
ctx.textAlign = "center";
|
|
4703
5326
|
ctx.textBaseline = "top";
|
|
@@ -4736,8 +5359,7 @@ function renderDeviation(ctx, w, h, data, options) {
|
|
|
4736
5359
|
toCanvasX,
|
|
4737
5360
|
toCanvasY,
|
|
4738
5361
|
fs,
|
|
4739
|
-
|
|
4740
|
-
cf,
|
|
5362
|
+
options,
|
|
4741
5363
|
options.showDataLabels,
|
|
4742
5364
|
{ left: plotX, right: plotX + plotW, top: plotY, bottom: plotY + plotH },
|
|
4743
5365
|
w,
|
|
@@ -4752,15 +5374,14 @@ function renderDeviation(ctx, w, h, data, options) {
|
|
|
4752
5374
|
plotW,
|
|
4753
5375
|
plotH,
|
|
4754
5376
|
fs,
|
|
4755
|
-
|
|
4756
|
-
cf,
|
|
5377
|
+
options,
|
|
4757
5378
|
bubbleRects(data, toCanvasX, toCanvasY)
|
|
4758
5379
|
);
|
|
4759
5380
|
}
|
|
4760
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });
|
|
4761
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, sourceInline: options.sourceInline, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });
|
|
5381
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: fs.title, canvasWidth: w });
|
|
5382
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, sourceInline: options.sourceInline, footnotes: options.footnotes, fontSize: fs.dataLabel, canvasWidth: w });
|
|
4762
5383
|
}
|
|
4763
|
-
function drawPoints(ctx, data, toX, toY, fs,
|
|
5384
|
+
function drawPoints(ctx, data, toX, toY, fs, options, showLabels, bounds, canvasW, canvasH) {
|
|
4764
5385
|
const maxSize = data.points.length > 0 ? Math.max(...data.points.map((p) => p.size), 1) : 1;
|
|
4765
5386
|
const placer = new LabelPlacer();
|
|
4766
5387
|
if (bounds) {
|
|
@@ -4769,8 +5390,8 @@ function drawPoints(ctx, data, toX, toY, fs, font, cf, showLabels, bounds, canva
|
|
|
4769
5390
|
placer.reserveCircle(toX(pt.x), toY(pt.y), r);
|
|
4770
5391
|
}
|
|
4771
5392
|
}
|
|
4772
|
-
const
|
|
4773
|
-
for (const pt of
|
|
5393
|
+
const drawOrder2 = [...data.points].sort((a, b) => b.size - a.size);
|
|
5394
|
+
for (const pt of drawOrder2) {
|
|
4774
5395
|
const cx = toX(pt.x);
|
|
4775
5396
|
const cy = toY(pt.y);
|
|
4776
5397
|
if (data.showBubble && pt.size > 0) {
|
|
@@ -4794,7 +5415,7 @@ function drawPoints(ctx, data, toX, toY, fs, font, cf, showLabels, bounds, canva
|
|
|
4794
5415
|
}
|
|
4795
5416
|
if (pt.label) {
|
|
4796
5417
|
ctx.fillStyle = "#000";
|
|
4797
|
-
ctx.font = getFont(fs.dataLabel,
|
|
5418
|
+
ctx.font = getFont(fs.dataLabel, options, "bold");
|
|
4798
5419
|
const offset = data.showBubble && pt.size > 0 ? pt.size / maxSize * data.bubbleScale + 4 : 8;
|
|
4799
5420
|
if (bounds) {
|
|
4800
5421
|
placer.place(ctx, pt.label, cx, cy, {
|
|
@@ -4810,7 +5431,7 @@ function drawPoints(ctx, data, toX, toY, fs, font, cf, showLabels, bounds, canva
|
|
|
4810
5431
|
}
|
|
4811
5432
|
if (showLabels) {
|
|
4812
5433
|
ctx.fillStyle = "#555";
|
|
4813
|
-
ctx.font = getFont(fs.dataLabel * 0.8,
|
|
5434
|
+
ctx.font = getFont(fs.dataLabel * 0.8, options, "normal");
|
|
4814
5435
|
ctx.textAlign = "left";
|
|
4815
5436
|
ctx.textBaseline = "top";
|
|
4816
5437
|
const offset = data.showBubble && pt.size > 0 ? pt.size / maxSize * data.bubbleScale + 4 : 8;
|
|
@@ -4836,16 +5457,16 @@ function symmetricRange(absMax) {
|
|
|
4836
5457
|
const step = limit / 4;
|
|
4837
5458
|
return { limit, step };
|
|
4838
5459
|
}
|
|
4839
|
-
function
|
|
5460
|
+
function formatTick6(v) {
|
|
4840
5461
|
return Math.abs(v) < 1e-4 ? "0" : Number(v.toFixed(2)).toString();
|
|
4841
5462
|
}
|
|
4842
|
-
function measureYAxisName(ctx, data, w, yMin, yMax, yStep, fs,
|
|
5463
|
+
function measureYAxisName(ctx, data, w, yMin, yMax, yStep, fs, options) {
|
|
4843
5464
|
ctx.save();
|
|
4844
|
-
ctx.font = getFont(fs.tick,
|
|
5465
|
+
ctx.font = getFont(fs.tick, options, "bold");
|
|
4845
5466
|
const ticks = [];
|
|
4846
5467
|
for (let v = yMin; v <= yMax + yStep * 0.01; v += yStep) ticks.push(v);
|
|
4847
|
-
const tickW = widestLabel(ctx, ticks.map(
|
|
4848
|
-
const makeFont = (size2) => getFont(size2,
|
|
5468
|
+
const tickW = widestLabel(ctx, ticks.map(formatTick6));
|
|
5469
|
+
const makeFont = (size2) => getFont(size2, options, "bold");
|
|
4849
5470
|
ctx.font = makeFont(fs.axisLabel);
|
|
4850
5471
|
const given = (data.yLabel || "").split("\\n");
|
|
4851
5472
|
const budget = Math.max(40, w * 0.3 - 18 - tickW - 12);
|
|
@@ -4894,12 +5515,12 @@ function pickLegendCorner(preferred, boxW, boxH, plotX, plotY, plotW, plotH, avo
|
|
|
4894
5515
|
}
|
|
4895
5516
|
return { corner: best.corner, x: best.x, y: best.y };
|
|
4896
5517
|
}
|
|
4897
|
-
function measureFillLegend(ctx, data, fs,
|
|
5518
|
+
function measureFillLegend(ctx, data, fs, options) {
|
|
4898
5519
|
const legend = data.fillLegend;
|
|
4899
5520
|
if (!legend || legend.items.length === 0) return { boxW: 0, boxH: 0 };
|
|
4900
5521
|
const fontSize = fs.dataLabel * 0.8;
|
|
4901
5522
|
ctx.save();
|
|
4902
|
-
ctx.font = getFont(fontSize,
|
|
5523
|
+
ctx.font = getFont(fontSize, options, "bold");
|
|
4903
5524
|
const rowH = fontSize * 1.5;
|
|
4904
5525
|
const swatch = fontSize * 0.95;
|
|
4905
5526
|
const pad = 8;
|
|
@@ -4911,7 +5532,7 @@ function measureFillLegend(ctx, data, fs, font, cf) {
|
|
|
4911
5532
|
ctx.restore();
|
|
4912
5533
|
return { boxW: textW + pad * 2, boxH: titleH + legend.items.length * rowH + pad * 2 };
|
|
4913
5534
|
}
|
|
4914
|
-
function bubbleLegendMetrics(ctx, data, fs,
|
|
5535
|
+
function bubbleLegendMetrics(ctx, data, fs, options) {
|
|
4915
5536
|
const sizes = data.points.map((p) => p.size).filter((s) => s > 0);
|
|
4916
5537
|
if (sizes.length === 0) return null;
|
|
4917
5538
|
const maxSize = Math.max(...sizes);
|
|
@@ -4924,7 +5545,7 @@ function bubbleLegendMetrics(ctx, data, fs, font, cf) {
|
|
|
4924
5545
|
const maxR = radiusOf(uniqueSteps[0]);
|
|
4925
5546
|
const labelFontSize = fs.dataLabel * 0.85;
|
|
4926
5547
|
ctx.save();
|
|
4927
|
-
ctx.font = getFont(labelFontSize,
|
|
5548
|
+
ctx.font = getFont(labelFontSize, options, "bold");
|
|
4928
5549
|
const labelW = Math.max(50, ...uniqueSteps.map((v) => ctx.measureText(labelOf(v)).width));
|
|
4929
5550
|
ctx.restore();
|
|
4930
5551
|
const pad = 14;
|
|
@@ -4941,7 +5562,7 @@ function bubbleLegendMetrics(ctx, data, fs, font, cf) {
|
|
|
4941
5562
|
const boxH = maxR * 2 + lowest + pad * 2 + 10;
|
|
4942
5563
|
return { uniqueSteps, labelOf, radiusOf, maxR, boxW, boxH, labelOffsets, lowest, labelFontSize, pad };
|
|
4943
5564
|
}
|
|
4944
|
-
function drawBubbleLegendAt(ctx, m, boxX, boxY,
|
|
5565
|
+
function drawBubbleLegendAt(ctx, m, boxX, boxY, options) {
|
|
4945
5566
|
ctx.fillStyle = "#fff";
|
|
4946
5567
|
ctx.strokeStyle = "#666";
|
|
4947
5568
|
ctx.lineWidth = 1.5;
|
|
@@ -4970,18 +5591,18 @@ function drawBubbleLegendAt(ctx, m, boxX, boxY, font, cf) {
|
|
|
4970
5591
|
ctx.stroke();
|
|
4971
5592
|
ctx.restore();
|
|
4972
5593
|
ctx.fillStyle = "#000";
|
|
4973
|
-
ctx.font = getFont(m.labelFontSize,
|
|
5594
|
+
ctx.font = getFont(m.labelFontSize, options, "bold");
|
|
4974
5595
|
ctx.textAlign = "left";
|
|
4975
5596
|
ctx.textBaseline = "middle";
|
|
4976
5597
|
ctx.fillText(m.labelOf(size), circleX + m.maxR + 16, bottomCircleY + m.labelOffsets[si]);
|
|
4977
5598
|
});
|
|
4978
5599
|
}
|
|
4979
|
-
function drawBubbleLegend(ctx, data, plotX, plotY, plotW, plotH, fs,
|
|
5600
|
+
function drawBubbleLegend(ctx, data, plotX, plotY, plotW, plotH, fs, options, avoid = [], extraH = 0) {
|
|
4980
5601
|
const preferred = insideCorner(data.bubbleLegendPosition);
|
|
4981
|
-
const m = bubbleLegendMetrics(ctx, data, fs,
|
|
5602
|
+
const m = bubbleLegendMetrics(ctx, data, fs, options);
|
|
4982
5603
|
if (!m) return { height: 0, corner: preferred, bottom: 0 };
|
|
4983
5604
|
const spot = pickLegendCorner(preferred, m.boxW, m.boxH, plotX, plotY, plotW, plotH, avoid, extraH);
|
|
4984
|
-
drawBubbleLegendAt(ctx, m, spot.x, spot.y,
|
|
5605
|
+
drawBubbleLegendAt(ctx, m, spot.x, spot.y, options);
|
|
4985
5606
|
return { height: m.boxH, corner: spot.corner, bottom: spot.y + m.boxH };
|
|
4986
5607
|
}
|
|
4987
5608
|
function drawBoxedLabel(ctx, text, x, y, fontSize, place) {
|
|
@@ -5009,12 +5630,12 @@ function drawBoxedLabel(ctx, text, x, y, fontSize, place) {
|
|
|
5009
5630
|
}
|
|
5010
5631
|
ctx.restore();
|
|
5011
5632
|
}
|
|
5012
|
-
function drawFillLegendAt(ctx, data, boxX, boxY, m, fs,
|
|
5633
|
+
function drawFillLegendAt(ctx, data, boxX, boxY, m, fs, options) {
|
|
5013
5634
|
const legend = data.fillLegend;
|
|
5014
5635
|
if (!legend || legend.items.length === 0) return;
|
|
5015
5636
|
const fontSize = fs.dataLabel * 0.8;
|
|
5016
5637
|
ctx.save();
|
|
5017
|
-
ctx.font = getFont(fontSize,
|
|
5638
|
+
ctx.font = getFont(fontSize, options, "bold");
|
|
5018
5639
|
const rowH = fontSize * 1.5;
|
|
5019
5640
|
const swatch = fontSize * 0.95;
|
|
5020
5641
|
const pad = 8;
|
|
@@ -5046,13 +5667,13 @@ function drawFillLegendAt(ctx, data, boxX, boxY, m, fs, font, cf) {
|
|
|
5046
5667
|
}
|
|
5047
5668
|
ctx.restore();
|
|
5048
5669
|
}
|
|
5049
|
-
function drawFillLegend(ctx, data, plotX, plotY, plotW, plotH, stackBelow, fs,
|
|
5670
|
+
function drawFillLegend(ctx, data, plotX, plotY, plotW, plotH, stackBelow, fs, options, corner = "bottom-right") {
|
|
5050
5671
|
const legend = data.fillLegend;
|
|
5051
5672
|
if (!legend || legend.items.length === 0) return;
|
|
5052
|
-
const m = measureFillLegend(ctx, data, fs,
|
|
5673
|
+
const m = measureFillLegend(ctx, data, fs, options);
|
|
5053
5674
|
const boxX = corner.endsWith("right") ? plotX + plotW - m.boxW - 10 : plotX + 10;
|
|
5054
5675
|
const boxY = stackBelow > 0 ? stackBelow + 6 : corner.startsWith("top") ? plotY + 10 : plotY + plotH - m.boxH - 10;
|
|
5055
|
-
drawFillLegendAt(ctx, data, boxX, boxY, m, fs,
|
|
5676
|
+
drawFillLegendAt(ctx, data, boxX, boxY, m, fs, options);
|
|
5056
5677
|
}
|
|
5057
5678
|
|
|
5058
5679
|
// src/core/graphs/StackedBarPie.ts
|
|
@@ -5067,13 +5688,14 @@ function renderStackedGraph(ctx, w, h, data, options) {
|
|
|
5067
5688
|
function renderStackedBar(ctx, w, h, data, options) {
|
|
5068
5689
|
const showLegend = options.showLegend;
|
|
5069
5690
|
const legendPos = options.legendPosition;
|
|
5070
|
-
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5) : 0;
|
|
5691
|
+
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5, options) : 0;
|
|
5071
5692
|
const isVertical = data.barDirection === "vertical";
|
|
5072
5693
|
const legendReserve = showLegend && legendPos === "bottom" ? measureBottomLegend(
|
|
5073
5694
|
ctx,
|
|
5074
5695
|
data.seriesLabels,
|
|
5075
5696
|
options.fontSize.dataLabel * 0.85 + 5,
|
|
5076
|
-
w - (isVertical ? 80 : 100) - (isVertical ? 60 + legendW : 160 + legendW)
|
|
5697
|
+
w - (isVertical ? 80 : 100) - (isVertical ? 60 + legendW : 160 + legendW),
|
|
5698
|
+
options
|
|
5077
5699
|
) : 0;
|
|
5078
5700
|
const padding = {
|
|
5079
5701
|
top: options.title ? 100 : 50,
|
|
@@ -5092,8 +5714,6 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5092
5714
|
const plotY = padding.top;
|
|
5093
5715
|
const plotW = w - padding.left - padding.right;
|
|
5094
5716
|
const plotH = h - padding.top - padding.bottom;
|
|
5095
|
-
const font = options.fontFamily;
|
|
5096
|
-
const customFont = options.customFont;
|
|
5097
5717
|
const n = data.categories.length;
|
|
5098
5718
|
const sCount = data.seriesLabels.length;
|
|
5099
5719
|
ctx.strokeStyle = "#000";
|
|
@@ -5107,7 +5727,7 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5107
5727
|
ctx.lineTo(plotX, plotY);
|
|
5108
5728
|
ctx.stroke();
|
|
5109
5729
|
const stepV = data.axisStep ?? 20;
|
|
5110
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
5730
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
5111
5731
|
ctx.fillStyle = "#000";
|
|
5112
5732
|
ctx.textAlign = "right";
|
|
5113
5733
|
ctx.textBaseline = "middle";
|
|
@@ -5131,7 +5751,7 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5131
5751
|
ctx.restore();
|
|
5132
5752
|
}
|
|
5133
5753
|
}
|
|
5134
|
-
ctx.font = getFont(options.fontSize.axisLabel,
|
|
5754
|
+
ctx.font = getFont(options.fontSize.axisLabel, options, "bold");
|
|
5135
5755
|
ctx.textAlign = "right";
|
|
5136
5756
|
ctx.textBaseline = "bottom";
|
|
5137
5757
|
ctx.fillText(data.unit, plotX - 10, plotY - 16);
|
|
@@ -5152,10 +5772,10 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5152
5772
|
ctx.lineWidth = 0.8;
|
|
5153
5773
|
ctx.strokeRect(cx - barW / 2, y, barW, barH);
|
|
5154
5774
|
if (data.labelInSegment) {
|
|
5155
|
-
drawSegmentLabel(ctx, data, options, s, cx, y + barH / 2, barW, barH, lightAt(data, s)
|
|
5775
|
+
drawSegmentLabel(ctx, data, options, s, cx, y + barH / 2, barW, barH, lightAt(data, s));
|
|
5156
5776
|
} else if (options.showDataLabels && barH > options.fontSize.dataLabel) {
|
|
5157
5777
|
ctx.fillStyle = lightAt(data, s) ? "#000" : "#fff";
|
|
5158
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
5778
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options, "bold");
|
|
5159
5779
|
ctx.textAlign = "center";
|
|
5160
5780
|
ctx.textBaseline = "middle";
|
|
5161
5781
|
ctx.fillText(String(val), cx, y + barH / 2);
|
|
@@ -5163,7 +5783,7 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5163
5783
|
cumY += barH;
|
|
5164
5784
|
}
|
|
5165
5785
|
ctx.fillStyle = "#000";
|
|
5166
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
5786
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
5167
5787
|
ctx.textAlign = "center";
|
|
5168
5788
|
ctx.textBaseline = "top";
|
|
5169
5789
|
ctx.fillText(data.categories[c].label, cx, plotY + plotH + 12);
|
|
@@ -5177,7 +5797,7 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5177
5797
|
ctx.lineTo(plotX, plotY);
|
|
5178
5798
|
ctx.stroke();
|
|
5179
5799
|
const stepH = data.axisStep ?? 20;
|
|
5180
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
5800
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
5181
5801
|
ctx.fillStyle = "#000";
|
|
5182
5802
|
ctx.textAlign = "center";
|
|
5183
5803
|
ctx.textBaseline = "top";
|
|
@@ -5201,7 +5821,7 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5201
5821
|
ctx.restore();
|
|
5202
5822
|
}
|
|
5203
5823
|
}
|
|
5204
|
-
ctx.font = getFont(options.fontSize.axisLabel,
|
|
5824
|
+
ctx.font = getFont(options.fontSize.axisLabel, options, "bold");
|
|
5205
5825
|
ctx.textAlign = "left";
|
|
5206
5826
|
ctx.textBaseline = "top";
|
|
5207
5827
|
ctx.fillText(data.unit, plotX + plotW + 30, plotY + plotH + 10);
|
|
@@ -5223,7 +5843,7 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5223
5843
|
ctx.strokeRect(x, cy - barH / 2, bw, barH);
|
|
5224
5844
|
if (options.showDataLabels && bw > options.fontSize.dataLabel * 2) {
|
|
5225
5845
|
ctx.fillStyle = lightAt(data, s) ? "#000" : "#fff";
|
|
5226
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
5846
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options, "bold");
|
|
5227
5847
|
ctx.textAlign = "center";
|
|
5228
5848
|
ctx.textBaseline = "middle";
|
|
5229
5849
|
ctx.fillText(String(val), x + bw / 2, cy);
|
|
@@ -5231,13 +5851,13 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5231
5851
|
cumX += bw;
|
|
5232
5852
|
}
|
|
5233
5853
|
ctx.fillStyle = "#000";
|
|
5234
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
5854
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
5235
5855
|
ctx.textAlign = "right";
|
|
5236
5856
|
ctx.textBaseline = "middle";
|
|
5237
5857
|
ctx.fillText(data.categories[c].label, plotX - 10, cy);
|
|
5238
5858
|
}
|
|
5239
5859
|
}
|
|
5240
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
5860
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
5241
5861
|
if (showLegend) {
|
|
5242
5862
|
const items = data.seriesLabels.map((label, i) => ({
|
|
5243
5863
|
type: "rect",
|
|
@@ -5247,6 +5867,7 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5247
5867
|
}));
|
|
5248
5868
|
drawLegend({
|
|
5249
5869
|
ctx,
|
|
5870
|
+
fonts: options,
|
|
5250
5871
|
items,
|
|
5251
5872
|
position: legendPos,
|
|
5252
5873
|
plotX,
|
|
@@ -5258,17 +5879,18 @@ function renderStackedBar(ctx, w, h, data, options) {
|
|
|
5258
5879
|
fontSize: options.fontSize.dataLabel * 0.85 + 5
|
|
5259
5880
|
});
|
|
5260
5881
|
}
|
|
5261
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, sourceLeft: options.sourceLeft, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
5882
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, sourceLeft: options.sourceLeft, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
5262
5883
|
}
|
|
5263
5884
|
function renderPieChart(ctx, w, h, data, options) {
|
|
5264
5885
|
const showLegend = options.showLegend;
|
|
5265
5886
|
const legendPos = options.legendPosition;
|
|
5266
|
-
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5) : 0;
|
|
5887
|
+
const legendW = showLegend && legendPos === "right" ? measureLegendWidth(ctx, data.seriesLabels, options.fontSize.dataLabel * 0.85 + 5, options) : 0;
|
|
5267
5888
|
const legendReserve = showLegend && legendPos === "bottom" ? measureBottomLegend(
|
|
5268
5889
|
ctx,
|
|
5269
5890
|
data.seriesLabels,
|
|
5270
5891
|
options.fontSize.dataLabel * 0.85 + 5,
|
|
5271
5892
|
w - 60 - (60 + legendW),
|
|
5893
|
+
options,
|
|
5272
5894
|
"rect",
|
|
5273
5895
|
16
|
|
5274
5896
|
) : 0;
|
|
@@ -5289,8 +5911,6 @@ function renderPieChart(ctx, w, h, data, options) {
|
|
|
5289
5911
|
const plotY = padding.top;
|
|
5290
5912
|
const plotW = w - padding.left - padding.right;
|
|
5291
5913
|
const plotH = h - padding.top - padding.bottom;
|
|
5292
|
-
const font = options.fontFamily;
|
|
5293
|
-
const customFont = options.customFont;
|
|
5294
5914
|
const n = data.categories.length;
|
|
5295
5915
|
const sCount = data.seriesLabels.length;
|
|
5296
5916
|
const pieScale = (data.pieScale ?? 100) / 100;
|
|
@@ -5330,7 +5950,7 @@ function renderPieChart(ctx, w, h, data, options) {
|
|
|
5330
5950
|
const lx = cx + Math.cos(midAngle) * maxR * 0.65;
|
|
5331
5951
|
const ly = cy + Math.sin(midAngle) * maxR * 0.65;
|
|
5332
5952
|
ctx.fillStyle = lightAt(data, s) ? "#000" : "#fff";
|
|
5333
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
5953
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options, "bold");
|
|
5334
5954
|
ctx.textAlign = "center";
|
|
5335
5955
|
ctx.textBaseline = "middle";
|
|
5336
5956
|
ctx.fillText(String(val), lx, ly);
|
|
@@ -5343,12 +5963,12 @@ function renderPieChart(ctx, w, h, data, options) {
|
|
|
5343
5963
|
ctx.arc(cx, cy, maxR, 0, Math.PI * 2);
|
|
5344
5964
|
ctx.stroke();
|
|
5345
5965
|
ctx.fillStyle = "#000";
|
|
5346
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
5966
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
5347
5967
|
ctx.textAlign = "center";
|
|
5348
5968
|
ctx.textBaseline = "top";
|
|
5349
5969
|
ctx.fillText(data.categories[c].label, cx, cy + maxR + 12);
|
|
5350
5970
|
}
|
|
5351
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
5971
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
5352
5972
|
if (showLegend) {
|
|
5353
5973
|
const items = data.seriesLabels.map((label, i) => ({
|
|
5354
5974
|
type: "rect",
|
|
@@ -5366,6 +5986,7 @@ function renderPieChart(ctx, w, h, data, options) {
|
|
|
5366
5986
|
const effectivePlotH = pieBottom - plotY;
|
|
5367
5987
|
drawLegend({
|
|
5368
5988
|
ctx,
|
|
5989
|
+
fonts: options,
|
|
5369
5990
|
items,
|
|
5370
5991
|
position: legendPos,
|
|
5371
5992
|
plotX,
|
|
@@ -5383,6 +6004,7 @@ function renderPieChart(ctx, w, h, data, options) {
|
|
|
5383
6004
|
const effectivePlotH = pieBottom - plotY;
|
|
5384
6005
|
drawLegend({
|
|
5385
6006
|
ctx,
|
|
6007
|
+
fonts: options,
|
|
5386
6008
|
items,
|
|
5387
6009
|
position: legendPos,
|
|
5388
6010
|
plotX,
|
|
@@ -5396,7 +6018,7 @@ function renderPieChart(ctx, w, h, data, options) {
|
|
|
5396
6018
|
});
|
|
5397
6019
|
}
|
|
5398
6020
|
}
|
|
5399
|
-
drawSourceAndFootnote({ ctx, plotX, plotW, height: h, source: options.source, sourceLeft: options.sourceLeft, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
6021
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX, plotW, height: h, source: options.source, sourceLeft: options.sourceLeft, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
5400
6022
|
}
|
|
5401
6023
|
function stackOrder(cat, sCount) {
|
|
5402
6024
|
const given = cat.segmentOrder;
|
|
@@ -5414,12 +6036,12 @@ function lightAt(data, i) {
|
|
|
5414
6036
|
const v = data.seriesFills?.[i];
|
|
5415
6037
|
return v === void 0 || v === null ? isLightFill(i) : isLightFillValue(v);
|
|
5416
6038
|
}
|
|
5417
|
-
function drawSegmentLabel(ctx, data, options, s, cx, cy, barW, barH, light
|
|
6039
|
+
function drawSegmentLabel(ctx, data, options, s, cx, cy, barW, barH, light) {
|
|
5418
6040
|
const text = data.seriesLabels[s] ?? "";
|
|
5419
6041
|
const size = options.fontSize.dataLabel * 0.9;
|
|
5420
6042
|
if (!text || barH < size * 1.1) return;
|
|
5421
|
-
const family = data.seriesIsSymbol && !data.seriesIsSymbol[s] ? "sans" :
|
|
5422
|
-
ctx.font = getFont(size,
|
|
6043
|
+
const family = data.seriesIsSymbol && !data.seriesIsSymbol[s] ? "sans" : options.fontFamily;
|
|
6044
|
+
ctx.font = getFont(size, options, "bold", family);
|
|
5423
6045
|
ctx.textAlign = "center";
|
|
5424
6046
|
ctx.textBaseline = "middle";
|
|
5425
6047
|
const maxW = Math.max(1, barW - 6);
|
|
@@ -5484,8 +6106,6 @@ function renderTernaryGraph(ctx, w, h, data, options) {
|
|
|
5484
6106
|
const triH = triSize * Math.sqrt(3) / 2;
|
|
5485
6107
|
const cx = plotX + plotW / 2;
|
|
5486
6108
|
const cy = padding.top + 11 + triH * 2 / 3;
|
|
5487
|
-
const font = options.fontFamily;
|
|
5488
|
-
const customFont = options.customFont;
|
|
5489
6109
|
const topPt = { x: cx, y: cy - triH * 2 / 3 };
|
|
5490
6110
|
const leftPt = { x: cx - triSize / 2, y: cy + triH / 3 };
|
|
5491
6111
|
const rightPt = { x: cx + triSize / 2, y: cy + triH / 3 };
|
|
@@ -5529,7 +6149,7 @@ function renderTernaryGraph(ctx, w, h, data, options) {
|
|
|
5529
6149
|
const bTick = { x: Math.cos(Math.PI / 3), y: Math.sin(Math.PI / 3) };
|
|
5530
6150
|
const cTick = { x: Math.cos(Math.PI / 3), y: -Math.sin(Math.PI / 3) };
|
|
5531
6151
|
ctx.fillStyle = "#000";
|
|
5532
|
-
ctx.font = getFont(options.fontSize.tick,
|
|
6152
|
+
ctx.font = getFont(options.fontSize.tick, options, "bold");
|
|
5533
6153
|
ctx.strokeStyle = "#000";
|
|
5534
6154
|
ctx.lineWidth = 1.5;
|
|
5535
6155
|
for (let i = 0; i <= steps; i++) {
|
|
@@ -5561,7 +6181,7 @@ function renderTernaryGraph(ctx, w, h, data, options) {
|
|
|
5561
6181
|
const cAt = nudgeInside(ctx, cLabel, cPos.x + cTick.x * tickLen + 4, cPos.y + cTick.y * tickLen, w, h);
|
|
5562
6182
|
ctx.fillText(cLabel, cAt.x, cAt.y);
|
|
5563
6183
|
}
|
|
5564
|
-
ctx.font = getFont(nameFit.fontSize,
|
|
6184
|
+
ctx.font = getFont(nameFit.fontSize, options, "bold");
|
|
5565
6185
|
ctx.fillStyle = "#000";
|
|
5566
6186
|
const labelLineH = nameFit.fontSize * 1.3;
|
|
5567
6187
|
const spots = namePlaces(w, plotX + plotW / 2, padding.top, triSize, nameFit.lines, labelLineH);
|
|
@@ -5579,21 +6199,21 @@ function renderTernaryGraph(ctx, w, h, data, options) {
|
|
|
5579
6199
|
ctx.fill();
|
|
5580
6200
|
if (p.label) {
|
|
5581
6201
|
ctx.fillStyle = "#000";
|
|
5582
|
-
ctx.font = getFont(options.fontSize.dataLabel,
|
|
6202
|
+
ctx.font = getFont(options.fontSize.dataLabel, options, "bold");
|
|
5583
6203
|
ctx.textAlign = "left";
|
|
5584
6204
|
ctx.textBaseline = "bottom";
|
|
5585
6205
|
ctx.fillText(p.label, x + 10, y - 4);
|
|
5586
6206
|
}
|
|
5587
6207
|
if (options.showDataLabels) {
|
|
5588
6208
|
ctx.fillStyle = "#555";
|
|
5589
|
-
ctx.font = getFont(options.fontSize.dataLabel * 0.8,
|
|
6209
|
+
ctx.font = getFont(options.fontSize.dataLabel * 0.8, options);
|
|
5590
6210
|
ctx.textAlign = "left";
|
|
5591
6211
|
ctx.textBaseline = "top";
|
|
5592
6212
|
ctx.fillText(`(${p.a}, ${p.b}, ${p.c})`, x + 10, y + 4);
|
|
5593
6213
|
}
|
|
5594
6214
|
}
|
|
5595
|
-
drawTitle({ ctx, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
5596
|
-
drawSourceAndFootnote({ ctx, plotX: leftPt.x, plotW: rightPt.x - leftPt.x, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
6215
|
+
drawTitle({ ctx, fonts: options, plotX, plotW, title: options.title, fontSize: options.fontSize.title, canvasWidth: w });
|
|
6216
|
+
drawSourceAndFootnote({ ctx, fonts: options, plotX: leftPt.x, plotW: rightPt.x - leftPt.x, height: h, source: options.source, footnotes: options.footnotes, fontSize: options.fontSize.dataLabel, canvasWidth: w });
|
|
5597
6217
|
}
|
|
5598
6218
|
var SHRINK_LIMIT3 = 0.8;
|
|
5599
6219
|
function namePlaces(_w, cx, padTop, triSize, lines, lineH) {
|
|
@@ -5613,7 +6233,7 @@ function namePlaces(_w, cx, padTop, triSize, lines, lineH) {
|
|
|
5613
6233
|
}
|
|
5614
6234
|
function fitAxisNames(ctx, labels, options, w, h, cx, padTop, maxSize) {
|
|
5615
6235
|
ctx.save();
|
|
5616
|
-
const makeFont = (size2) => getFont(size2, options
|
|
6236
|
+
const makeFont = (size2) => getFont(size2, options, "bold");
|
|
5617
6237
|
let fontSize = options.fontSize.axisLabel * 1.3;
|
|
5618
6238
|
const given = labels.map((l) => (l || "").split("\\n"));
|
|
5619
6239
|
let lines = given.map((g) => g.slice());
|
|
@@ -5667,6 +6287,7 @@ var REGISTRY = {
|
|
|
5667
6287
|
// 위 climate 주석 참고 — 이 칸의 렌더러만 바꾼 오배선은 컴파일러가 잡지 못한다.
|
|
5668
6288
|
"deviation-a": { render: renderDeviationAGraph, createDefaultData: createDefaultDeviationAData },
|
|
5669
6289
|
"deviation-b": { render: renderDeviationBGraph, createDefaultData: createDefaultDeviationBData },
|
|
6290
|
+
"econ-plane": { render: renderEconPlane, createDefaultData: createDefaultEconPlaneData },
|
|
5670
6291
|
hythergraph: { render: renderHythergraph, createDefaultData: createDefaultHythergraphData },
|
|
5671
6292
|
line: { render: renderLineGraph, createDefaultData: createDefaultLineData },
|
|
5672
6293
|
"matrix-table": { render: renderMatrixTable, createDefaultData: createDefaultMatrixTableData },
|
|
@@ -5927,6 +6548,7 @@ function mergeOptions(base, patch) {
|
|
|
5927
6548
|
...base,
|
|
5928
6549
|
...patch,
|
|
5929
6550
|
fontSize: { ...base.fontSize, ...patch?.fontSize },
|
|
6551
|
+
fontStack: { ...base.fontStack, ...patch?.fontStack },
|
|
5930
6552
|
footnotes: [...patch?.footnotes ?? base.footnotes]
|
|
5931
6553
|
};
|
|
5932
6554
|
}
|
|
@@ -5954,7 +6576,7 @@ var CsatChart = class {
|
|
|
5954
6576
|
*
|
|
5955
6577
|
* 필드가 아니라 **메서드**로 둔다. `static readonly ensureFonts = ensureFonts`
|
|
5956
6578
|
* 로 적으면 ES2020 로 낮출 때 클래스 «뒤» 의 대입문이 되는데, 그것은 지울 수
|
|
5957
|
-
* 없는 부수효과라 이 클래스와 레지스트리와 렌더러
|
|
6579
|
+
* 없는 부수효과라 이 클래스와 레지스트리와 렌더러 17종이 모든 번들에 박힌다.
|
|
5958
6580
|
* 메서드는 클래스 본문의 일부라 어느 목표에서도 그런 일이 없다 — 실제로
|
|
5959
6581
|
* 겪은 문제이고, `test/bundle.test.ts` 가 이걸 회귀로 잡지는 않으니 여기
|
|
5960
6582
|
* 적어 둔다.
|
|
@@ -6130,6 +6752,7 @@ var LINE_DASH2 = /* @__PURE__ */ Object.freeze({
|
|
|
6130
6752
|
MONTH_LABELS_EN,
|
|
6131
6753
|
MONTH_LABELS_NUM,
|
|
6132
6754
|
autoRange,
|
|
6755
|
+
createAdAsData,
|
|
6133
6756
|
createDefaultAbsBarData,
|
|
6134
6757
|
createDefaultCategoryDotData,
|
|
6135
6758
|
createDefaultClimateData,
|
|
@@ -6137,6 +6760,7 @@ var LINE_DASH2 = /* @__PURE__ */ Object.freeze({
|
|
|
6137
6760
|
createDefaultDataTableData,
|
|
6138
6761
|
createDefaultDeviationAData,
|
|
6139
6762
|
createDefaultDeviationBData,
|
|
6763
|
+
createDefaultEconPlaneData,
|
|
6140
6764
|
createDefaultGraphOptions,
|
|
6141
6765
|
createDefaultHythergraphData,
|
|
6142
6766
|
createDefaultLineData,
|
|
@@ -6147,6 +6771,8 @@ var LINE_DASH2 = /* @__PURE__ */ Object.freeze({
|
|
|
6147
6771
|
createDefaultStackedData,
|
|
6148
6772
|
createDefaultTernaryData,
|
|
6149
6773
|
createDefaultTreemapData,
|
|
6774
|
+
createPointShiftData,
|
|
6775
|
+
createSupplyDemandData,
|
|
6150
6776
|
ensureFonts,
|
|
6151
6777
|
installRoundRectPolyfill,
|
|
6152
6778
|
isCsatChartType,
|
|
@@ -6158,6 +6784,7 @@ var LINE_DASH2 = /* @__PURE__ */ Object.freeze({
|
|
|
6158
6784
|
renderDataTable,
|
|
6159
6785
|
renderDeviationAGraph,
|
|
6160
6786
|
renderDeviationBGraph,
|
|
6787
|
+
renderEconPlane,
|
|
6161
6788
|
renderHythergraph,
|
|
6162
6789
|
renderLineGraph,
|
|
6163
6790
|
renderMatrixTable,
|