@opendata-ai/openchart-engine 6.9.0 → 6.10.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/dist/index.js +126 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/charts/bar/__tests__/compute.test.ts +92 -0
- package/src/charts/bar/compute.ts +83 -0
- package/src/charts/column/__tests__/compute.test.ts +66 -0
- package/src/charts/column/compute.ts +89 -1
- package/src/layout/scales.ts +10 -2
package/dist/index.js
CHANGED
|
@@ -774,6 +774,20 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
|
|
|
774
774
|
const categoryGroups = groupByField(spec.data, yChannel.field);
|
|
775
775
|
const needsStacking = Array.from(categoryGroups.values()).some((rows) => rows.length > 1);
|
|
776
776
|
if (needsStacking) {
|
|
777
|
+
const stackDisabled = xChannel.stack === null || xChannel.stack === false;
|
|
778
|
+
if (stackDisabled) {
|
|
779
|
+
return computeGroupedBars(
|
|
780
|
+
spec.data,
|
|
781
|
+
xChannel.field,
|
|
782
|
+
yChannel.field,
|
|
783
|
+
colorField,
|
|
784
|
+
xScale,
|
|
785
|
+
yScale,
|
|
786
|
+
bandwidth,
|
|
787
|
+
baseline,
|
|
788
|
+
scales
|
|
789
|
+
);
|
|
790
|
+
}
|
|
777
791
|
return computeStackedBars(
|
|
778
792
|
spec.data,
|
|
779
793
|
xChannel.field,
|
|
@@ -834,6 +848,51 @@ function computeStackedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
834
848
|
}
|
|
835
849
|
return marks;
|
|
836
850
|
}
|
|
851
|
+
function computeGroupedBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, baseline, scales) {
|
|
852
|
+
const marks = [];
|
|
853
|
+
const categoryGroups = groupByField(data, categoryField);
|
|
854
|
+
const groupIndexMap = /* @__PURE__ */ new Map();
|
|
855
|
+
for (const row of data) {
|
|
856
|
+
const key = String(row[colorField] ?? "");
|
|
857
|
+
if (!groupIndexMap.has(key)) {
|
|
858
|
+
groupIndexMap.set(key, groupIndexMap.size);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
const groupCount = groupIndexMap.size;
|
|
862
|
+
if (groupCount === 0) return marks;
|
|
863
|
+
const gap = Math.min(1, bandwidth * 0.05);
|
|
864
|
+
const subBandHeight = Math.max((bandwidth - gap * (groupCount - 1)) / groupCount, MIN_BAR_WIDTH);
|
|
865
|
+
for (const [category, rows] of categoryGroups) {
|
|
866
|
+
const bandY = yScale(category);
|
|
867
|
+
if (bandY === void 0) continue;
|
|
868
|
+
for (const row of rows) {
|
|
869
|
+
const groupKey = String(row[colorField] ?? "");
|
|
870
|
+
const value2 = Number(row[valueField] ?? 0);
|
|
871
|
+
if (!Number.isFinite(value2)) continue;
|
|
872
|
+
const groupIndex = groupIndexMap.get(groupKey) ?? 0;
|
|
873
|
+
const color2 = getColor(scales, groupKey);
|
|
874
|
+
const xPos = value2 >= 0 ? baseline : xScale(value2);
|
|
875
|
+
const barWidth = Math.max(Math.abs(xScale(value2) - baseline), MIN_BAR_WIDTH);
|
|
876
|
+
const subY = bandY + groupIndex * (subBandHeight + gap);
|
|
877
|
+
const aria = {
|
|
878
|
+
label: `${category}, ${groupKey}: ${formatBarValue(value2)}`
|
|
879
|
+
};
|
|
880
|
+
marks.push({
|
|
881
|
+
type: "rect",
|
|
882
|
+
x: xPos,
|
|
883
|
+
y: subY,
|
|
884
|
+
width: barWidth,
|
|
885
|
+
height: subBandHeight,
|
|
886
|
+
fill: color2,
|
|
887
|
+
cornerRadius: 2,
|
|
888
|
+
data: row,
|
|
889
|
+
aria,
|
|
890
|
+
orient: "horizontal"
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return marks;
|
|
895
|
+
}
|
|
837
896
|
function computeColoredBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, baseline, scales) {
|
|
838
897
|
const marks = [];
|
|
839
898
|
for (const row of data) {
|
|
@@ -1072,6 +1131,20 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
|
1072
1131
|
const categoryGroups = groupByField(spec.data, xChannel.field);
|
|
1073
1132
|
const needsStacking = Array.from(categoryGroups.values()).some((rows) => rows.length > 1);
|
|
1074
1133
|
if (needsStacking) {
|
|
1134
|
+
const stackDisabled = yChannel.stack === null || yChannel.stack === false;
|
|
1135
|
+
if (stackDisabled) {
|
|
1136
|
+
return computeGroupedColumns(
|
|
1137
|
+
spec.data,
|
|
1138
|
+
xChannel.field,
|
|
1139
|
+
yChannel.field,
|
|
1140
|
+
colorField,
|
|
1141
|
+
xScale,
|
|
1142
|
+
yScale,
|
|
1143
|
+
bandwidth,
|
|
1144
|
+
baseline,
|
|
1145
|
+
scales
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1075
1148
|
return computeStackedColumns(
|
|
1076
1149
|
spec.data,
|
|
1077
1150
|
xChannel.field,
|
|
@@ -1179,6 +1252,55 @@ function computeColoredColumns(data, categoryField, valueField, colorField, xSca
|
|
|
1179
1252
|
}
|
|
1180
1253
|
return marks;
|
|
1181
1254
|
}
|
|
1255
|
+
function computeGroupedColumns(data, categoryField, valueField, colorField, xScale, yScale, bandwidth, baseline, scales) {
|
|
1256
|
+
const marks = [];
|
|
1257
|
+
const categoryGroups = groupByField(data, categoryField);
|
|
1258
|
+
const groupIndexMap = /* @__PURE__ */ new Map();
|
|
1259
|
+
for (const row of data) {
|
|
1260
|
+
const key = String(row[colorField] ?? "");
|
|
1261
|
+
if (!groupIndexMap.has(key)) {
|
|
1262
|
+
groupIndexMap.set(key, groupIndexMap.size);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
const groupCount = groupIndexMap.size;
|
|
1266
|
+
if (groupCount === 0) return marks;
|
|
1267
|
+
const gap = Math.min(1, bandwidth * 0.05);
|
|
1268
|
+
const subBandWidth = Math.max(
|
|
1269
|
+
(bandwidth - gap * (groupCount - 1)) / groupCount,
|
|
1270
|
+
MIN_COLUMN_HEIGHT
|
|
1271
|
+
);
|
|
1272
|
+
for (const [category, rows] of categoryGroups) {
|
|
1273
|
+
const bandX = xScale(category);
|
|
1274
|
+
if (bandX === void 0) continue;
|
|
1275
|
+
for (const row of rows) {
|
|
1276
|
+
const groupKey = String(row[colorField] ?? "");
|
|
1277
|
+
const value2 = Number(row[valueField] ?? 0);
|
|
1278
|
+
if (!Number.isFinite(value2)) continue;
|
|
1279
|
+
const groupIndex = groupIndexMap.get(groupKey) ?? 0;
|
|
1280
|
+
const color2 = getColor(scales, groupKey);
|
|
1281
|
+
const yPos = yScale(value2);
|
|
1282
|
+
const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
|
|
1283
|
+
const y2 = value2 >= 0 ? yPos : baseline;
|
|
1284
|
+
const subX = bandX + groupIndex * (subBandWidth + gap);
|
|
1285
|
+
const aria = {
|
|
1286
|
+
label: `${category}, ${groupKey}: ${formatColumnValue(value2)}`
|
|
1287
|
+
};
|
|
1288
|
+
marks.push({
|
|
1289
|
+
type: "rect",
|
|
1290
|
+
x: subX,
|
|
1291
|
+
y: y2,
|
|
1292
|
+
width: subBandWidth,
|
|
1293
|
+
height: columnHeight,
|
|
1294
|
+
fill: color2,
|
|
1295
|
+
cornerRadius: 2,
|
|
1296
|
+
data: row,
|
|
1297
|
+
aria,
|
|
1298
|
+
orient: "vertical"
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return marks;
|
|
1303
|
+
}
|
|
1182
1304
|
function computeStackedColumns(data, categoryField, valueField, colorField, xScale, yScale, bandwidth, _baseline, scales) {
|
|
1183
1305
|
const marks = [];
|
|
1184
1306
|
const categoryGroups = groupByField(data, categoryField);
|
|
@@ -8130,7 +8252,8 @@ function computeScales(spec, chartArea, data) {
|
|
|
8130
8252
|
}
|
|
8131
8253
|
if (encoding.x) {
|
|
8132
8254
|
let xData = data;
|
|
8133
|
-
|
|
8255
|
+
const xStackDisabled = encoding.x.stack === null || encoding.x.stack === false;
|
|
8256
|
+
if (spec.markType === "bar" && encoding.color && encoding.x.type === "quantitative" && !xStackDisabled) {
|
|
8134
8257
|
const yField = encoding.y?.field;
|
|
8135
8258
|
const xField = encoding.x.field;
|
|
8136
8259
|
if (yField) {
|
|
@@ -8158,7 +8281,8 @@ function computeScales(spec, chartArea, data) {
|
|
|
8158
8281
|
if (encoding.y) {
|
|
8159
8282
|
let yData = data;
|
|
8160
8283
|
const isVerticalBar = spec.markType === "bar" && (encoding.x?.type === "nominal" || encoding.x?.type === "ordinal") && encoding.y.type === "quantitative";
|
|
8161
|
-
|
|
8284
|
+
const yStackDisabled = encoding.y.stack === null || encoding.y.stack === false;
|
|
8285
|
+
if ((isVerticalBar || spec.markType === "area") && encoding.color && encoding.y.type === "quantitative" && !yStackDisabled) {
|
|
8162
8286
|
const xField = encoding.x?.field;
|
|
8163
8287
|
const yField = encoding.y.field;
|
|
8164
8288
|
if (xField) {
|