@constela/runtime 2.0.6 → 2.0.7
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 +576 -5
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -376,7 +376,6 @@ function createTypedStateStore(definitions) {
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
// src/expression/evaluator.ts
|
|
379
|
-
import { CHART_HELPERS } from "@constela/core";
|
|
380
379
|
var SAFE_ARRAY_METHODS = /* @__PURE__ */ new Set([
|
|
381
380
|
"length",
|
|
382
381
|
"at",
|
|
@@ -959,8 +958,28 @@ var GLOBAL_FUNCTIONS = {
|
|
|
959
958
|
// Virtual scroll helpers
|
|
960
959
|
getVisibleRange: (scrollTop, itemHeight, containerHeight, overscan) => getVisibleRange(scrollTop, itemHeight, containerHeight, overscan),
|
|
961
960
|
getTotalHeight: (itemCount, itemHeight) => getTotalHeight(itemCount, itemHeight),
|
|
962
|
-
// Chart helpers
|
|
963
|
-
|
|
961
|
+
// Chart helpers - Coordinate calculation
|
|
962
|
+
normalizeValue: (value, min, max) => normalizeValue(value, min, max),
|
|
963
|
+
scaleValue: (value, domainMin, domainMax, rangeMin, rangeMax) => scaleValue(value, domainMin, domainMax, rangeMin, rangeMax),
|
|
964
|
+
getBarDimensions: (data, index, width, height, gap, orientation) => getBarDimensions(data, index, width, height, gap, orientation),
|
|
965
|
+
// Chart helpers - Path generation
|
|
966
|
+
getLinePath: (points, curved) => getLinePath(points, curved),
|
|
967
|
+
getAreaPath: (points, baseline, curved) => getAreaPath(points, baseline, curved),
|
|
968
|
+
getArcPath: (cx, cy, radius, startAngle, endAngle) => getArcPath(cx, cy, radius, startAngle, endAngle),
|
|
969
|
+
// Chart helpers - Pie/Donut
|
|
970
|
+
getPieSlices: (data, valueKey) => getPieSlices(data, valueKey),
|
|
971
|
+
getDonutSlices: (data, valueKey, innerRadius) => getDonutSlices(data, valueKey, innerRadius),
|
|
972
|
+
// Chart helpers - Radar
|
|
973
|
+
getRadarPoints: (data, valueKey, cx, cy, radius, maxValue) => getRadarPoints(data, valueKey, cx, cy, radius, maxValue),
|
|
974
|
+
getRadarAxes: (labels, cx, cy, radius) => getRadarAxes(labels, cx, cy, radius),
|
|
975
|
+
// Chart helpers - Utilities
|
|
976
|
+
getChartBounds: (data, valueKey) => getChartBounds(data, valueKey),
|
|
977
|
+
getLinePoints: (data, valueKey, width, height, padding) => getLinePoints(data, valueKey, width, height, padding),
|
|
978
|
+
generateTicks: (min, max, count) => generateTicks(min, max, count),
|
|
979
|
+
// Chart helpers - Data aggregation
|
|
980
|
+
binData: (data, valueKey, binCount) => binData(data, valueKey, binCount),
|
|
981
|
+
aggregateData: (data, groupKey, valueKey, aggregation) => aggregateData(data, groupKey, valueKey, aggregation),
|
|
982
|
+
downsample: (data, targetCount, method) => downsample(data, targetCount, method)
|
|
964
983
|
};
|
|
965
984
|
function callGlobalFunction(method, args) {
|
|
966
985
|
const fn = GLOBAL_FUNCTIONS[method];
|
|
@@ -1293,6 +1312,548 @@ function evaluateBinary(op, left, right, ctx) {
|
|
|
1293
1312
|
throw new Error("Unknown binary operator: " + op);
|
|
1294
1313
|
}
|
|
1295
1314
|
}
|
|
1315
|
+
function normalizeValue(value, min, max) {
|
|
1316
|
+
if (typeof value !== "number" || typeof min !== "number" || typeof max !== "number") {
|
|
1317
|
+
return void 0;
|
|
1318
|
+
}
|
|
1319
|
+
if (max === min) {
|
|
1320
|
+
return 0;
|
|
1321
|
+
}
|
|
1322
|
+
return (value - min) / (max - min);
|
|
1323
|
+
}
|
|
1324
|
+
function scaleValue(value, domainMin, domainMax, rangeMin, rangeMax) {
|
|
1325
|
+
if (typeof value !== "number" || typeof domainMin !== "number" || typeof domainMax !== "number" || typeof rangeMin !== "number" || typeof rangeMax !== "number") {
|
|
1326
|
+
return void 0;
|
|
1327
|
+
}
|
|
1328
|
+
if (domainMax === domainMin) {
|
|
1329
|
+
return rangeMin;
|
|
1330
|
+
}
|
|
1331
|
+
const normalized = (value - domainMin) / (domainMax - domainMin);
|
|
1332
|
+
return rangeMin + normalized * (rangeMax - rangeMin);
|
|
1333
|
+
}
|
|
1334
|
+
function getBarDimensions(data, index, width, height, gap, orientation) {
|
|
1335
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
1336
|
+
return void 0;
|
|
1337
|
+
}
|
|
1338
|
+
if (typeof index !== "number" || index < 0 || index >= data.length) {
|
|
1339
|
+
return void 0;
|
|
1340
|
+
}
|
|
1341
|
+
if (typeof width !== "number" || typeof height !== "number" || typeof gap !== "number") {
|
|
1342
|
+
return void 0;
|
|
1343
|
+
}
|
|
1344
|
+
const isVertical = orientation === "vertical";
|
|
1345
|
+
const barCount = data.length;
|
|
1346
|
+
const values = data.map((d2) => typeof d2 === "number" ? d2 : 0);
|
|
1347
|
+
const maxValue = Math.max(...values);
|
|
1348
|
+
if (isVertical) {
|
|
1349
|
+
const totalGap = gap * (barCount + 1);
|
|
1350
|
+
const barWidth = (width - totalGap) / barCount;
|
|
1351
|
+
const barX = gap + index * (barWidth + gap);
|
|
1352
|
+
const value = values[index] ?? 0;
|
|
1353
|
+
const barHeight = maxValue > 0 ? value / maxValue * height : 0;
|
|
1354
|
+
const barY = height - barHeight;
|
|
1355
|
+
return {
|
|
1356
|
+
x: barX,
|
|
1357
|
+
y: barY,
|
|
1358
|
+
width: barWidth,
|
|
1359
|
+
height: barHeight
|
|
1360
|
+
};
|
|
1361
|
+
} else {
|
|
1362
|
+
const totalGap = gap * barCount;
|
|
1363
|
+
const barHeight = (height - totalGap) / barCount;
|
|
1364
|
+
const barY = gap + index * (barHeight + gap);
|
|
1365
|
+
const value = values[index] ?? 0;
|
|
1366
|
+
const barWidth = maxValue > 0 ? value / maxValue * width : 0;
|
|
1367
|
+
return {
|
|
1368
|
+
x: 0,
|
|
1369
|
+
y: barY,
|
|
1370
|
+
width: barWidth,
|
|
1371
|
+
height: barHeight
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
function getCurvedPath(points) {
|
|
1376
|
+
if (points.length < 2) {
|
|
1377
|
+
return points.length === 1 ? `M${points[0].x},${points[0].y}` : "";
|
|
1378
|
+
}
|
|
1379
|
+
if (points.length === 2) {
|
|
1380
|
+
return `M${points[0].x},${points[0].y} L${points[1].x},${points[1].y}`;
|
|
1381
|
+
}
|
|
1382
|
+
const pathParts = [`M${points[0].x},${points[0].y}`];
|
|
1383
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
1384
|
+
const p0 = points[Math.max(0, i - 1)];
|
|
1385
|
+
const p1 = points[i];
|
|
1386
|
+
const p2 = points[i + 1];
|
|
1387
|
+
const p3 = points[Math.min(points.length - 1, i + 2)];
|
|
1388
|
+
const cp1x = p1.x + (p2.x - p0.x) / 6;
|
|
1389
|
+
const cp1y = p1.y + (p2.y - p0.y) / 6;
|
|
1390
|
+
const cp2x = p2.x - (p3.x - p1.x) / 6;
|
|
1391
|
+
const cp2y = p2.y - (p3.y - p1.y) / 6;
|
|
1392
|
+
pathParts.push(`C${cp1x},${cp1y} ${cp2x},${cp2y} ${p2.x},${p2.y}`);
|
|
1393
|
+
}
|
|
1394
|
+
return pathParts.join(" ");
|
|
1395
|
+
}
|
|
1396
|
+
function getLinePath(points, curved) {
|
|
1397
|
+
if (!Array.isArray(points)) {
|
|
1398
|
+
return void 0;
|
|
1399
|
+
}
|
|
1400
|
+
if (points.length === 0) {
|
|
1401
|
+
return "";
|
|
1402
|
+
}
|
|
1403
|
+
for (const point of points) {
|
|
1404
|
+
if (typeof point !== "object" || point === null || typeof point.x !== "number" || typeof point.y !== "number") {
|
|
1405
|
+
return void 0;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
const validPoints = points;
|
|
1409
|
+
if (validPoints.length === 1) {
|
|
1410
|
+
return `M${validPoints[0].x},${validPoints[0].y}`;
|
|
1411
|
+
}
|
|
1412
|
+
if (curved !== true) {
|
|
1413
|
+
const pathParts = validPoints.map((point, i) => {
|
|
1414
|
+
const command = i === 0 ? "M" : "L";
|
|
1415
|
+
return `${command}${point.x},${point.y}`;
|
|
1416
|
+
});
|
|
1417
|
+
return pathParts.join(" ");
|
|
1418
|
+
}
|
|
1419
|
+
return getCurvedPath(validPoints);
|
|
1420
|
+
}
|
|
1421
|
+
function getAreaPath(points, baseline, curved) {
|
|
1422
|
+
if (!Array.isArray(points)) {
|
|
1423
|
+
return void 0;
|
|
1424
|
+
}
|
|
1425
|
+
if (typeof baseline !== "number") {
|
|
1426
|
+
return void 0;
|
|
1427
|
+
}
|
|
1428
|
+
if (points.length === 0) {
|
|
1429
|
+
return "";
|
|
1430
|
+
}
|
|
1431
|
+
for (const point of points) {
|
|
1432
|
+
if (typeof point !== "object" || point === null || typeof point.x !== "number" || typeof point.y !== "number") {
|
|
1433
|
+
return void 0;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
const validPoints = points;
|
|
1437
|
+
let upperPath;
|
|
1438
|
+
if (curved === true && validPoints.length > 2) {
|
|
1439
|
+
upperPath = getCurvedPath(validPoints);
|
|
1440
|
+
} else {
|
|
1441
|
+
upperPath = validPoints.map((p2, i) => (i === 0 ? "M" : "L") + `${p2.x},${p2.y}`).join(" ");
|
|
1442
|
+
}
|
|
1443
|
+
const lastPoint = validPoints[validPoints.length - 1];
|
|
1444
|
+
const firstPoint = validPoints[0];
|
|
1445
|
+
return `${upperPath} L${lastPoint.x},${baseline} L${firstPoint.x},${baseline} Z`;
|
|
1446
|
+
}
|
|
1447
|
+
function getArcPath(cx, cy, radius, startAngle, endAngle) {
|
|
1448
|
+
if (typeof cx !== "number" || typeof cy !== "number" || typeof radius !== "number" || typeof startAngle !== "number" || typeof endAngle !== "number") {
|
|
1449
|
+
return void 0;
|
|
1450
|
+
}
|
|
1451
|
+
if (radius <= 0) {
|
|
1452
|
+
return void 0;
|
|
1453
|
+
}
|
|
1454
|
+
const x1 = cx + radius * Math.cos(startAngle);
|
|
1455
|
+
const y1 = cy + radius * Math.sin(startAngle);
|
|
1456
|
+
const x2 = cx + radius * Math.cos(endAngle);
|
|
1457
|
+
const y2 = cy + radius * Math.sin(endAngle);
|
|
1458
|
+
const angleDiff = endAngle - startAngle;
|
|
1459
|
+
const largeArcFlag = Math.abs(angleDiff) > Math.PI ? 1 : 0;
|
|
1460
|
+
return `M${x1},${y1} A${radius},${radius} 0 ${largeArcFlag},1 ${x2},${y2}`;
|
|
1461
|
+
}
|
|
1462
|
+
function getPieSlices(data, valueKey) {
|
|
1463
|
+
if (!Array.isArray(data)) {
|
|
1464
|
+
return void 0;
|
|
1465
|
+
}
|
|
1466
|
+
if (typeof valueKey !== "string") {
|
|
1467
|
+
return void 0;
|
|
1468
|
+
}
|
|
1469
|
+
if (data.length === 0) {
|
|
1470
|
+
return [];
|
|
1471
|
+
}
|
|
1472
|
+
const values = data.map((item) => {
|
|
1473
|
+
if (typeof item !== "object" || item === null) return 0;
|
|
1474
|
+
const val = item[valueKey];
|
|
1475
|
+
return typeof val === "number" ? val : 0;
|
|
1476
|
+
});
|
|
1477
|
+
const total = values.reduce((sum, val) => sum + val, 0);
|
|
1478
|
+
const slices = [];
|
|
1479
|
+
let currentAngle = 0;
|
|
1480
|
+
for (let i = 0; i < values.length; i++) {
|
|
1481
|
+
const value = values[i];
|
|
1482
|
+
const percentage = total > 0 ? value / total * 100 : 0;
|
|
1483
|
+
const angleSpan = total > 0 ? value / total * Math.PI * 2 : 0;
|
|
1484
|
+
slices.push({
|
|
1485
|
+
startAngle: currentAngle,
|
|
1486
|
+
endAngle: currentAngle + angleSpan,
|
|
1487
|
+
value,
|
|
1488
|
+
percentage
|
|
1489
|
+
});
|
|
1490
|
+
currentAngle += angleSpan;
|
|
1491
|
+
}
|
|
1492
|
+
return slices;
|
|
1493
|
+
}
|
|
1494
|
+
function getDonutSlices(data, valueKey, innerRadius) {
|
|
1495
|
+
if (typeof innerRadius !== "number" || innerRadius < 0) {
|
|
1496
|
+
return void 0;
|
|
1497
|
+
}
|
|
1498
|
+
const pieSlices = getPieSlices(data, valueKey);
|
|
1499
|
+
if (pieSlices === void 0) {
|
|
1500
|
+
return void 0;
|
|
1501
|
+
}
|
|
1502
|
+
const outerRadius = 100;
|
|
1503
|
+
return pieSlices.map((slice) => ({
|
|
1504
|
+
...slice,
|
|
1505
|
+
outerRadius,
|
|
1506
|
+
innerRadius
|
|
1507
|
+
}));
|
|
1508
|
+
}
|
|
1509
|
+
function getRadarPoints(data, valueKey, cx, cy, radius, maxValue) {
|
|
1510
|
+
if (!Array.isArray(data)) {
|
|
1511
|
+
return void 0;
|
|
1512
|
+
}
|
|
1513
|
+
if (typeof valueKey !== "string") {
|
|
1514
|
+
return void 0;
|
|
1515
|
+
}
|
|
1516
|
+
if (typeof cx !== "number" || typeof cy !== "number" || typeof radius !== "number" || typeof maxValue !== "number") {
|
|
1517
|
+
return void 0;
|
|
1518
|
+
}
|
|
1519
|
+
if (maxValue <= 0) {
|
|
1520
|
+
return void 0;
|
|
1521
|
+
}
|
|
1522
|
+
if (data.length === 0) {
|
|
1523
|
+
return [];
|
|
1524
|
+
}
|
|
1525
|
+
const points = [];
|
|
1526
|
+
const angleStep = Math.PI * 2 / data.length;
|
|
1527
|
+
for (let i = 0; i < data.length; i++) {
|
|
1528
|
+
const item = data[i];
|
|
1529
|
+
const value = typeof item === "object" && item !== null ? item[valueKey] : 0;
|
|
1530
|
+
const numValue = typeof value === "number" ? value : 0;
|
|
1531
|
+
const scaledRadius = numValue / maxValue * radius;
|
|
1532
|
+
const angle = -Math.PI / 2 + i * angleStep;
|
|
1533
|
+
const x2 = cx + scaledRadius * Math.cos(angle);
|
|
1534
|
+
const y2 = cy + scaledRadius * Math.sin(angle);
|
|
1535
|
+
points.push({ x: x2, y: y2 });
|
|
1536
|
+
}
|
|
1537
|
+
return points;
|
|
1538
|
+
}
|
|
1539
|
+
function getRadarAxes(labels, cx, cy, radius) {
|
|
1540
|
+
if (!Array.isArray(labels)) {
|
|
1541
|
+
return void 0;
|
|
1542
|
+
}
|
|
1543
|
+
if (typeof cx !== "number" || typeof cy !== "number" || typeof radius !== "number") {
|
|
1544
|
+
return void 0;
|
|
1545
|
+
}
|
|
1546
|
+
if (radius < 0) {
|
|
1547
|
+
return void 0;
|
|
1548
|
+
}
|
|
1549
|
+
if (labels.length === 0) {
|
|
1550
|
+
return [];
|
|
1551
|
+
}
|
|
1552
|
+
const axes = [];
|
|
1553
|
+
const angleStep = Math.PI * 2 / labels.length;
|
|
1554
|
+
for (let i = 0; i < labels.length; i++) {
|
|
1555
|
+
const label = String(labels[i]);
|
|
1556
|
+
const angle = -Math.PI / 2 + i * angleStep;
|
|
1557
|
+
const x2 = cx + radius * Math.cos(angle);
|
|
1558
|
+
const y2 = cy + radius * Math.sin(angle);
|
|
1559
|
+
axes.push({
|
|
1560
|
+
x1: cx,
|
|
1561
|
+
y1: cy,
|
|
1562
|
+
x2,
|
|
1563
|
+
y2,
|
|
1564
|
+
label,
|
|
1565
|
+
angle
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
return axes;
|
|
1569
|
+
}
|
|
1570
|
+
function getChartBounds(data, valueKey) {
|
|
1571
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
1572
|
+
return void 0;
|
|
1573
|
+
}
|
|
1574
|
+
if (typeof valueKey !== "string") {
|
|
1575
|
+
return void 0;
|
|
1576
|
+
}
|
|
1577
|
+
const values = [];
|
|
1578
|
+
for (const item of data) {
|
|
1579
|
+
if (typeof item !== "object" || item === null) {
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1582
|
+
const val = item[valueKey];
|
|
1583
|
+
if (typeof val === "number") {
|
|
1584
|
+
values.push(val);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
if (values.length === 0) {
|
|
1588
|
+
return void 0;
|
|
1589
|
+
}
|
|
1590
|
+
return {
|
|
1591
|
+
min: Math.min(...values),
|
|
1592
|
+
max: Math.max(...values)
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
function getLinePoints(data, valueKey, width, height, padding) {
|
|
1596
|
+
if (!Array.isArray(data) || data.length === 0) return void 0;
|
|
1597
|
+
if (typeof valueKey !== "string") return void 0;
|
|
1598
|
+
if (typeof width !== "number" || typeof height !== "number") return void 0;
|
|
1599
|
+
if (width <= 0 || height <= 0) return void 0;
|
|
1600
|
+
const pad = typeof padding === "number" ? padding : 40;
|
|
1601
|
+
if (typeof padding !== "undefined" && typeof padding !== "number") return void 0;
|
|
1602
|
+
const bounds = getChartBounds(data, valueKey);
|
|
1603
|
+
if (!bounds) return void 0;
|
|
1604
|
+
const { min, max } = bounds;
|
|
1605
|
+
const chartWidth = width - pad * 2;
|
|
1606
|
+
const chartHeight = height - pad * 2;
|
|
1607
|
+
return data.map((item, idx) => {
|
|
1608
|
+
const value = item[valueKey];
|
|
1609
|
+
if (typeof value !== "number") return { x: 0, y: 0 };
|
|
1610
|
+
const x2 = data.length === 1 ? pad : pad + idx / (data.length - 1) * chartWidth;
|
|
1611
|
+
const y2 = min === max ? pad + chartHeight / 2 : pad + chartHeight - scaleValue(value, min, max, 0, chartHeight);
|
|
1612
|
+
return { x: x2, y: y2 };
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
function generateTicks(min, max, count) {
|
|
1616
|
+
if (typeof min !== "number" || typeof max !== "number" || typeof count !== "number") {
|
|
1617
|
+
return [];
|
|
1618
|
+
}
|
|
1619
|
+
if (count <= 0) {
|
|
1620
|
+
return [];
|
|
1621
|
+
}
|
|
1622
|
+
if (min === max) {
|
|
1623
|
+
return [min];
|
|
1624
|
+
}
|
|
1625
|
+
if (count === 1) {
|
|
1626
|
+
return [min];
|
|
1627
|
+
}
|
|
1628
|
+
if (count === 2) {
|
|
1629
|
+
return [min, max];
|
|
1630
|
+
}
|
|
1631
|
+
const range = max - min;
|
|
1632
|
+
const rawStep = range / (count - 1);
|
|
1633
|
+
const niceStep = getNiceStep(rawStep);
|
|
1634
|
+
const niceMin = Math.floor(min / niceStep) * niceStep;
|
|
1635
|
+
const ticks = [];
|
|
1636
|
+
for (let i = 0; i < count; i++) {
|
|
1637
|
+
const tick = niceMin + i * niceStep;
|
|
1638
|
+
ticks.push(Math.round(tick * 1e10) / 1e10);
|
|
1639
|
+
}
|
|
1640
|
+
return ticks;
|
|
1641
|
+
}
|
|
1642
|
+
function getNiceStep(rawStep) {
|
|
1643
|
+
const magnitude = Math.pow(10, Math.floor(Math.log10(rawStep)));
|
|
1644
|
+
const normalized = rawStep / magnitude;
|
|
1645
|
+
let niceNormalized;
|
|
1646
|
+
if (normalized <= 1) {
|
|
1647
|
+
niceNormalized = 1;
|
|
1648
|
+
} else if (normalized <= 2) {
|
|
1649
|
+
niceNormalized = 2;
|
|
1650
|
+
} else if (normalized <= 2.5) {
|
|
1651
|
+
niceNormalized = 2.5;
|
|
1652
|
+
} else if (normalized <= 5) {
|
|
1653
|
+
niceNormalized = 5;
|
|
1654
|
+
} else {
|
|
1655
|
+
niceNormalized = 10;
|
|
1656
|
+
}
|
|
1657
|
+
return niceNormalized * magnitude;
|
|
1658
|
+
}
|
|
1659
|
+
function binData(data, valueKey, binCount) {
|
|
1660
|
+
if (!Array.isArray(data)) {
|
|
1661
|
+
return void 0;
|
|
1662
|
+
}
|
|
1663
|
+
if (typeof valueKey !== "string") {
|
|
1664
|
+
return void 0;
|
|
1665
|
+
}
|
|
1666
|
+
if (typeof binCount !== "number" || binCount <= 0) {
|
|
1667
|
+
return [];
|
|
1668
|
+
}
|
|
1669
|
+
if (data.length === 0) {
|
|
1670
|
+
return [];
|
|
1671
|
+
}
|
|
1672
|
+
const values = [];
|
|
1673
|
+
for (const item of data) {
|
|
1674
|
+
if (typeof item !== "object" || item === null) continue;
|
|
1675
|
+
const val = item[valueKey];
|
|
1676
|
+
if (typeof val === "number") {
|
|
1677
|
+
values.push(val);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
if (values.length === 0) {
|
|
1681
|
+
return [];
|
|
1682
|
+
}
|
|
1683
|
+
const minVal = Math.min(...values);
|
|
1684
|
+
const maxVal = Math.max(...values);
|
|
1685
|
+
const binWidth = maxVal === minVal ? 1 : (maxVal - minVal) / binCount;
|
|
1686
|
+
const bins = [];
|
|
1687
|
+
for (let i = 0; i < binCount; i++) {
|
|
1688
|
+
bins.push({
|
|
1689
|
+
binStart: minVal + i * binWidth,
|
|
1690
|
+
binEnd: minVal + (i + 1) * binWidth,
|
|
1691
|
+
count: 0,
|
|
1692
|
+
values: []
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
for (const val of values) {
|
|
1696
|
+
let binIndex = Math.floor((val - minVal) / binWidth);
|
|
1697
|
+
if (binIndex >= binCount) {
|
|
1698
|
+
binIndex = binCount - 1;
|
|
1699
|
+
}
|
|
1700
|
+
bins[binIndex].count++;
|
|
1701
|
+
bins[binIndex].values.push(val);
|
|
1702
|
+
}
|
|
1703
|
+
return bins;
|
|
1704
|
+
}
|
|
1705
|
+
function aggregateData(data, groupKey, valueKey, aggregation) {
|
|
1706
|
+
if (!Array.isArray(data)) {
|
|
1707
|
+
return void 0;
|
|
1708
|
+
}
|
|
1709
|
+
if (typeof groupKey !== "string" || typeof valueKey !== "string") {
|
|
1710
|
+
return void 0;
|
|
1711
|
+
}
|
|
1712
|
+
const validAggregations = /* @__PURE__ */ new Set(["sum", "avg", "min", "max", "count"]);
|
|
1713
|
+
if (typeof aggregation !== "string" || !validAggregations.has(aggregation)) {
|
|
1714
|
+
return void 0;
|
|
1715
|
+
}
|
|
1716
|
+
if (data.length === 0) {
|
|
1717
|
+
return [];
|
|
1718
|
+
}
|
|
1719
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1720
|
+
for (const item of data) {
|
|
1721
|
+
if (typeof item !== "object" || item === null) continue;
|
|
1722
|
+
const obj = item;
|
|
1723
|
+
const group = String(obj[groupKey] ?? "");
|
|
1724
|
+
const val = obj[valueKey];
|
|
1725
|
+
const numVal = typeof val === "number" ? val : 0;
|
|
1726
|
+
if (!groups.has(group)) {
|
|
1727
|
+
groups.set(group, []);
|
|
1728
|
+
}
|
|
1729
|
+
groups.get(group).push(numVal);
|
|
1730
|
+
}
|
|
1731
|
+
const result = [];
|
|
1732
|
+
for (const [group, values] of groups) {
|
|
1733
|
+
let aggregatedValue;
|
|
1734
|
+
switch (aggregation) {
|
|
1735
|
+
case "sum":
|
|
1736
|
+
aggregatedValue = values.reduce((sum, v2) => sum + v2, 0);
|
|
1737
|
+
break;
|
|
1738
|
+
case "avg":
|
|
1739
|
+
aggregatedValue = values.reduce((sum, v2) => sum + v2, 0) / values.length;
|
|
1740
|
+
break;
|
|
1741
|
+
case "min":
|
|
1742
|
+
aggregatedValue = Math.min(...values);
|
|
1743
|
+
break;
|
|
1744
|
+
case "max":
|
|
1745
|
+
aggregatedValue = Math.max(...values);
|
|
1746
|
+
break;
|
|
1747
|
+
case "count":
|
|
1748
|
+
aggregatedValue = values.length;
|
|
1749
|
+
break;
|
|
1750
|
+
default:
|
|
1751
|
+
aggregatedValue = 0;
|
|
1752
|
+
}
|
|
1753
|
+
result.push({ group, value: aggregatedValue });
|
|
1754
|
+
}
|
|
1755
|
+
return result;
|
|
1756
|
+
}
|
|
1757
|
+
function downsample(data, targetCount, method) {
|
|
1758
|
+
if (!Array.isArray(data)) {
|
|
1759
|
+
return void 0;
|
|
1760
|
+
}
|
|
1761
|
+
if (typeof targetCount !== "number" || targetCount <= 0) {
|
|
1762
|
+
return void 0;
|
|
1763
|
+
}
|
|
1764
|
+
const validMethods = /* @__PURE__ */ new Set(["uniform", "lttb"]);
|
|
1765
|
+
if (typeof method !== "string" || !validMethods.has(method)) {
|
|
1766
|
+
return void 0;
|
|
1767
|
+
}
|
|
1768
|
+
if (data.length === 0) {
|
|
1769
|
+
return [];
|
|
1770
|
+
}
|
|
1771
|
+
if (targetCount >= data.length) {
|
|
1772
|
+
return data;
|
|
1773
|
+
}
|
|
1774
|
+
if (method === "uniform") {
|
|
1775
|
+
return downsampleUniform(data, targetCount);
|
|
1776
|
+
} else {
|
|
1777
|
+
return downsampleLTTB(data, targetCount);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
function downsampleUniform(data, targetCount) {
|
|
1781
|
+
if (targetCount === 1) {
|
|
1782
|
+
return [data[0]];
|
|
1783
|
+
}
|
|
1784
|
+
if (targetCount === 2) {
|
|
1785
|
+
return [data[0], data[data.length - 1]];
|
|
1786
|
+
}
|
|
1787
|
+
const result = [];
|
|
1788
|
+
const step = (data.length - 1) / (targetCount - 1);
|
|
1789
|
+
for (let i = 0; i < targetCount; i++) {
|
|
1790
|
+
const index = Math.round(i * step);
|
|
1791
|
+
result.push(data[index]);
|
|
1792
|
+
}
|
|
1793
|
+
return result;
|
|
1794
|
+
}
|
|
1795
|
+
function downsampleLTTB(data, targetCount) {
|
|
1796
|
+
if (targetCount === 1) {
|
|
1797
|
+
return [data[0]];
|
|
1798
|
+
}
|
|
1799
|
+
if (targetCount === 2) {
|
|
1800
|
+
return [data[0], data[data.length - 1]];
|
|
1801
|
+
}
|
|
1802
|
+
const getXY = (point) => {
|
|
1803
|
+
if (typeof point !== "object" || point === null) {
|
|
1804
|
+
return { x: 0, y: 0 };
|
|
1805
|
+
}
|
|
1806
|
+
const obj = point;
|
|
1807
|
+
const x2 = typeof obj["x"] === "number" ? obj["x"] : typeof obj["timestamp"] === "number" ? obj["timestamp"] : 0;
|
|
1808
|
+
const y2 = typeof obj["y"] === "number" ? obj["y"] : typeof obj["value"] === "number" ? obj["value"] : 0;
|
|
1809
|
+
return { x: x2, y: y2 };
|
|
1810
|
+
};
|
|
1811
|
+
const result = [];
|
|
1812
|
+
result.push(data[0]);
|
|
1813
|
+
const numBuckets = targetCount - 2;
|
|
1814
|
+
const middleData = data.length - 2;
|
|
1815
|
+
const bucketSize = middleData / numBuckets;
|
|
1816
|
+
let prevSelectedIndex = 0;
|
|
1817
|
+
for (let bucketIndex = 0; bucketIndex < numBuckets; bucketIndex++) {
|
|
1818
|
+
const bucketStart = Math.floor(bucketIndex * bucketSize) + 1;
|
|
1819
|
+
const bucketEnd = Math.floor((bucketIndex + 1) * bucketSize) + 1;
|
|
1820
|
+
const nextBucketStart = bucketEnd;
|
|
1821
|
+
const nextBucketEnd = bucketIndex < numBuckets - 1 ? Math.floor((bucketIndex + 2) * bucketSize) + 1 : data.length;
|
|
1822
|
+
let avgX = 0;
|
|
1823
|
+
let avgY = 0;
|
|
1824
|
+
const nextLen = nextBucketEnd - nextBucketStart;
|
|
1825
|
+
if (nextLen > 0) {
|
|
1826
|
+
for (let j2 = nextBucketStart; j2 < nextBucketEnd; j2++) {
|
|
1827
|
+
const point = getXY(data[j2]);
|
|
1828
|
+
avgX += point.x;
|
|
1829
|
+
avgY += point.y;
|
|
1830
|
+
}
|
|
1831
|
+
avgX /= nextLen;
|
|
1832
|
+
avgY /= nextLen;
|
|
1833
|
+
} else {
|
|
1834
|
+
const lastPoint = getXY(data[data.length - 1]);
|
|
1835
|
+
avgX = lastPoint.x;
|
|
1836
|
+
avgY = lastPoint.y;
|
|
1837
|
+
}
|
|
1838
|
+
const pointA = getXY(data[prevSelectedIndex]);
|
|
1839
|
+
let maxArea = -1;
|
|
1840
|
+
let maxAreaIndex = bucketStart;
|
|
1841
|
+
for (let j2 = bucketStart; j2 < bucketEnd; j2++) {
|
|
1842
|
+
const pointB = getXY(data[j2]);
|
|
1843
|
+
const area = Math.abs(
|
|
1844
|
+
pointA.x * (pointB.y - avgY) + pointB.x * (avgY - pointA.y) + avgX * (pointA.y - pointB.y)
|
|
1845
|
+
);
|
|
1846
|
+
if (area > maxArea) {
|
|
1847
|
+
maxArea = area;
|
|
1848
|
+
maxAreaIndex = j2;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
result.push(data[maxAreaIndex]);
|
|
1852
|
+
prevSelectedIndex = maxAreaIndex;
|
|
1853
|
+
}
|
|
1854
|
+
result.push(data[data.length - 1]);
|
|
1855
|
+
return result;
|
|
1856
|
+
}
|
|
1296
1857
|
function evaluateStyle(expr, ctx) {
|
|
1297
1858
|
const preset = ctx.styles?.[expr.name];
|
|
1298
1859
|
if (!preset) return void 0;
|
|
@@ -15007,13 +15568,17 @@ function renderPortal(node, ctx) {
|
|
|
15007
15568
|
}
|
|
15008
15569
|
function createLocalStateStore(stateDefs, ctx) {
|
|
15009
15570
|
const signals = {};
|
|
15571
|
+
const evaluatedValues = {};
|
|
15010
15572
|
for (const [name, def] of Object.entries(stateDefs)) {
|
|
15011
15573
|
const initial = def.initial;
|
|
15012
15574
|
let initialValue;
|
|
15013
15575
|
if (initial && typeof initial === "object" && "expr" in initial) {
|
|
15014
15576
|
const evalCtx = {
|
|
15015
15577
|
state: ctx.state,
|
|
15016
|
-
locals:
|
|
15578
|
+
locals: {
|
|
15579
|
+
...ctx.locals,
|
|
15580
|
+
...evaluatedValues
|
|
15581
|
+
}
|
|
15017
15582
|
};
|
|
15018
15583
|
if (ctx.route) evalCtx.route = ctx.route;
|
|
15019
15584
|
if (ctx.imports) evalCtx.imports = ctx.imports;
|
|
@@ -15022,6 +15587,7 @@ function createLocalStateStore(stateDefs, ctx) {
|
|
|
15022
15587
|
initialValue = initial;
|
|
15023
15588
|
}
|
|
15024
15589
|
signals[name] = createSignal(initialValue);
|
|
15590
|
+
evaluatedValues[name] = initialValue;
|
|
15025
15591
|
}
|
|
15026
15592
|
return {
|
|
15027
15593
|
get(name) {
|
|
@@ -15352,13 +15918,17 @@ function createReactiveLocals2(baseLocals, itemSignal, indexSignal, itemName, in
|
|
|
15352
15918
|
}
|
|
15353
15919
|
function createLocalStateStore2(stateDefs, ctx) {
|
|
15354
15920
|
const signals = {};
|
|
15921
|
+
const evaluatedValues = {};
|
|
15355
15922
|
for (const [name, def] of Object.entries(stateDefs)) {
|
|
15356
15923
|
const initial = def.initial;
|
|
15357
15924
|
let initialValue;
|
|
15358
15925
|
if (initial && typeof initial === "object" && "expr" in initial) {
|
|
15359
15926
|
const evalCtx = {
|
|
15360
15927
|
state: ctx.state,
|
|
15361
|
-
locals:
|
|
15928
|
+
locals: {
|
|
15929
|
+
...ctx.locals,
|
|
15930
|
+
...evaluatedValues
|
|
15931
|
+
}
|
|
15362
15932
|
};
|
|
15363
15933
|
if (ctx.route) evalCtx.route = ctx.route;
|
|
15364
15934
|
if (ctx.imports) evalCtx.imports = ctx.imports;
|
|
@@ -15367,6 +15937,7 @@ function createLocalStateStore2(stateDefs, ctx) {
|
|
|
15367
15937
|
initialValue = initial;
|
|
15368
15938
|
}
|
|
15369
15939
|
signals[name] = createSignal(initialValue);
|
|
15940
|
+
evaluatedValues[name] = initialValue;
|
|
15370
15941
|
}
|
|
15371
15942
|
return {
|
|
15372
15943
|
get(name) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/runtime",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "Runtime DOM renderer for Constela UI framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"dompurify": "^3.3.1",
|
|
19
19
|
"marked": "^17.0.1",
|
|
20
20
|
"shiki": "^3.20.0",
|
|
21
|
-
"@constela/
|
|
22
|
-
"@constela/
|
|
21
|
+
"@constela/compiler": "0.15.10",
|
|
22
|
+
"@constela/core": "0.18.4"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/dompurify": "^3.2.0",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"tsup": "^8.0.0",
|
|
30
30
|
"typescript": "^5.3.0",
|
|
31
31
|
"vitest": "^2.0.0",
|
|
32
|
-
"@constela/server": "14.0.
|
|
32
|
+
"@constela/server": "14.0.5"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@constela/ai": "3.0.
|
|
35
|
+
"@constela/ai": "3.0.4"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"@constela/ai": {
|