@evergis/react 4.0.153 → 4.0.155
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/components/Dashboard/componentTypes.d.ts +3 -3
- package/dist/components/Dashboard/components/Chart/ChartPlotContext.d.ts +24 -0
- package/dist/components/Dashboard/components/Chart/components/AxisTitles.d.ts +23 -0
- package/dist/components/Dashboard/components/Chart/styled.d.ts +33 -0
- package/dist/components/Dashboard/components/ChartLegend/index.d.ts +1 -0
- package/dist/components/Dashboard/components/ChartLegend/styled.d.ts +1 -0
- package/dist/components/Dashboard/constants.d.ts +37 -1
- package/dist/components/Dashboard/containers/AttachmentContainer/constants.d.ts +1 -0
- package/dist/components/Dashboard/containers/ChartContainer/types.d.ts +3 -1
- package/dist/components/Dashboard/containers/FiltersContainer/constants.d.ts +1 -0
- package/dist/components/Dashboard/containers/styled.d.ts +38 -0
- package/dist/components/Dashboard/elements/ElementIcon/components/AttachmentIcon/index.d.ts +7 -0
- package/dist/components/Dashboard/elements/ElementIcon/components/AttachmentIcon/styled.d.ts +3 -0
- package/dist/components/Dashboard/elements/ElementIcon/components/AttachmentIcon/types.d.ts +15 -0
- package/dist/components/Dashboard/elements/ElementIcon/constants.d.ts +2 -0
- package/dist/components/Dashboard/elements/ElementIcon/utils/getAttachmentIcon.d.ts +7 -0
- package/dist/components/Dashboard/hooks/index.d.ts +3 -0
- package/dist/components/Dashboard/hooks/useChartAxisAttributes.d.ts +19 -0
- package/dist/components/Dashboard/hooks/useChartAxisTickFormat.d.ts +18 -0
- package/dist/components/Dashboard/hooks/useChartAxisTitles.d.ts +32 -0
- package/dist/components/Dashboard/types.d.ts +59 -1
- package/dist/components/Dashboard/utils/chartAxis.d.ts +29 -0
- package/dist/components/Dashboard/utils/index.d.ts +1 -0
- package/dist/index.js +679 -89
- package/dist/index.js.map +1 -1
- package/dist/react.esm.js +662 -90
- package/dist/react.esm.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -3358,6 +3358,46 @@ const MAX_CHART_WIDTH = 300;
|
|
|
3358
3358
|
const DEFAULT_CHART_ANGLE = 4;
|
|
3359
3359
|
const DEFAULT_CHART_HEIGHT = 90;
|
|
3360
3360
|
const STACK_BAR_TOTAL_HEIGHT = 20;
|
|
3361
|
+
/** Высота строки текста подписи оси (шрифт 10px) — по макету. */
|
|
3362
|
+
const AXIS_TITLE_LINE_HEIGHT = 12;
|
|
3363
|
+
/** Зазор между подписью оси и значениями шкалы — по макету. */
|
|
3364
|
+
const AXIS_TITLE_GAP = 4;
|
|
3365
|
+
/**
|
|
3366
|
+
* Поле SVG графика под выступ крайних делений шкалы значений.
|
|
3367
|
+
*
|
|
3368
|
+
* d3 центрирует подпись деления на его линии (`dy: 0.32em`), поэтому у крайнего деления половина подписи
|
|
3369
|
+
* выходит за поле графика. Если поле вплотную к краю SVG, половина оказывается за SVG, и её срезает
|
|
3370
|
+
* обёртка: у линейного графика в fill — `ChartFillMeasure` (`overflow: hidden`), у барчарта всегда —
|
|
3371
|
+
* `BarChartContainer` (`overflow-x: hidden`, отчего и по вертикали содержимое не `visible`).
|
|
3372
|
+
*/
|
|
3373
|
+
const Y_AXIS_LABEL_OVERHANG = 6;
|
|
3374
|
+
/** Зазор от строки горизонтальных подписей осей до верхней линии сетки — по макету. */
|
|
3375
|
+
const AXIS_TITLE_TOP_GRID_GAP = 9;
|
|
3376
|
+
/** Число делений шкал линейного графика; у левой и правой шкал оно общее, чтобы совпадала сетка. */
|
|
3377
|
+
const LINE_CHART_Y_TICKS = 4;
|
|
3378
|
+
/**
|
|
3379
|
+
* Высота строки горизонтальных подписей осей (`axis.titlePosition: "top"`). Вместе с верхним полем SVG
|
|
3380
|
+
* (Y_AXIS_LABEL_OVERHANG) даёт строку текста и 9px до верхней линии сетки: деление центрировано на
|
|
3381
|
+
* линии и выступает над ней на ~5px, так что между подписью и значением шкалы остаются те же 4px,
|
|
3382
|
+
* что и у вертикальной подписи.
|
|
3383
|
+
*/
|
|
3384
|
+
const AXIS_TITLE_TOP_HEIGHT = AXIS_TITLE_LINE_HEIGHT + AXIS_TITLE_TOP_GRID_GAP - Y_AXIS_LABEL_OVERHANG;
|
|
3385
|
+
/**
|
|
3386
|
+
* Пустая полоса под подписями оси X внутри SVG линейного графика: под ось зарезервировано
|
|
3387
|
+
* LINE_CHART_LABEL_MARGIN (20px), а подписи сдвинуты вниз на +16px, поэтому строка подписи
|
|
3388
|
+
* (шрифт d3 10px) кончается примерно в 14px от низа SVG.
|
|
3389
|
+
*/
|
|
3390
|
+
const LINE_CHART_X_LABELS_BLANK = 14;
|
|
3391
|
+
/** Зазор между подписями оси X и подписью самой оси — по макету. */
|
|
3392
|
+
const X_AXIS_TITLE_GAP = 4;
|
|
3393
|
+
/** Зазор между подписью оси X и легендой, идущей отдельной строкой, — по макету. */
|
|
3394
|
+
const X_AXIS_TITLE_LEGEND_GAP = 8;
|
|
3395
|
+
/** Выравнивание подписи оси X → `justify-content` строки-подвала графика. */
|
|
3396
|
+
const CHART_AXIS_ALIGN_JUSTIFY = {
|
|
3397
|
+
left: "flex-start",
|
|
3398
|
+
center: "center",
|
|
3399
|
+
right: "flex-end",
|
|
3400
|
+
};
|
|
3361
3401
|
const FILTER_PREFIX = "%";
|
|
3362
3402
|
/** Имя системного геометрического фильтра — выделение, нарисованное пользователем на карте. */
|
|
3363
3403
|
const GEOMETRY_FILTER_NAME = "geometry";
|
|
@@ -3467,7 +3507,76 @@ const CONTAINER_BODY_FILL_STYLE = {
|
|
|
3467
3507
|
*/
|
|
3468
3508
|
const CONTAINERS_GROUP_DEFAULTS = { boxSizing: "border-box" };
|
|
3469
3509
|
|
|
3470
|
-
|
|
3510
|
+
/** Обёртка линейного графика: строка горизонтальных подписей над телом, вертикальные — по бокам. */
|
|
3511
|
+
const LineChartLayout = styled.div.withConfig({ displayName: "LineChartLayout", componentId: "sc-gmzloe" }) `
|
|
3512
|
+
display: flex;
|
|
3513
|
+
flex-direction: column;
|
|
3514
|
+
width: 100%;
|
|
3515
|
+
height: 100%;
|
|
3516
|
+
min-width: 0;
|
|
3517
|
+
min-height: 0;
|
|
3518
|
+
`;
|
|
3519
|
+
const LineChartRow = styled.div.withConfig({ displayName: "LineChartRow", componentId: "sc-dxzi1m" }) `
|
|
3520
|
+
display: flex;
|
|
3521
|
+
flex: 1 1 auto;
|
|
3522
|
+
align-items: stretch;
|
|
3523
|
+
min-width: 0;
|
|
3524
|
+
min-height: 0;
|
|
3525
|
+
`;
|
|
3526
|
+
/**
|
|
3527
|
+
* Тело линейного графика между вертикальными подписями осей. Забирает всю оставшуюся ширину строки —
|
|
3528
|
+
* её и измеряем, чтобы отдать d3 пиксельную ширину: подписи бывают в одну и в две строки.
|
|
3529
|
+
*/
|
|
3530
|
+
const LineChartBody = styled.div.withConfig({ displayName: "LineChartBody", componentId: "sc-1sbsc7u" }) `
|
|
3531
|
+
flex: 1 1 auto;
|
|
3532
|
+
min-width: 0;
|
|
3533
|
+
`;
|
|
3534
|
+
const AxisTitlesRow = styled.div.withConfig({ displayName: "AxisTitlesRow", componentId: "sc-1hskx6f" }) `
|
|
3535
|
+
display: flex;
|
|
3536
|
+
flex: 0 0 ${AXIS_TITLE_TOP_HEIGHT}px;
|
|
3537
|
+
justify-content: space-between;
|
|
3538
|
+
align-items: flex-start;
|
|
3539
|
+
gap: 0.5rem;
|
|
3540
|
+
height: ${AXIS_TITLE_TOP_HEIGHT}px;
|
|
3541
|
+
min-width: 0;
|
|
3542
|
+
`;
|
|
3543
|
+
const AxisTitleText = styled.div.withConfig({ displayName: "AxisTitleText", componentId: "sc-9shs4l" }) `
|
|
3544
|
+
font-size: 0.625rem;
|
|
3545
|
+
line-height: ${AXIS_TITLE_LINE_HEIGHT}px;
|
|
3546
|
+
color: ${({ $color, theme: { palette } }) => $color || palette.iconDisabled};
|
|
3547
|
+
`;
|
|
3548
|
+
const AxisTitleTop = styled(AxisTitleText).withConfig({ displayName: "AxisTitleTop", componentId: "sc-pv3dgm" }) `
|
|
3549
|
+
overflow: hidden;
|
|
3550
|
+
white-space: nowrap;
|
|
3551
|
+
text-overflow: ellipsis;
|
|
3552
|
+
`;
|
|
3553
|
+
/**
|
|
3554
|
+
* Колонка вертикальной подписи оси: ширина — по тексту (одна или две строки) плюс зазор до значений
|
|
3555
|
+
* шкалы. Вертикальные отступы колонки повторяют поля SVG, поэтому подпись центрируется по полю
|
|
3556
|
+
* графика, а не по всей высоте вместе с подписями оси X.
|
|
3557
|
+
*/
|
|
3558
|
+
const AxisTitleSideColumn = styled.div.withConfig({ displayName: "AxisTitleSideColumn", componentId: "sc-11nmkfu" }) `
|
|
3559
|
+
display: flex;
|
|
3560
|
+
flex: 0 0 auto;
|
|
3561
|
+
align-items: center;
|
|
3562
|
+
justify-content: center;
|
|
3563
|
+
box-sizing: border-box;
|
|
3564
|
+
padding: ${({ $plotTop }) => $plotTop}px 0 ${({ $plotBottom }) => $plotBottom}px;
|
|
3565
|
+
${({ $side }) => ($side === "left" ? "padding-right" : "padding-left")}: ${AXIS_TITLE_GAP}px;
|
|
3566
|
+
`;
|
|
3567
|
+
/**
|
|
3568
|
+
* Сам текст вертикальной подписи. Поворот вынесен на внутренний элемент: повёрнутая целиком колонка
|
|
3569
|
+
* перенесла бы зазор до шкалы на внешнюю сторону. Левая подпись читается снизу вверх, правая — сверху
|
|
3570
|
+
* вниз, как на макете.
|
|
3571
|
+
*/
|
|
3572
|
+
const AxisTitleSide = styled(AxisTitleText).withConfig({ displayName: "AxisTitleSide", componentId: "sc-lretfl" }) `
|
|
3573
|
+
max-height: 100%;
|
|
3574
|
+
overflow: hidden;
|
|
3575
|
+
text-align: center;
|
|
3576
|
+
writing-mode: vertical-rl;
|
|
3577
|
+
transform: ${({ $side }) => ($side === "left" ? "rotate(180deg)" : "none")};
|
|
3578
|
+
`;
|
|
3579
|
+
const ChartTooltipTable = styled.table.withConfig({ displayName: "ChartTooltipTable", componentId: "sc-1j0a1z" }) `
|
|
3471
3580
|
td {
|
|
3472
3581
|
padding: 0;
|
|
3473
3582
|
}
|
|
@@ -3476,7 +3585,7 @@ const ChartTooltipTable = styled.table.withConfig({ displayName: "ChartTooltipTa
|
|
|
3476
3585
|
padding-bottom: 0.375rem;
|
|
3477
3586
|
}
|
|
3478
3587
|
`;
|
|
3479
|
-
const ChartTooltip = styled(uilibGl.Flex).withConfig({ displayName: "ChartTooltip", componentId: "sc-
|
|
3588
|
+
const ChartTooltip = styled(uilibGl.Flex).withConfig({ displayName: "ChartTooltip", componentId: "sc-1ps09qa" }) `
|
|
3480
3589
|
flex-wrap: nowrap;
|
|
3481
3590
|
background: rgb(48, 69, 79);
|
|
3482
3591
|
border-radius: 0.25rem;
|
|
@@ -3486,26 +3595,26 @@ const ChartTooltip = styled(uilibGl.Flex).withConfig({ displayName: "ChartToolti
|
|
|
3486
3595
|
margin-bottom: 0.25rem;
|
|
3487
3596
|
}
|
|
3488
3597
|
`;
|
|
3489
|
-
const ChartTooltipLabel = styled.div.withConfig({ displayName: "ChartTooltipLabel", componentId: "sc-
|
|
3598
|
+
const ChartTooltipLabel = styled.div.withConfig({ displayName: "ChartTooltipLabel", componentId: "sc-1chgokj" }) `
|
|
3490
3599
|
margin-right: 0.25rem;
|
|
3491
3600
|
`;
|
|
3492
|
-
const ChartTooltipColor = styled.div.withConfig({ displayName: "ChartTooltipColor", componentId: "sc-
|
|
3601
|
+
const ChartTooltipColor = styled.div.withConfig({ displayName: "ChartTooltipColor", componentId: "sc-1un94mw" }) `
|
|
3493
3602
|
width: 0.625rem;
|
|
3494
3603
|
height: 0.625rem;
|
|
3495
3604
|
background-color: ${({ $color }) => $color};
|
|
3496
3605
|
border-radius: ${({ theme: { borderRadius } }) => borderRadius.smallest};
|
|
3497
3606
|
margin-right: 0.25rem;
|
|
3498
3607
|
`;
|
|
3499
|
-
const ChartLegendColor$1 = styled.div.withConfig({ displayName: "ChartLegendColor", componentId: "sc-
|
|
3608
|
+
const ChartLegendColor$1 = styled.div.withConfig({ displayName: "ChartLegendColor", componentId: "sc-sw1cfw" }) `
|
|
3500
3609
|
width: 0.5rem;
|
|
3501
3610
|
height: 0.5rem;
|
|
3502
3611
|
background-color: ${({ $color }) => $color};
|
|
3503
3612
|
border-radius: 0.125rem;
|
|
3504
3613
|
`;
|
|
3505
|
-
const ChartLegendValue = styled.div.withConfig({ displayName: "ChartLegendValue", componentId: "sc-
|
|
3614
|
+
const ChartLegendValue = styled.div.withConfig({ displayName: "ChartLegendValue", componentId: "sc-1s1kjum" }) `
|
|
3506
3615
|
opacity: 0.65;
|
|
3507
3616
|
`;
|
|
3508
|
-
const ChartTooltipRow = styled(uilibGl.Flex).withConfig({ displayName: "ChartTooltipRow", componentId: "sc-
|
|
3617
|
+
const ChartTooltipRow = styled(uilibGl.Flex).withConfig({ displayName: "ChartTooltipRow", componentId: "sc-ftllia" }) `
|
|
3509
3618
|
display: flex;
|
|
3510
3619
|
flex-direction: row;
|
|
3511
3620
|
flex-wrap: nowrap;
|
|
@@ -3517,11 +3626,11 @@ const ChartTooltipRow = styled(uilibGl.Flex).withConfig({ displayName: "ChartToo
|
|
|
3517
3626
|
margin-right: 0.25rem;
|
|
3518
3627
|
}
|
|
3519
3628
|
`;
|
|
3520
|
-
const ChartTooltipName = styled.div.withConfig({ displayName: "ChartTooltipName", componentId: "sc-
|
|
3629
|
+
const ChartTooltipName = styled.div.withConfig({ displayName: "ChartTooltipName", componentId: "sc-16ov53z" }) `
|
|
3521
3630
|
margin-right: 1rem;
|
|
3522
3631
|
opacity: 0.65;
|
|
3523
3632
|
`;
|
|
3524
|
-
const PieChartCenter = styled.div.withConfig({ displayName: "PieChartCenter", componentId: "sc-
|
|
3633
|
+
const PieChartCenter = styled.div.withConfig({ displayName: "PieChartCenter", componentId: "sc-5m536c" }) `
|
|
3525
3634
|
position: absolute;
|
|
3526
3635
|
top: 50%;
|
|
3527
3636
|
left: 50%;
|
|
@@ -3530,7 +3639,7 @@ const PieChartCenter = styled.div.withConfig({ displayName: "PieChartCenter", co
|
|
|
3530
3639
|
font-weight: bold;
|
|
3531
3640
|
font-size: 1.5rem;
|
|
3532
3641
|
`;
|
|
3533
|
-
const ChartWrapperContainer = styled.div.withConfig({ displayName: "ChartWrapperContainer", componentId: "sc-
|
|
3642
|
+
const ChartWrapperContainer = styled.div.withConfig({ displayName: "ChartWrapperContainer", componentId: "sc-1ixngl" }) `
|
|
3534
3643
|
position: relative;
|
|
3535
3644
|
width: 100%;
|
|
3536
3645
|
`;
|
|
@@ -3539,7 +3648,7 @@ const ChartWrapperContainer = styled.div.withConfig({ displayName: "ChartWrapper
|
|
|
3539
3648
|
* на неё вешается ref для ResizeObserver — меряем обе оси без цикла. Плоский div (не Flex uilib), как соседний
|
|
3540
3649
|
* ChartWrapperContainer, чтобы ref гарантированно дошёл до DOM-узла. Тело графика центрируется внутри.
|
|
3541
3650
|
*/
|
|
3542
|
-
const ChartFillMeasure = styled.div.withConfig({ displayName: "ChartFillMeasure", componentId: "sc-
|
|
3651
|
+
const ChartFillMeasure = styled.div.withConfig({ displayName: "ChartFillMeasure", componentId: "sc-3rqqmt" }) `
|
|
3543
3652
|
position: relative;
|
|
3544
3653
|
display: flex;
|
|
3545
3654
|
align-items: center;
|
|
@@ -3557,7 +3666,7 @@ const ChartFillMeasure = styled.div.withConfig({ displayName: "ChartFillMeasure"
|
|
|
3557
3666
|
display: block;
|
|
3558
3667
|
}
|
|
3559
3668
|
`;
|
|
3560
|
-
const Tooltip = styled.div.withConfig({ displayName: "Tooltip", componentId: "sc-
|
|
3669
|
+
const Tooltip = styled.div.withConfig({ displayName: "Tooltip", componentId: "sc-k91v0" }) `
|
|
3561
3670
|
position: relative;
|
|
3562
3671
|
border-radius: 0.25rem;
|
|
3563
3672
|
background-color: rgba(28, 33, 48);
|
|
@@ -3662,7 +3771,7 @@ const LineChartStyles = styled.createGlobalStyle `
|
|
|
3662
3771
|
fill: ${({ theme: { palette } }) => palette.textDisabled};
|
|
3663
3772
|
}
|
|
3664
3773
|
`;
|
|
3665
|
-
const StyledBarChart = styled(charts.BarChart).withConfig({ displayName: "StyledBarChart", componentId: "sc-
|
|
3774
|
+
const StyledBarChart = styled(charts.BarChart).withConfig({ displayName: "StyledBarChart", componentId: "sc-16gz38d" }) `
|
|
3666
3775
|
.domain {
|
|
3667
3776
|
display: none;
|
|
3668
3777
|
}
|
|
@@ -4550,6 +4659,79 @@ const getAttributeConfigurationByName = (attributes, name) => {
|
|
|
4550
4659
|
return attributes[name];
|
|
4551
4660
|
};
|
|
4552
4661
|
|
|
4662
|
+
const EMPTY_AXIS = {};
|
|
4663
|
+
const warnedLegacySources = new Set();
|
|
4664
|
+
// Сборщик консьюмера подставляет `process.env.NODE_ENV` строкой, поэтому выражение должно быть
|
|
4665
|
+
// ровно таким — без `typeof process` и без `?.`. Сам `process` webpack 5 в браузер не завозит:
|
|
4666
|
+
// проверка `typeof process` отключила бы предупреждения и в dev. Консьюмер без сборщика получит
|
|
4667
|
+
// ReferenceError — он гасится, и предупреждений просто нет.
|
|
4668
|
+
const isDevEnvironment = (() => {
|
|
4669
|
+
try {
|
|
4670
|
+
return process.env.NODE_ENV !== "production";
|
|
4671
|
+
}
|
|
4672
|
+
catch {
|
|
4673
|
+
return false;
|
|
4674
|
+
}
|
|
4675
|
+
})();
|
|
4676
|
+
/**
|
|
4677
|
+
* Один раз на источник сообщает, что конфиг остался на устаревшей плоской форме.
|
|
4678
|
+
*
|
|
4679
|
+
* Без такого маркера момент «легаси закончилось» не наступает никогда, и временный слой
|
|
4680
|
+
* совместимости становится постоянным.
|
|
4681
|
+
*/
|
|
4682
|
+
const warnLegacyAxis = ({ dataSourceName, attributeName }) => {
|
|
4683
|
+
if (!isDevEnvironment)
|
|
4684
|
+
return;
|
|
4685
|
+
const key = `${dataSourceName ?? "?"}:${attributeName ?? "?"}`;
|
|
4686
|
+
if (warnedLegacySources.has(key))
|
|
4687
|
+
return;
|
|
4688
|
+
warnedLegacySources.add(key);
|
|
4689
|
+
// eslint-disable-next-line no-console
|
|
4690
|
+
console.warn(`[Dashboard] Источник "${key}" использует устаревшие поля оси (chartAxis/axisColor/hideAxis). ` +
|
|
4691
|
+
"Перепишите их в объект axis: { type, side, color, title, hide }. " +
|
|
4692
|
+
"Слой совместимости временный.");
|
|
4693
|
+
};
|
|
4694
|
+
/**
|
|
4695
|
+
* Настройки оси источника.
|
|
4696
|
+
*
|
|
4697
|
+
* Читает `axis`, а при его отсутствии собирает объект из устаревших плоских полей
|
|
4698
|
+
* `chartAxis` / `axisColor` / `hideAxis`. Заданный `axis` имеет приоритет: узел, который уже
|
|
4699
|
+
* правили в редакторе, не должен откатываться к старым значениям.
|
|
4700
|
+
*/
|
|
4701
|
+
const resolveAxis = (source) => {
|
|
4702
|
+
if (!source)
|
|
4703
|
+
return EMPTY_AXIS;
|
|
4704
|
+
const { axis, chartAxis, axisColor, hideAxis } = source;
|
|
4705
|
+
if (axis)
|
|
4706
|
+
return axis;
|
|
4707
|
+
if (chartAxis === undefined && axisColor === undefined && hideAxis === undefined)
|
|
4708
|
+
return EMPTY_AXIS;
|
|
4709
|
+
warnLegacyAxis(source);
|
|
4710
|
+
return {
|
|
4711
|
+
type: chartAxis === "x" ? "x" : "y",
|
|
4712
|
+
color: axisColor,
|
|
4713
|
+
hide: hideAxis,
|
|
4714
|
+
};
|
|
4715
|
+
};
|
|
4716
|
+
/**
|
|
4717
|
+
* Источник питает ось значений.
|
|
4718
|
+
*
|
|
4719
|
+
* Отсутствие `axis.type` означает `"y"`: конфиг без явного типа — самый частый случай,
|
|
4720
|
+
* и раньше такой источник молча выпадал из графика.
|
|
4721
|
+
*/
|
|
4722
|
+
const isValueAxis = (source) => (resolveAxis(source).type ?? "y") === "y";
|
|
4723
|
+
/** Сторона оси; отсутствие `axis.side` означает `"left"`. */
|
|
4724
|
+
const getAxisSide = (source) => resolveAxis(source).side ?? "left";
|
|
4725
|
+
/** Источник выводится на правой оси. */
|
|
4726
|
+
const isRightAxis = (source) => getAxisSide(source) === "right";
|
|
4727
|
+
/**
|
|
4728
|
+
* Запись описывает ось чарта, а не источник фильтра.
|
|
4729
|
+
*
|
|
4730
|
+
* `BarChartFilter` собирает `relatedConfig` вручную и осей не задаёт — ему нужна исходная палитра,
|
|
4731
|
+
* а не растянутая под число элементов.
|
|
4732
|
+
*/
|
|
4733
|
+
const hasAxisConfig = ({ axis, chartAxis }) => axis !== undefined || chartAxis !== undefined;
|
|
4734
|
+
|
|
4553
4735
|
const getAttributesConfiguration = (layer) => {
|
|
4554
4736
|
const layerAttributeConfiguration = layer?.configuration?.attributesConfiguration ?? {};
|
|
4555
4737
|
const { geometryAttribute, idAttribute } = layerAttributeConfiguration;
|
|
@@ -4574,7 +4756,7 @@ const getAttributesConfiguration = (layer) => {
|
|
|
4574
4756
|
|
|
4575
4757
|
const formatChartRelatedValue = (t, value, layerInfo, relatedAttributes) => {
|
|
4576
4758
|
const layerDefinition = getAttributesConfiguration(layerInfo);
|
|
4577
|
-
const relatedAxis = relatedAttributes.find(
|
|
4759
|
+
const relatedAxis = relatedAttributes.find(isValueAxis);
|
|
4578
4760
|
const attribute = getAttributeConfigurationByName(layerDefinition.attributes, relatedAxis?.attributeName);
|
|
4579
4761
|
return attribute
|
|
4580
4762
|
? formatAttributeValue({ t, type: attribute.type, value, stringFormat: attribute.stringFormat })
|
|
@@ -5419,11 +5601,11 @@ const getAttributeValue = (element, attributes) => {
|
|
|
5419
5601
|
return typeof value === "string" && maxLength && maxLength < value.length ? (jsxRuntime.jsx(TextTrim, { maxLength: maxLength, wordBreak: wordBreak, expandable: expandable, lineBreak: lineBreak, children: value })) : (value);
|
|
5420
5602
|
};
|
|
5421
5603
|
|
|
5422
|
-
const getChartAxes = (chartElement) => chartElement?.options?.relatedDataSources?.filter(
|
|
5604
|
+
const getChartAxes = (chartElement) => chartElement?.options?.relatedDataSources?.filter(isValueAxis);
|
|
5423
5605
|
|
|
5424
5606
|
const getChartFilterName = (relatedDataSources) => {
|
|
5425
5607
|
const relatedAttributes = relatedDataSources || [];
|
|
5426
|
-
const axes = relatedAttributes.filter(
|
|
5608
|
+
const axes = relatedAttributes.filter(isValueAxis);
|
|
5427
5609
|
return axes?.[0]?.filterName;
|
|
5428
5610
|
};
|
|
5429
5611
|
|
|
@@ -5642,13 +5824,14 @@ const GRID_TILE_SIZE = "4.5rem";
|
|
|
5642
5824
|
const LIST_ICON_SIZE = "1.5rem";
|
|
5643
5825
|
const JPG_MIME_TYPE = "image/jpeg";
|
|
5644
5826
|
const PNG_MIME_TYPE = "image/png";
|
|
5827
|
+
const SVG_MIME_TYPE = "image/svg+xml";
|
|
5645
5828
|
const IMAGE_MIME_TYPES = [
|
|
5646
5829
|
"image/apng",
|
|
5647
5830
|
"image/avif",
|
|
5648
5831
|
"image/gif",
|
|
5649
5832
|
"image/jpeg",
|
|
5650
5833
|
"image/png",
|
|
5651
|
-
|
|
5834
|
+
SVG_MIME_TYPE,
|
|
5652
5835
|
"image/webp",
|
|
5653
5836
|
];
|
|
5654
5837
|
const XLSX_MIME_TYPES = [
|
|
@@ -7053,6 +7236,96 @@ const useFetchImageWithAuth = (url) => {
|
|
|
7053
7236
|
return isExternal ? url : blobUrl;
|
|
7054
7237
|
};
|
|
7055
7238
|
|
|
7239
|
+
/**
|
|
7240
|
+
* Оси значений линейного графика и атрибуты, по `stringFormat` которых форматируются деления.
|
|
7241
|
+
*
|
|
7242
|
+
* У левой и правой осей это разные атрибуты, поэтому они резолвятся по отдельности: иначе на
|
|
7243
|
+
* правой оси окажутся единицы измерения левой.
|
|
7244
|
+
*/
|
|
7245
|
+
const useChartAxisAttributes = ({ dataSources, relatedAttributes }) => {
|
|
7246
|
+
const { layerInfos, dataSources: fetchedDataSources } = useWidgetContext();
|
|
7247
|
+
const axes = React.useMemo(() => (relatedAttributes || []).filter(isValueAxis), [relatedAttributes]);
|
|
7248
|
+
const strokeColors = React.useMemo(() => axes.map(related => resolveAxis(related).color), [axes]);
|
|
7249
|
+
const leftAxis = React.useMemo(() => axes.find(related => !isRightAxis(related)) ?? axes[0] ?? relatedAttributes?.[0], [axes, relatedAttributes]);
|
|
7250
|
+
const rightAxis = React.useMemo(() => axes.find(isRightAxis), [axes]);
|
|
7251
|
+
const resolveAxisAttribute = React.useCallback((related) => {
|
|
7252
|
+
if (!related)
|
|
7253
|
+
return undefined;
|
|
7254
|
+
const layerInfo = getDataSourceLayerInfo({
|
|
7255
|
+
layerInfos,
|
|
7256
|
+
configDataSource: dataSources?.find(({ name }) => name === related.dataSourceName),
|
|
7257
|
+
fetchedDataSource: getDataSource(related.dataSourceName, fetchedDataSources),
|
|
7258
|
+
});
|
|
7259
|
+
return getLayerInfoAttribute(layerInfo, related.attributeName);
|
|
7260
|
+
}, [dataSources, fetchedDataSources, layerInfos]);
|
|
7261
|
+
const leftAxisAttribute = React.useMemo(() => resolveAxisAttribute(leftAxis), [resolveAxisAttribute, leftAxis]);
|
|
7262
|
+
const rightAxisAttribute = React.useMemo(() => resolveAxisAttribute(rightAxis), [resolveAxisAttribute, rightAxis]);
|
|
7263
|
+
return { axes, strokeColors, leftAxisAttribute, rightAxisAttribute };
|
|
7264
|
+
};
|
|
7265
|
+
|
|
7266
|
+
/**
|
|
7267
|
+
* Форматтеры делений левой и правой шкал линейного графика — по `stringFormat` своего атрибута.
|
|
7268
|
+
*
|
|
7269
|
+
* Отдаются в `tickFormat` генератора оси, а не подменяют текст после отрисовки: d3 измеряет ширину
|
|
7270
|
+
* шкалы по уже нарисованным подписям и по ней сдвигает поле графика. Подмена текста задним числом
|
|
7271
|
+
* (`30` → `30,0`) делала подпись шире замера, и она заезжала на подпись оси слева.
|
|
7272
|
+
*/
|
|
7273
|
+
const useChartAxisTickFormat = ({ dataSources, relatedAttributes }) => {
|
|
7274
|
+
const { t } = useGlobalContext();
|
|
7275
|
+
const { leftAxisAttribute, rightAxisAttribute } = useChartAxisAttributes({ dataSources, relatedAttributes });
|
|
7276
|
+
const createTickFormat = React.useCallback((attribute) => (value) => {
|
|
7277
|
+
if (!value)
|
|
7278
|
+
return "0";
|
|
7279
|
+
return attribute
|
|
7280
|
+
? String(formatAttributeValue({
|
|
7281
|
+
t,
|
|
7282
|
+
type: attribute.type,
|
|
7283
|
+
value: Number(value),
|
|
7284
|
+
stringFormat: attribute.stringFormat,
|
|
7285
|
+
noUnits: true,
|
|
7286
|
+
}))
|
|
7287
|
+
: String(value);
|
|
7288
|
+
}, [t]);
|
|
7289
|
+
const formatLeftTick = React.useMemo(() => createTickFormat(leftAxisAttribute), [createTickFormat, leftAxisAttribute]);
|
|
7290
|
+
const formatRightTick = React.useMemo(() => createTickFormat(rightAxisAttribute), [createTickFormat, rightAxisAttribute]);
|
|
7291
|
+
return { formatLeftTick, formatRightTick };
|
|
7292
|
+
};
|
|
7293
|
+
|
|
7294
|
+
/**
|
|
7295
|
+
* Подписи осей линейного графика и место, которое они занимают над телом графика.
|
|
7296
|
+
*
|
|
7297
|
+
* Режим размещения общий для всех осей (`options.axis.titlePosition`), текст — у каждой оси свой.
|
|
7298
|
+
* Цвет подписи отдельной опцией не задаётся: одна серия на стороне — подпись в её цвет, несколько —
|
|
7299
|
+
* нейтральная, потому что тогда подпись описывает всю ось, а не одну из линий. Ширину вертикальных
|
|
7300
|
+
* подписей здесь не считаем — она зависит от числа строк текста и измеряется по факту.
|
|
7301
|
+
*/
|
|
7302
|
+
const useChartAxisTitles = ({ options, axes, isLineChart }) => {
|
|
7303
|
+
const axisTitlePosition = options?.axis?.titlePosition ?? "side";
|
|
7304
|
+
const axisTitles = React.useMemo(() => {
|
|
7305
|
+
const pick = (side) => {
|
|
7306
|
+
const sideAxes = axes.filter(related => getAxisSide(related) === side);
|
|
7307
|
+
const title = sideAxes.map(resolveAxis).find(axis => axis.title)?.title;
|
|
7308
|
+
if (!title)
|
|
7309
|
+
return undefined;
|
|
7310
|
+
return { title, color: sideAxes.length === 1 ? resolveAxis(sideAxes[0]).color : undefined };
|
|
7311
|
+
};
|
|
7312
|
+
return { left: pick("left"), right: pick("right") };
|
|
7313
|
+
}, [axes]);
|
|
7314
|
+
const hasAxisTitles = !!(isLineChart && (axisTitles.left || axisTitles.right));
|
|
7315
|
+
const hasTopTitles = hasAxisTitles && axisTitlePosition === "top";
|
|
7316
|
+
const hasSideTitles = hasAxisTitles && axisTitlePosition === "side";
|
|
7317
|
+
const hideLeftAxis = React.useMemo(() => axes.filter(related => !isRightAxis(related)).every(related => resolveAxis(related).hide), [axes]);
|
|
7318
|
+
const hideRightAxis = React.useMemo(() => axes.filter(isRightAxis).every(related => resolveAxis(related).hide), [axes]);
|
|
7319
|
+
return {
|
|
7320
|
+
axisTitles,
|
|
7321
|
+
hasTopTitles,
|
|
7322
|
+
hasSideTitles,
|
|
7323
|
+
axisTitlesHeight: hasTopTitles ? AXIS_TITLE_TOP_HEIGHT : 0,
|
|
7324
|
+
hideLeftAxis,
|
|
7325
|
+
hideRightAxis,
|
|
7326
|
+
};
|
|
7327
|
+
};
|
|
7328
|
+
|
|
7056
7329
|
const HEIGHT_OFFSET = 20;
|
|
7057
7330
|
const FILL_OPACITY = 0.28;
|
|
7058
7331
|
const lineGenerator = line()
|
|
@@ -7098,20 +7371,10 @@ function wrap() {
|
|
|
7098
7371
|
}
|
|
7099
7372
|
}
|
|
7100
7373
|
const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes, defaultColor, fontColor, markers, showMarkers, }) => {
|
|
7101
|
-
const {
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
.map(({ axisColor }) => axisColor);
|
|
7106
|
-
const yAxis = React.useMemo(() => relatedAttributes?.find(({ chartAxis }) => chartAxis === "y") ?? relatedAttributes?.[0], [relatedAttributes]);
|
|
7107
|
-
const yAxisAttribute = React.useMemo(() => {
|
|
7108
|
-
const layerInfo = getDataSourceLayerInfo({
|
|
7109
|
-
layerInfos,
|
|
7110
|
-
configDataSource: dataSources?.find(({ name }) => name === yAxis?.dataSourceName),
|
|
7111
|
-
fetchedDataSource: getDataSource(yAxis?.dataSourceName, fetchedDataSources),
|
|
7112
|
-
});
|
|
7113
|
-
return getLayerInfoAttribute(layerInfo, yAxis?.attributeName);
|
|
7114
|
-
}, [dataSources, fetchedDataSources, layerInfos, yAxis]);
|
|
7374
|
+
const { strokeColors } = useChartAxisAttributes({
|
|
7375
|
+
dataSources,
|
|
7376
|
+
relatedAttributes,
|
|
7377
|
+
});
|
|
7115
7378
|
const ref = React.useRef({
|
|
7116
7379
|
path: null,
|
|
7117
7380
|
area: null,
|
|
@@ -7164,22 +7427,9 @@ const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes
|
|
|
7164
7427
|
svg
|
|
7165
7428
|
.selectAll(`.${charts.lineChartClassNames.lineChartXScaleGlobal} .tick text`)
|
|
7166
7429
|
.attr("y", 16);
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
if (!item)
|
|
7171
|
-
return 0;
|
|
7172
|
-
return yAxisAttribute
|
|
7173
|
-
? formatAttributeValue({
|
|
7174
|
-
t,
|
|
7175
|
-
type: yAxisAttribute.type,
|
|
7176
|
-
value: item,
|
|
7177
|
-
stringFormat: yAxisAttribute.stringFormat,
|
|
7178
|
-
noUnits: true,
|
|
7179
|
-
})
|
|
7180
|
-
: item;
|
|
7181
|
-
})
|
|
7182
|
-
.each(wrap);
|
|
7430
|
+
// Текст делений форматируется ещё в генераторе оси (useChartAxisTickFormat), до замера ширины
|
|
7431
|
+
// шкалы; здесь его только укорачиваем.
|
|
7432
|
+
svg.selectAll(`.${charts.lineChartClassNames.lineChartYScaleGlobal} .tick text`).each(wrap);
|
|
7183
7433
|
svg.selectAll(".tick text").attr("style", `color: ${fontColor}`);
|
|
7184
7434
|
const global = svg.select(`.${charts.lineChartClassNames.lineChartLinesGlobal}`);
|
|
7185
7435
|
const lineChartLines = svg.selectAll(`.${charts.lineChartClassNames.lineChartLine}`);
|
|
@@ -7215,7 +7465,6 @@ const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes
|
|
|
7215
7465
|
svg.append("defs").html(() => defs.join(""));
|
|
7216
7466
|
}, [
|
|
7217
7467
|
fontColor,
|
|
7218
|
-
yAxisAttribute,
|
|
7219
7468
|
chartId,
|
|
7220
7469
|
strokeColors,
|
|
7221
7470
|
defaultColor,
|
|
@@ -7223,7 +7472,6 @@ const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes
|
|
|
7223
7472
|
onChange,
|
|
7224
7473
|
showMarkers,
|
|
7225
7474
|
markers,
|
|
7226
|
-
t,
|
|
7227
7475
|
]);
|
|
7228
7476
|
return [customize, onChange];
|
|
7229
7477
|
};
|
|
@@ -7380,10 +7628,10 @@ const useChartData = ({ element, type }) => {
|
|
|
7380
7628
|
const configDataSources = useConfigDataSources(type);
|
|
7381
7629
|
const relatedAttributes = React.useMemo(() => element?.options?.relatedDataSources || [], [element?.options?.relatedDataSources]);
|
|
7382
7630
|
const loading = React.useMemo(() => !!relatedAttributes?.length &&
|
|
7383
|
-
!dataSources?.some(({ name }) => relatedAttributes.some(
|
|
7631
|
+
!dataSources?.some(({ name }) => relatedAttributes.some(related => isValueAxis(related) && related.dataSourceName === name)), [dataSources, relatedAttributes]);
|
|
7384
7632
|
const fetchedData = React.useMemo(() => {
|
|
7385
7633
|
const isRelated = !!relatedAttributes?.length;
|
|
7386
|
-
const filteredAttributes = relatedAttributes.filter(
|
|
7634
|
+
const filteredAttributes = relatedAttributes.filter(isValueAxis);
|
|
7387
7635
|
if (!element)
|
|
7388
7636
|
return [];
|
|
7389
7637
|
if (isRelated) {
|
|
@@ -7391,7 +7639,7 @@ const useChartData = ({ element, type }) => {
|
|
|
7391
7639
|
return [];
|
|
7392
7640
|
return filteredAttributes
|
|
7393
7641
|
.map(relatedAxis => {
|
|
7394
|
-
const { dataSourceName,
|
|
7642
|
+
const { dataSourceName, attributeName, attributeUnits } = relatedAxis;
|
|
7395
7643
|
const dataSource = configDataSources.find(({ name }) => name === dataSourceName);
|
|
7396
7644
|
const featureDataSource = getDataSource(dataSourceName, dataSources);
|
|
7397
7645
|
const layerInfo = getDataSourceLayerInfo({
|
|
@@ -7411,7 +7659,10 @@ const useChartData = ({ element, type }) => {
|
|
|
7411
7659
|
layerInfo,
|
|
7412
7660
|
})
|
|
7413
7661
|
: null,
|
|
7414
|
-
color:
|
|
7662
|
+
color: resolveAxis(relatedAxis).color,
|
|
7663
|
+
// Сторона едет вместе с данными: серии без значений отсеиваются ниже, и сопоставлять
|
|
7664
|
+
// их с осями по индексу уже нельзя.
|
|
7665
|
+
axisSide: getAxisSide(relatedAxis),
|
|
7415
7666
|
attributeName,
|
|
7416
7667
|
attributeUnits,
|
|
7417
7668
|
dataSourceName,
|
|
@@ -7424,6 +7675,7 @@ const useChartData = ({ element, type }) => {
|
|
|
7424
7675
|
return [
|
|
7425
7676
|
{
|
|
7426
7677
|
items: getDataFromAttributes(t, element, attributes),
|
|
7678
|
+
axisSide: "left",
|
|
7427
7679
|
layerInfo: null,
|
|
7428
7680
|
attributeName: null,
|
|
7429
7681
|
attributeUnits: null,
|
|
@@ -7443,7 +7695,7 @@ const useChartData = ({ element, type }) => {
|
|
|
7443
7695
|
]);
|
|
7444
7696
|
const isResolved = React.useMemo(() => !relatedAttributes?.length ||
|
|
7445
7697
|
relatedAttributes
|
|
7446
|
-
.filter(
|
|
7698
|
+
.filter(isValueAxis)
|
|
7447
7699
|
.every(({ dataSourceName }) => Array.isArray(getDataSource(dataSourceName, dataSources)?.features)), [relatedAttributes, dataSources]);
|
|
7448
7700
|
const [data, setData] = React.useState(fetchedData);
|
|
7449
7701
|
React.useEffect(() => {
|
|
@@ -8975,6 +9227,15 @@ const ChartLegendContainer = styled(uilibGl.Flex).withConfig({ displayName: "Cha
|
|
|
8975
9227
|
flex-direction: column;
|
|
8976
9228
|
flex-wrap: wrap;
|
|
8977
9229
|
justify-content: ${({ twoColumns }) => (twoColumns ? "flex-start" : "center")};
|
|
9230
|
+
|
|
9231
|
+
/* Явная опция column перебивает раскладку, которую легенде навязывает ChartContainer
|
|
9232
|
+
(под графиком — в ряд, сбоку — столбиком). Без опции поведение прежнее. */
|
|
9233
|
+
${({ $column }) => $column !== undefined &&
|
|
9234
|
+
styled.css `
|
|
9235
|
+
&&& {
|
|
9236
|
+
flex-direction: ${$column ? "column" : "row"};
|
|
9237
|
+
}
|
|
9238
|
+
`}
|
|
8978
9239
|
`;
|
|
8979
9240
|
const ChartLegendItem = styled(uilibGl.Flex).withConfig({ displayName: "ChartLegendItem", componentId: "sc-16m9piw" }) `
|
|
8980
9241
|
cursor: ${({ onClick }) => (onClick ? "pointer" : "default")};
|
|
@@ -9043,12 +9304,129 @@ const ContainerChart = styled(uilibGl.Flex).withConfig({ displayName: "Container
|
|
|
9043
9304
|
`}
|
|
9044
9305
|
`;
|
|
9045
9306
|
const ContainerLegend = styled(uilibGl.Flex).withConfig({ displayName: "ContainerLegend", componentId: "sc-z1n1s8" }) ``;
|
|
9046
|
-
|
|
9307
|
+
/**
|
|
9308
|
+
* Выравнивание строки по полю графика: ширина тела графика, а внутренние отступы — до краёв поля
|
|
9309
|
+
* (колонка подписи оси и значения шкалы). Тело графика центрируется в слоте, поэтому и строка
|
|
9310
|
+
* центрируется. Правило поднято `&&&` над шириной, которую строкам задаёт `ContainerValue`.
|
|
9311
|
+
*/
|
|
9312
|
+
const plotInsetsMixin = styled.css `
|
|
9313
|
+
${({ $insets }) => $insets &&
|
|
9314
|
+
styled.css `
|
|
9315
|
+
&&& {
|
|
9316
|
+
box-sizing: border-box;
|
|
9317
|
+
align-self: center;
|
|
9318
|
+
width: ${$insets.width}px;
|
|
9319
|
+
max-width: 100%;
|
|
9320
|
+
padding-left: ${$insets.left}px;
|
|
9321
|
+
padding-right: ${$insets.right}px;
|
|
9322
|
+
}
|
|
9323
|
+
`}
|
|
9324
|
+
`;
|
|
9325
|
+
/**
|
|
9326
|
+
* Строка ждёт, пока график сообщит положение поля: место занимает, но не видна. Иначе до первой
|
|
9327
|
+
* отрисовки d3 она стояла бы по краю контейнера и потом прыгала на место.
|
|
9328
|
+
*/
|
|
9329
|
+
const awaitingPlotMixin = styled.css `
|
|
9330
|
+
${({ $awaitingPlot }) => $awaitingPlot &&
|
|
9331
|
+
styled.css `
|
|
9332
|
+
visibility: hidden;
|
|
9333
|
+
`}
|
|
9334
|
+
`;
|
|
9335
|
+
/**
|
|
9336
|
+
* Строка под телом графика: подпись оси X и, при `legendInline`, легенда справа от неё.
|
|
9337
|
+
*
|
|
9338
|
+
* `$offsetTop` подтягивает строку к подписям оси X: под ними внутри SVG остаётся пустая полоса,
|
|
9339
|
+
* и без поправки зазор получился бы втрое больше макетного.
|
|
9340
|
+
*/
|
|
9341
|
+
const ChartFooter = styled(uilibGl.Flex).withConfig({ displayName: "ChartFooter", componentId: "sc-nii28w" }) `
|
|
9342
|
+
align-items: center;
|
|
9343
|
+
flex-wrap: nowrap;
|
|
9344
|
+
gap: 0.5rem;
|
|
9345
|
+
width: 100%;
|
|
9346
|
+
margin-top: ${({ $offsetTop }) => $offsetTop ?? 0}px;
|
|
9347
|
+
justify-content: ${({ $align }) => $align};
|
|
9348
|
+
|
|
9349
|
+
${ContainerLegend} {
|
|
9350
|
+
justify-content: flex-end;
|
|
9351
|
+
margin-left: auto;
|
|
9352
|
+
}
|
|
9353
|
+
|
|
9354
|
+
/* Легенда в строке с подписью: записи прижаты вправо, без отступа сверху, который ей задаёт
|
|
9355
|
+
ContainerValue, и без нижнего отступа записей — иначе она съедет вниз относительно подписи.
|
|
9356
|
+
Прижимать нужно сами записи: обёртки легенды — Flex из uilib-gl с width: 100%, и выравнивание
|
|
9357
|
+
внешнего контейнера до записей не доходит, а своё у легенды — по центру. */
|
|
9358
|
+
&&& ${ChartLegendContainer} {
|
|
9359
|
+
justify-content: flex-end;
|
|
9360
|
+
margin-top: 0;
|
|
9361
|
+
}
|
|
9362
|
+
|
|
9363
|
+
&&& ${ChartLegendItem} {
|
|
9364
|
+
margin-bottom: 0;
|
|
9365
|
+
}
|
|
9366
|
+
|
|
9367
|
+
${plotInsetsMixin};
|
|
9368
|
+
${awaitingPlotMixin};
|
|
9369
|
+
`;
|
|
9370
|
+
const ChartXAxisTitle = styled.div.withConfig({ displayName: "ChartXAxisTitle", componentId: "sc-1vsgzi9" }) `
|
|
9371
|
+
font-size: 0.625rem;
|
|
9372
|
+
line-height: 0.75rem;
|
|
9373
|
+
white-space: nowrap;
|
|
9374
|
+
color: ${({ $color, theme: { palette } }) => $color || palette.textDisabled};
|
|
9375
|
+
`;
|
|
9376
|
+
/**
|
|
9377
|
+
* Строка легенды под графиком. `$gapTop` заменяет отступ сверху, который легенде задаёт `ContainerValue`;
|
|
9378
|
+
* `$offsetTop` сдвигает саму строку — подтягивает её к подписям оси X, как строку с подписью оси.
|
|
9379
|
+
*
|
|
9380
|
+
* `$justify` выравнивает сами записи. Выравнивание строки (`justifyContent`) до них не доходит: обёртки
|
|
9381
|
+
* легенды — Flex из uilib-gl с `width: 100%`, а записи внутри легенда всегда центрирует.
|
|
9382
|
+
*/
|
|
9383
|
+
const ChartLegendRow = styled(ContainerLegend).withConfig({ displayName: "ChartLegendRow", componentId: "sc-1a12wrz" }) `
|
|
9384
|
+
${({ $offsetTop }) => $offsetTop !== undefined &&
|
|
9385
|
+
styled.css `
|
|
9386
|
+
margin-top: ${$offsetTop}px;
|
|
9387
|
+
`}
|
|
9388
|
+
|
|
9389
|
+
${({ $gapTop }) => $gapTop !== undefined &&
|
|
9390
|
+
styled.css `
|
|
9391
|
+
&&& ${ChartLegendContainer} {
|
|
9392
|
+
margin-top: ${$gapTop}px;
|
|
9393
|
+
}
|
|
9394
|
+
`}
|
|
9395
|
+
|
|
9396
|
+
${({ $justify }) => $justify &&
|
|
9397
|
+
styled.css `
|
|
9398
|
+
&&& ${ChartLegendContainer} {
|
|
9399
|
+
justify-content: ${$justify};
|
|
9400
|
+
}
|
|
9401
|
+
`}
|
|
9402
|
+
|
|
9403
|
+
${plotInsetsMixin};
|
|
9404
|
+
${awaitingPlotMixin};
|
|
9405
|
+
`;
|
|
9406
|
+
/**
|
|
9407
|
+
* Колонка «график + подпись оси X» для легенды сбоку (`twoColumns`). Без неё подпись встала бы третьим
|
|
9408
|
+
* элементом ряда рядом с легендой, а не под графиком.
|
|
9409
|
+
*/
|
|
9410
|
+
const ChartWithFooter = styled(uilibGl.Flex).withConfig({ displayName: "ChartWithFooter", componentId: "sc-140gv2s" }) `
|
|
9411
|
+
flex-direction: column;
|
|
9412
|
+
flex-wrap: nowrap;
|
|
9413
|
+
min-width: 0;
|
|
9414
|
+
|
|
9415
|
+
${({ $fill }) => $fill &&
|
|
9416
|
+
styled.css `
|
|
9417
|
+
min-height: 0;
|
|
9418
|
+
`}
|
|
9419
|
+
|
|
9420
|
+
&&& ${ContainerChart} {
|
|
9421
|
+
width: 100%;
|
|
9422
|
+
}
|
|
9423
|
+
`;
|
|
9424
|
+
const ContainerUnits = styled.div.withConfig({ displayName: "ContainerUnits", componentId: "sc-vj15z6" }) `
|
|
9047
9425
|
margin-left: 0.5rem;
|
|
9048
9426
|
white-space: nowrap;
|
|
9049
9427
|
font-size: 0.75rem;
|
|
9050
9428
|
`;
|
|
9051
|
-
const ContainerValue = styled(uilibGl.Flex).withConfig({ displayName: "ContainerValue", componentId: "sc-
|
|
9429
|
+
const ContainerValue = styled(uilibGl.Flex).withConfig({ displayName: "ContainerValue", componentId: "sc-1bnfj5v" }) `
|
|
9052
9430
|
justify-content: flex-end;
|
|
9053
9431
|
align-items: center;
|
|
9054
9432
|
flex-wrap: nowrap;
|
|
@@ -9067,7 +9445,7 @@ const ContainerValue = styled(uilibGl.Flex).withConfig({ displayName: "Container
|
|
|
9067
9445
|
width: ${({ column }) => (column ? "100%" : "auto")};
|
|
9068
9446
|
}
|
|
9069
9447
|
|
|
9070
|
-
${ContainerChart}, ${ContainerLegend} {
|
|
9448
|
+
${ContainerChart}, ${ContainerLegend}, ${ChartWithFooter} {
|
|
9071
9449
|
width: ${({ column }) => (column ? "100%" : "50%")};
|
|
9072
9450
|
}
|
|
9073
9451
|
|
|
@@ -9090,7 +9468,7 @@ const SizeIconMixin = styled.css `
|
|
|
9090
9468
|
font-size: ${({ $fontSize }) => $fontSize}px !important;
|
|
9091
9469
|
}
|
|
9092
9470
|
`;
|
|
9093
|
-
styled(uilibGl.Icon).withConfig({ displayName: "ContainerIcon", componentId: "sc-
|
|
9471
|
+
styled(uilibGl.Icon).withConfig({ displayName: "ContainerIcon", componentId: "sc-18399bz" }) `
|
|
9094
9472
|
width: auto;
|
|
9095
9473
|
height: auto;
|
|
9096
9474
|
margin-bottom: 0.5rem;
|
|
@@ -9104,7 +9482,7 @@ const SvgContainerColorMixin$1 = styled.css `
|
|
|
9104
9482
|
fill: ${({ $fontColor }) => $fontColor};
|
|
9105
9483
|
}
|
|
9106
9484
|
`;
|
|
9107
|
-
const SvgContainer$1 = styled.div.withConfig({ displayName: "SvgContainer", componentId: "sc-
|
|
9485
|
+
const SvgContainer$1 = styled.div.withConfig({ displayName: "SvgContainer", componentId: "sc-8l0ajh" }) `
|
|
9108
9486
|
&&& {
|
|
9109
9487
|
min-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
|
|
9110
9488
|
max-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
|
|
@@ -9116,7 +9494,7 @@ const SvgContainer$1 = styled.div.withConfig({ displayName: "SvgContainer", comp
|
|
|
9116
9494
|
}
|
|
9117
9495
|
}
|
|
9118
9496
|
`;
|
|
9119
|
-
const TwoColumnContainerWrapper = styled(Container).withConfig({ displayName: "TwoColumnContainerWrapper", componentId: "sc-
|
|
9497
|
+
const TwoColumnContainerWrapper = styled(Container).withConfig({ displayName: "TwoColumnContainerWrapper", componentId: "sc-1yxeknf" }) `
|
|
9120
9498
|
flex-direction: row;
|
|
9121
9499
|
flex-wrap: nowrap;
|
|
9122
9500
|
align-items: center;
|
|
@@ -9143,6 +9521,16 @@ const CameraContainer = React.memo(({ elementConfig, type, renderElement }) => {
|
|
|
9143
9521
|
return (jsxRuntime.jsxs(ContainerRoot, { ...root, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxRuntime.jsxs(Container, { ...body, children: [jsxRuntime.jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsxRuntime.jsx(ContainerValue, { children: renderElement({ id: "value", wrap: false }) })] }))] }));
|
|
9144
9522
|
});
|
|
9145
9523
|
|
|
9524
|
+
/**
|
|
9525
|
+
* Контекст, через который элемент `chart` сообщает контейнеру, где лежит поле графика.
|
|
9526
|
+
*
|
|
9527
|
+
* По макету подпись оси X и легенда выравниваются по полю графика, а не по краям контейнера.
|
|
9528
|
+
* Ширину шкал d3 вычисляет только при отрисовке, а подвал с подписью и легендой рисует
|
|
9529
|
+
* `ChartContainer` — снаружи элемента. Без контейнера (элемент сам по себе) провайдера нет,
|
|
9530
|
+
* и отчёт просто не отправляется.
|
|
9531
|
+
*/
|
|
9532
|
+
const ChartPlotContext = React.createContext({});
|
|
9533
|
+
|
|
9146
9534
|
/**
|
|
9147
9535
|
* Контекст вписывания графика (`options.fill` контейнера Chart).
|
|
9148
9536
|
*
|
|
@@ -9152,14 +9540,43 @@ const CameraContainer = React.memo(({ elementConfig, type, renderElement }) => {
|
|
|
9152
9540
|
*/
|
|
9153
9541
|
const FillContext = React.createContext({ fill: false, fitHeight: false });
|
|
9154
9542
|
|
|
9543
|
+
const isSameInsets = (prev, next) => !!prev && prev.left === next.left && prev.right === next.right && prev.width === next.width;
|
|
9155
9544
|
const ChartContainer = React.memo(({ elementConfig, isVisible, type, renderElement }) => {
|
|
9156
9545
|
const { options, children } = elementConfig || {};
|
|
9157
|
-
const { twoColumns, hideEmpty, height } = options || {};
|
|
9546
|
+
const { twoColumns, hideEmpty, height, legendInline } = options || {};
|
|
9158
9547
|
const fill = options?.fill;
|
|
9159
9548
|
const fillContextValue = React.useMemo(() => ({ fill: !!fill, fitHeight: !!fill && height != null }), [fill, height]);
|
|
9549
|
+
// Поле графика отчитывается после каждой отрисовки d3; одинаковые значения не пишем в стейт,
|
|
9550
|
+
// иначе отчёт и ре-рендер гоняли бы друг друга.
|
|
9551
|
+
const [plotInsets, setPlotInsets] = React.useState(null);
|
|
9552
|
+
const reportPlotInsets = React.useCallback((next) => setPlotInsets(prev => (isSameInsets(prev, next) ? prev : next)), []);
|
|
9553
|
+
const plotContextValue = React.useMemo(() => ({ reportPlotInsets }), [reportPlotInsets]);
|
|
9160
9554
|
const aliasElement = children.find(child => child.id === "alias");
|
|
9161
9555
|
const chartElement = children.find(child => child.id === "chart");
|
|
9162
9556
|
const legendElement = children.find(child => child.id === "legend");
|
|
9557
|
+
const isLineChart = chartElement?.options?.chartType === "line";
|
|
9558
|
+
// Подпись оси X — часть макета осей линейного графика, как и подписи осей Y в `Chart`. У других типов
|
|
9559
|
+
// её нет, даже если она осталась в конфиге после смены типа: редактор показывает настройки осей только
|
|
9560
|
+
// линейному графику, и убрать там такую подпись было бы нечем.
|
|
9561
|
+
const xAxisTitle = React.useMemo(() => (isLineChart ? chartElement?.options?.axis?.x?.title : undefined), [isLineChart, chartElement?.options?.axis?.x?.title]);
|
|
9562
|
+
const footerJustify = React.useMemo(() => CHART_AXIS_ALIGN_JUSTIFY[chartElement?.options?.axis?.x?.align ?? "center"], [chartElement?.options?.axis?.x?.align]);
|
|
9563
|
+
// Легенда встаёт в строку с подписью оси X только когда та есть и легенда не вынесена вбок.
|
|
9564
|
+
const isLegendInline = React.useMemo(() => !!legendInline && !twoColumns && !!xAxisTitle, [legendInline, twoColumns, xAxisTitle]);
|
|
9565
|
+
// По макету подпись оси X и легенда у линейного графика выравниваются по полю графика, а не по
|
|
9566
|
+
// контейнеру. Легенда сбоку (twoColumns) живёт в своей колонке и в выравнивании не участвует.
|
|
9567
|
+
const footerInsets = React.useMemo(() => (isLineChart ? plotInsets : null), [isLineChart, plotInsets]);
|
|
9568
|
+
const isPlotLegend = isLineChart && !twoColumns;
|
|
9569
|
+
// Положение поля становится известно только после отрисовки: LineChart рисует d3 в useEffect, уже
|
|
9570
|
+
// после первого кадра. До отчёта подвал встал бы по краю контейнера и затем прыгнул на место, поэтому
|
|
9571
|
+
// прячем его — место под ним сохраняется, а появляется он вместе с линиями графика.
|
|
9572
|
+
const isAwaitingPlot = isLineChart && !plotInsets;
|
|
9573
|
+
// Пока подпись оси X не задана, легенда выравнивается прежней опцией `center` (по умолчанию — к левой
|
|
9574
|
+
// границе поля графика); с подписью — по ней, иначе подпись и легенда разъезжаются по разным краям.
|
|
9575
|
+
const legendJustify = React.useMemo(() => {
|
|
9576
|
+
if (xAxisTitle)
|
|
9577
|
+
return footerJustify;
|
|
9578
|
+
return legendElement?.options?.center ? "center" : "flex-start";
|
|
9579
|
+
}, [xAxisTitle, footerJustify, legendElement?.options?.center]);
|
|
9163
9580
|
const { root, body } = useContainerRoot({ elementConfig });
|
|
9164
9581
|
const { data, loading } = useChartData({
|
|
9165
9582
|
element: chartElement,
|
|
@@ -9171,7 +9588,12 @@ const ChartContainer = React.memo(({ elementConfig, isVisible, type, renderEleme
|
|
|
9171
9588
|
const hasItems = !!data[0]?.items?.length;
|
|
9172
9589
|
if (!loading && !hasItems && hideEmpty)
|
|
9173
9590
|
return null;
|
|
9174
|
-
|
|
9591
|
+
const chartSlot = (jsxRuntime.jsx(ContainerChart, { "$fill": !!fill, children: jsxRuntime.jsx(FillContext.Provider, { value: fillContextValue, children: jsxRuntime.jsx(ChartPlotContext.Provider, { value: plotContextValue, children: renderElement({ id: "chart" }) }) }) }));
|
|
9592
|
+
const footer = xAxisTitle ? (jsxRuntime.jsxs(ChartFooter, { "$align": footerJustify, "$insets": footerInsets, "$offsetTop": X_AXIS_TITLE_GAP - LINE_CHART_X_LABELS_BLANK, "$awaitingPlot": isAwaitingPlot, children: [jsxRuntime.jsx(ChartXAxisTitle, { children: xAxisTitle }), isLegendInline && jsxRuntime.jsx(ContainerLegend, { children: renderElement({ id: "legend" }) })] })) : null;
|
|
9593
|
+
// Легенда линейного графика: под подписью оси X — через X_AXIS_TITLE_LEGEND_GAP, без подписи — на её
|
|
9594
|
+
// месте, вплотную под подписями делений оси X. У остальных графиков — прежняя раскладка общей легенды.
|
|
9595
|
+
const legendRow = isLegendInline ? null : (jsxRuntime.jsx(ChartLegendRow, { justifyContent: legendJustify, "$insets": isPlotLegend ? footerInsets : null, "$gapTop": isPlotLegend ? (xAxisTitle ? X_AXIS_TITLE_LEGEND_GAP : 0) : undefined, "$offsetTop": isPlotLegend && !xAxisTitle ? X_AXIS_TITLE_GAP - LINE_CHART_X_LABELS_BLANK : undefined, "$justify": isPlotLegend ? legendJustify : undefined, "$awaitingPlot": isPlotLegend && isAwaitingPlot, children: renderElement({ id: "legend" }) }));
|
|
9596
|
+
return (jsxRuntime.jsxs(ContainerRoot, { ...root, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxRuntime.jsxs(Container, { ...body, isColumn: true, children: [aliasElement && jsxRuntime.jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsxRuntime.jsx(ContainerValue, { column: !twoColumns, alignItems: twoColumns && fill ? "stretch" : "center", "$fill": !!fill, children: hasItems ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [twoColumns && footer ? (jsxRuntime.jsxs(ChartWithFooter, { "$fill": !!fill, children: [chartSlot, footer] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [chartSlot, footer] })), legendRow] })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: "\u2014" })) })] }))] }));
|
|
9175
9597
|
});
|
|
9176
9598
|
|
|
9177
9599
|
const InnerContainerWrapper = styled.div.withConfig({ displayName: "InnerContainerWrapper", componentId: "sc-1a1fl7q" }) `
|
|
@@ -9529,6 +9951,7 @@ const DEFAULT_BAR_CHART_HEIGHT = 100;
|
|
|
9529
9951
|
const BAR_CHART_HEADER_HEIGHT = 40;
|
|
9530
9952
|
const BAR_CHART_FOOTER_MARGIN = 16;
|
|
9531
9953
|
const LINE_CHART_LABEL_MARGIN = 20;
|
|
9954
|
+
const LINE_CHART_X_AXIS_PADDING = 15;
|
|
9532
9955
|
const DEFAULT_BAR_CHART_MARGINS = {
|
|
9533
9956
|
top: 0,
|
|
9534
9957
|
left: 0,
|
|
@@ -10927,6 +11350,13 @@ const StyledSvg = styled(uilibGl.Flex).withConfig({ displayName: "StyledSvg", co
|
|
|
10927
11350
|
${({ $fontColor }) => !!$fontColor && StyledSvgColorMixin};
|
|
10928
11351
|
`;
|
|
10929
11352
|
|
|
11353
|
+
const AttachmentIconImg = styled.img.withConfig({ displayName: "AttachmentIconImg", componentId: "sc-1qtgzdf" }) `
|
|
11354
|
+
flex-shrink: 0;
|
|
11355
|
+
width: ${({ $size }) => $size}px;
|
|
11356
|
+
height: ${({ $size }) => $size}px;
|
|
11357
|
+
object-fit: contain;
|
|
11358
|
+
`;
|
|
11359
|
+
|
|
10930
11360
|
const ContainerIconValue = styled(uilibGl.Flex).withConfig({ displayName: "ContainerIconValue", componentId: "sc-1adlcb4" }) ``;
|
|
10931
11361
|
/**
|
|
10932
11362
|
* Обёртка иконки плитки. Собственный styled нужен, чтобы до иконки можно было дотянуться стилями:
|
|
@@ -11031,6 +11461,12 @@ const RoundedBackgroundContainerWrapper = styled(uilibGl.Flex).withConfig({ disp
|
|
|
11031
11461
|
width: ${BIG_ICON_SIZE} !important;
|
|
11032
11462
|
height: ${BIG_ICON_SIZE} !important;
|
|
11033
11463
|
}
|
|
11464
|
+
|
|
11465
|
+
/* Растровая иконка из атрибута-вложения: её размер тоже задаёт контейнер. */
|
|
11466
|
+
${AttachmentIconImg} {
|
|
11467
|
+
width: ${BIG_ICON_SIZE};
|
|
11468
|
+
height: ${BIG_ICON_SIZE};
|
|
11469
|
+
}
|
|
11034
11470
|
}
|
|
11035
11471
|
`};
|
|
11036
11472
|
|
|
@@ -15128,14 +15564,55 @@ const StyledIcon = styled(uilibGl.Icon).withConfig({ displayName: "StyledIcon",
|
|
|
15128
15564
|
${({ fontColor }) => !!fontColor && StyledIconFontColorMixin};
|
|
15129
15565
|
`;
|
|
15130
15566
|
|
|
15567
|
+
/** Размер иконки-вложения, px, когда в конфиге не задан `options.fontSize`. */
|
|
15568
|
+
const ATTACHMENT_ICON_DEFAULT_SIZE = 16;
|
|
15569
|
+
|
|
15570
|
+
/**
|
|
15571
|
+
* Иконка из значения атрибута-вложения: первое изображение списка, остальные файлы пропускаются.
|
|
15572
|
+
* Значение приходит JSON-строкой с сервера или массивом после правки — `parseAttachments` понимает оба.
|
|
15573
|
+
* SVG помечается отдельно: его рисуют инлайном, чтобы перекрасить цветом иконки.
|
|
15574
|
+
*/
|
|
15575
|
+
const getAttachmentIcon = (value) => {
|
|
15576
|
+
const image = parseAttachments(value).find(({ mimeType, name }) => IMAGE_FILE_TYPES.includes(getFileType(mimeType, name)));
|
|
15577
|
+
if (!image)
|
|
15578
|
+
return null;
|
|
15579
|
+
return {
|
|
15580
|
+
url: getResourceUrl(image.link),
|
|
15581
|
+
name: image.name,
|
|
15582
|
+
isSvg: image.mimeType === SVG_MIME_TYPE,
|
|
15583
|
+
};
|
|
15584
|
+
};
|
|
15585
|
+
|
|
15586
|
+
/**
|
|
15587
|
+
* Иконка из атрибута-вложения. SVG рисуется инлайном и перекрашивается `fontColor`, как у ElementSvg;
|
|
15588
|
+
* растровый файл — картинкой как есть. Изображений среди вложений нет — не рендерится ничего.
|
|
15589
|
+
*/
|
|
15590
|
+
const AttachmentIcon = React.memo(({ value, fontSize, fontColor, style }) => {
|
|
15591
|
+
const icon = React.useMemo(() => getAttachmentIcon(value), [value]);
|
|
15592
|
+
const size = React.useMemo(() => Number(fontSize) || ATTACHMENT_ICON_DEFAULT_SIZE, [fontSize]);
|
|
15593
|
+
const rasterUrl = React.useMemo(() => (icon && !icon.isSvg ? icon.url : null), [icon]);
|
|
15594
|
+
const imageSrc = useFetchImageWithAuth(rasterUrl);
|
|
15595
|
+
if (!icon)
|
|
15596
|
+
return null;
|
|
15597
|
+
if (icon.isSvg) {
|
|
15598
|
+
return jsxRuntime.jsx(SvgImage, { url: icon.url, width: size, height: size, fontColor: fontColor });
|
|
15599
|
+
}
|
|
15600
|
+
if (!imageSrc)
|
|
15601
|
+
return null;
|
|
15602
|
+
return jsxRuntime.jsx(AttachmentIconImg, { src: imageSrc, alt: icon.name, style: style, "$size": size });
|
|
15603
|
+
});
|
|
15604
|
+
|
|
15131
15605
|
const ElementIcon = React.memo(({ type, elementConfig, ...rest }) => {
|
|
15132
15606
|
const ctx = useWidgetContext(type);
|
|
15133
15607
|
const attributes = rest.attributes || ctx.attributes;
|
|
15134
15608
|
const { value, attributeName, options, style } = elementConfig || {};
|
|
15135
15609
|
const { fontSize, fontColor } = options || {};
|
|
15136
|
-
const
|
|
15137
|
-
|
|
15138
|
-
|
|
15610
|
+
const attribute = React.useMemo(() => (attributeName ? getAttributeByName(attributeName, attributes) : undefined), [attributeName, attributes]);
|
|
15611
|
+
const iconValue = React.useMemo(() => (attributeName ? attribute?.value : value), [attributeName, attribute, value]);
|
|
15612
|
+
// Во вложениях лежит список файлов, а не имя глифа: иконкой становится первое изображение.
|
|
15613
|
+
if (attribute?.subType === api.StringSubType.Attachments) {
|
|
15614
|
+
return jsxRuntime.jsx(AttachmentIcon, { value: attribute.value, fontSize: fontSize, fontColor: fontColor, style: style });
|
|
15615
|
+
}
|
|
15139
15616
|
return (jsxRuntime.jsx(StyledIcon, { kind: iconValue, fontSize: fontSize, fontColor: fontColor, style: style }));
|
|
15140
15617
|
});
|
|
15141
15618
|
|
|
@@ -15192,9 +15669,9 @@ const ElementImage = React.memo(({ type, elementConfig }) => {
|
|
|
15192
15669
|
});
|
|
15193
15670
|
|
|
15194
15671
|
const ElementLegend = React.memo(({ type, element, elementConfig }) => {
|
|
15195
|
-
const { attributes,
|
|
15672
|
+
const { attributes, expandedContainers } = useWidgetContext(type);
|
|
15196
15673
|
const { options } = elementConfig || {};
|
|
15197
|
-
const { twoColumns, chartId, relatedDataSources, fontSize } = options || {};
|
|
15674
|
+
const { twoColumns, column, chartId, relatedDataSources, fontSize } = options || {};
|
|
15198
15675
|
const chartElement = React.useMemo(() => findAnd.returnFound(element?.children, { id: chartId }), [chartId, element?.children]);
|
|
15199
15676
|
const { data, loading } = useChartData({
|
|
15200
15677
|
element: chartElement,
|
|
@@ -15205,10 +15682,10 @@ const ElementLegend = React.memo(({ type, element, elementConfig }) => {
|
|
|
15205
15682
|
if (!data[0]?.items?.length)
|
|
15206
15683
|
return [];
|
|
15207
15684
|
if (chartElement?.options?.chartType === "line") {
|
|
15208
|
-
return axes.map(
|
|
15685
|
+
return axes.map(related => {
|
|
15209
15686
|
return {
|
|
15210
|
-
name: alias,
|
|
15211
|
-
color:
|
|
15687
|
+
name: related.alias,
|
|
15688
|
+
color: resolveAxis(related).color,
|
|
15212
15689
|
};
|
|
15213
15690
|
});
|
|
15214
15691
|
}
|
|
@@ -15222,7 +15699,7 @@ const ElementLegend = React.memo(({ type, element, elementConfig }) => {
|
|
|
15222
15699
|
});
|
|
15223
15700
|
}, [data, chartElement?.options?.chartType, relatedDataSources, axes, attributes]);
|
|
15224
15701
|
return !chartElement?.options?.expanded ||
|
|
15225
|
-
expandedContainers?.[chartElement.id] ? (jsxRuntime.jsx(ChartLegend, { data: legendData, loading: loading, chartElement: chartElement, twoColumns: twoColumns, fontSize: fontSize, type: type })) : null;
|
|
15702
|
+
expandedContainers?.[chartElement.id] ? (jsxRuntime.jsx(ChartLegend, { data: legendData, loading: loading, chartElement: chartElement, twoColumns: twoColumns, column: column, fontSize: fontSize, type: type })) : null;
|
|
15226
15703
|
});
|
|
15227
15704
|
|
|
15228
15705
|
const ExternalLink = styled(uilibGl.IconButton).attrs(() => ({
|
|
@@ -16790,7 +17267,11 @@ const getDataFromRelatedFeatures = ({ t, config, filters, relatedConfig, dataSou
|
|
|
16790
17267
|
if (isOtherSliced) {
|
|
16791
17268
|
data = data.slice(0, config?.options?.otherItems);
|
|
16792
17269
|
}
|
|
16793
|
-
|
|
17270
|
+
// Источник чарта отличается от источника фильтра наличием осей: BarChartFilter собирает
|
|
17271
|
+
// relatedConfig вручную и оси не задаёт, потому что ему нужна исходная палитра, а не растянутая
|
|
17272
|
+
// под число элементов. Гейт смотрит и на устаревший chartAxis — иначе у старых конфигов
|
|
17273
|
+
// молча поехали бы цвета столбцов и долей.
|
|
17274
|
+
const gradientArray = hasAxisConfig(relatedConfig)
|
|
16794
17275
|
? stretchPalette(colors, data.length)
|
|
16795
17276
|
: colors;
|
|
16796
17277
|
const filter = getConfigFilter(relatedConfig?.filterName, filters);
|
|
@@ -18856,6 +19337,15 @@ const StackBar = ({ data, filterName, type, alias, options, fill, renderElement,
|
|
|
18856
19337
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [(alias || showTotal) && (jsxRuntime.jsxs(StackBarHeader, { children: [jsxRuntime.jsx(StackBarAlias, { children: renderElement({ id: "alias" }) }), showTotal && (jsxRuntime.jsxs(StackBarTotal, { children: [jsxRuntime.jsx(StackBarValue, { children: formatValue(total) }), !!units && jsxRuntime.jsx(StackBarUnits, { children: units })] }))] })), groupTooltip ? (jsxRuntime.jsx(uilibGl.Tooltip, { placement: "top", arrow: true, content: renderGroupTooltip, children: ref => (jsxRuntime.jsx(StackBarContainer, { ref: ref, "$fill": fill, children: renderItems })) })) : (jsxRuntime.jsx(StackBarContainer, { "$fill": fill, children: renderItems }))] }));
|
|
18857
19338
|
};
|
|
18858
19339
|
|
|
19340
|
+
/** Горизонтальные подписи осей над телом графика: левая прижата влево, правая — вправо. */
|
|
19341
|
+
const AxisTitlesTop = ({ left, right, neutralColor }) => (jsxRuntime.jsxs(AxisTitlesRow, { children: [jsxRuntime.jsx(AxisTitleTop, { "$color": left?.color || neutralColor, title: left?.title, children: left?.title }), jsxRuntime.jsx(AxisTitleTop, { "$color": right?.color || neutralColor, title: right?.title, children: right?.title })] }));
|
|
19342
|
+
/** Вертикальная подпись оси сбоку от тела графика. Без подписи ничего не рендерит. */
|
|
19343
|
+
const AxisTitleSideSlot = ({ side, axisTitle, neutralColor, plotTop, plotBottom, }) => {
|
|
19344
|
+
if (!axisTitle)
|
|
19345
|
+
return null;
|
|
19346
|
+
return (jsxRuntime.jsx(AxisTitleSideColumn, { "$side": side, "$plotTop": plotTop, "$plotBottom": plotBottom, children: jsxRuntime.jsx(AxisTitleSide, { "$side": side, "$color": axisTitle.color || neutralColor, title: axisTitle.title, children: axisTitle.title }) }));
|
|
19347
|
+
};
|
|
19348
|
+
|
|
18859
19349
|
const Chart = React.memo(({ config, element, elementConfig, type, renderElement }) => {
|
|
18860
19350
|
const theme = styled.useTheme();
|
|
18861
19351
|
const { palette } = theme;
|
|
@@ -18876,12 +19366,18 @@ const Chart = React.memo(({ config, element, elementConfig, type, renderElement
|
|
|
18876
19366
|
const radius = options?.radius || DEFAULT_BARCHART_RADIUS;
|
|
18877
19367
|
const padding = options?.padding || DEFAULT_BAR_PADDING;
|
|
18878
19368
|
const fontColor = options?.fontColor || palette.iconDisabled;
|
|
19369
|
+
// Нейтральная подпись оси — тем же цветом, что и значения шкалы (на макете у них один стиль).
|
|
19370
|
+
const axisTitleColor = options?.fontColor || palette.textDisabled;
|
|
18879
19371
|
const angle = options?.angle || DEFAULT_CHART_ANGLE;
|
|
18880
19372
|
const barWidth = options?.barWidth || DEFAULT_BAR_WIDTH;
|
|
18881
19373
|
const cornerRadius = options?.cornerRadius || 0;
|
|
18882
19374
|
const relatedAttributes = React.useMemo(() => relatedDataSources || [], [relatedDataSources]);
|
|
18883
|
-
const axes = React.useMemo(() => relatedAttributes.filter(
|
|
19375
|
+
const axes = React.useMemo(() => relatedAttributes.filter(isValueAxis), [relatedAttributes]);
|
|
18884
19376
|
const filterName = getChartFilterName(relatedDataSources);
|
|
19377
|
+
const { axisTitles, hasTopTitles, hasSideTitles, axisTitlesHeight, hideLeftAxis, hideRightAxis, } = useChartAxisTitles({ options, axes, isLineChart });
|
|
19378
|
+
// Ширина вертикальных подписей зависит от числа строк текста, поэтому тело между ними меряем
|
|
19379
|
+
// по факту, а не вычитаем константу.
|
|
19380
|
+
const [lineBodyRef, { width: measuredBodyWidth }] = useResizeBox(hasSideTitles);
|
|
18885
19381
|
const isRelated = !!relatedAttributes?.length;
|
|
18886
19382
|
const { data, loading } = useChartData({ element, type });
|
|
18887
19383
|
const totalWidthFromBarWidth = (data[0]?.items?.length || 0) * (barWidth + padding);
|
|
@@ -18909,17 +19405,54 @@ const Chart = React.memo(({ config, element, elementConfig, type, renderElement
|
|
|
18909
19405
|
const chartHeight = React.useMemo(() => (fitsHeight && measuredHeight ? measuredHeight : height), [fitsHeight, measuredHeight, height]);
|
|
18910
19406
|
// Круглый график вписываем по короткой стороне (object-fit: contain), центрируя в ячейке.
|
|
18911
19407
|
const pieSide = React.useMemo(() => (fitsHeight && measuredHeight ? Math.min(chartWidth, measuredHeight) : chartWidth), [fitsHeight, measuredHeight, chartWidth]);
|
|
19408
|
+
// Тело линейного графика — это ячейка за вычетом подписей осей. По высоте вычитаем только в
|
|
19409
|
+
// fill: вне fill chartHeight означает высоту самого тела, и обёртка честно растёт на подписи.
|
|
19410
|
+
const lineBodyWidth = React.useMemo(() => {
|
|
19411
|
+
if (!hasSideTitles)
|
|
19412
|
+
return chartWidth;
|
|
19413
|
+
if (measuredBodyWidth)
|
|
19414
|
+
return measuredBodyWidth;
|
|
19415
|
+
// До первого замера — оценка: одна строка текста и зазор на каждую подпись.
|
|
19416
|
+
const sideTitlesCount = Number(!!axisTitles.left) + Number(!!axisTitles.right);
|
|
19417
|
+
return Math.max(chartWidth - sideTitlesCount * (AXIS_TITLE_LINE_HEIGHT + AXIS_TITLE_GAP), 0);
|
|
19418
|
+
}, [hasSideTitles, measuredBodyWidth, chartWidth, axisTitles]);
|
|
19419
|
+
const lineBodyHeight = React.useMemo(() => (fitsHeight ? Math.max(chartHeight - axisTitlesHeight, 0) : chartHeight), [fitsHeight, chartHeight, axisTitlesHeight]);
|
|
19420
|
+
// Обёртка выше тела на строку горизонтальных подписей. В fill вся высота уже отмерена ячейкой,
|
|
19421
|
+
// поэтому прибавлять нечего — иначе тело вылезло бы за нижнюю границу.
|
|
19422
|
+
const lineWrapperHeight = React.useMemo(() => (fitsHeight ? chartHeight : chartHeight + axisTitlesHeight), [fitsHeight, chartHeight, axisTitlesHeight]);
|
|
18912
19423
|
const [customize] = useChartChange({
|
|
18913
19424
|
dataSources: config.dataSources,
|
|
18914
19425
|
chartId: elementConfig?.id,
|
|
18915
|
-
width:
|
|
18916
|
-
height:
|
|
19426
|
+
width: lineBodyWidth,
|
|
19427
|
+
height: lineBodyHeight,
|
|
18917
19428
|
fontColor,
|
|
18918
19429
|
relatedAttributes,
|
|
18919
19430
|
defaultColor: primaryColor,
|
|
18920
19431
|
markers: configMarkers,
|
|
18921
19432
|
showMarkers,
|
|
18922
19433
|
});
|
|
19434
|
+
const { formatLeftTick, formatRightTick } = useChartAxisTickFormat({
|
|
19435
|
+
dataSources: config.dataSources,
|
|
19436
|
+
relatedAttributes,
|
|
19437
|
+
});
|
|
19438
|
+
const { reportPlotInsets } = React.useContext(ChartPlotContext);
|
|
19439
|
+
// Положение поля графика известно только после отрисовки: ширину шкал вычисляет d3. Смещение тела
|
|
19440
|
+
// берём из offset-размеров, а не из getBoundingClientRect: они не искажаются масштабом превью и
|
|
19441
|
+
// transform предков, а координаты шкалы всегда в пикселях SVG.
|
|
19442
|
+
const customizeLine = React.useCallback(args => {
|
|
19443
|
+
customize?.(args);
|
|
19444
|
+
const body = lineBodyRef.current;
|
|
19445
|
+
const layout = body?.parentElement?.parentElement;
|
|
19446
|
+
if (!reportPlotInsets || !body || !layout)
|
|
19447
|
+
return;
|
|
19448
|
+
const bodyLeft = body.offsetLeft - layout.offsetLeft;
|
|
19449
|
+
const [plotLeft, plotRight] = args.xScale.range();
|
|
19450
|
+
reportPlotInsets({
|
|
19451
|
+
left: Math.round(bodyLeft + plotLeft),
|
|
19452
|
+
right: Math.round(layout.offsetWidth - bodyLeft - plotRight),
|
|
19453
|
+
width: layout.offsetWidth,
|
|
19454
|
+
});
|
|
19455
|
+
}, [customize, reportPlotInsets, lineBodyRef]);
|
|
18923
19456
|
const { formatFilterColor, onFilter } = useWidgetFilters(type, filterName, data?.[0]?.items);
|
|
18924
19457
|
const formattedAttributes = React.useMemo(() => getFormattedAttributes(t, (data[0]?.items || []), attributes, element), [attributes, data, element, t]);
|
|
18925
19458
|
const totalSum = React.useMemo(() => isRelated
|
|
@@ -18978,6 +19511,17 @@ const Chart = React.memo(({ config, element, elementConfig, type, renderElement
|
|
|
18978
19511
|
left: markersMargin,
|
|
18979
19512
|
};
|
|
18980
19513
|
}, [showMarkers, markers?.length]);
|
|
19514
|
+
// Значения шкалы барчарта d3 центрирует на делении, и крайние деления наполовину выступают за поле.
|
|
19515
|
+
// При нулевом поле SVG выступ уходит за SVG, где его срезает BarChartContainer (overflow-x: hidden,
|
|
19516
|
+
// отчего и по вертикали содержимое не visible). Место под выступ резервируем внутри SVG — внешние
|
|
19517
|
+
// размеры обёрток не меняются. Без значений шкалы (showLabels) выступать нечему.
|
|
19518
|
+
const barMargin = React.useMemo(() => showLabels
|
|
19519
|
+
? {
|
|
19520
|
+
...margin,
|
|
19521
|
+
top: Math.max(margin.top, Y_AXIS_LABEL_OVERHANG),
|
|
19522
|
+
bottom: Math.max(margin.bottom, Y_AXIS_LABEL_OVERHANG),
|
|
19523
|
+
}
|
|
19524
|
+
: margin, [showLabels, margin]);
|
|
18981
19525
|
const formatTooltipName = React.useCallback((name) => isRelated ? name : tooltipNameFromAttributes(name, formattedAttributes), [formattedAttributes, isRelated]);
|
|
18982
19526
|
const formatTooltipValue = React.useCallback((value, name) => isRelated
|
|
18983
19527
|
? tooltipValueFromRelatedFeatures(t, value, relatedAttributes, data[0]?.layerInfo)
|
|
@@ -19004,26 +19548,42 @@ const Chart = React.memo(({ config, element, elementConfig, type, renderElement
|
|
|
19004
19548
|
attributeUnits: item.attributeUnits,
|
|
19005
19549
|
dataSourceName: item.dataSourceName,
|
|
19006
19550
|
stroke: item.color || primaryColor,
|
|
19551
|
+
axis: item.axisSide ?? "left",
|
|
19007
19552
|
values: item.items
|
|
19008
19553
|
?.filter(({ value }) => !isEmptyValue(value))
|
|
19009
19554
|
?.map(({ value }) => Number(value)),
|
|
19010
19555
|
}));
|
|
19011
|
-
const isHidedY =
|
|
19012
|
-
const
|
|
19013
|
-
.
|
|
19556
|
+
const isHidedY = hideLeftAxis && hideRightAxis;
|
|
19557
|
+
const valuesOfSide = (side) => lineChartData
|
|
19558
|
+
.filter(({ axis }) => axis === side)
|
|
19014
19559
|
.map(({ values }) => values)
|
|
19015
19560
|
.flat();
|
|
19561
|
+
const leftValues = valuesOfSide("left");
|
|
19562
|
+
const rightValues = valuesOfSide("right");
|
|
19016
19563
|
// Резервируем нижнюю полосу под подписи оси X: без неё ось прижата к нижней границе,
|
|
19017
19564
|
// подписи (сдвинутые на +16px) вылезают за тело и режутся overflow: hidden у ChartFillMeasure.
|
|
19565
|
+
// Сверху резервируем выступ верхнего деления шкалы: оно центрировано на линии сетки и при нулевом
|
|
19566
|
+
// поле наполовину уходит за SVG, где в fill его срезает overflow: hidden у ChartFillMeasure.
|
|
19567
|
+
const topOverhang = Math.max(Y_AXIS_LABEL_OVERHANG - margin.top, 0);
|
|
19018
19568
|
const lineMargin = {
|
|
19019
19569
|
...margin,
|
|
19570
|
+
top: margin.top + topOverhang,
|
|
19020
19571
|
bottom: Math.max(margin.bottom, LINE_CHART_LABEL_MARGIN),
|
|
19021
19572
|
};
|
|
19022
|
-
|
|
19023
|
-
|
|
19024
|
-
|
|
19025
|
-
|
|
19026
|
-
|
|
19573
|
+
// draw.ts вычитает верхнее поле и из нижней границы поля графика — компенсируем добавленное,
|
|
19574
|
+
// чтобы низ поля и зазор до подписей оси X остались на месте.
|
|
19575
|
+
const lineXAxisPadding = LINE_CHART_X_AXIS_PADDING - topOverhang;
|
|
19576
|
+
// Колонки вертикальных подписей повторяют поля SVG, чтобы подпись центрировалась по полю
|
|
19577
|
+
// графика, а не по всей высоте вместе с подписями оси X.
|
|
19578
|
+
const plotTop = lineMargin.top;
|
|
19579
|
+
const plotBottom = lineMargin.top + lineMargin.bottom + lineXAxisPadding;
|
|
19580
|
+
return (jsxRuntime.jsxs(AnyChartWrapper, { height: lineWrapperHeight, children: [jsxRuntime.jsx(LineChartStyles, {}), jsxRuntime.jsxs(LineChartLayout, { children: [hasTopTitles && (jsxRuntime.jsx(AxisTitlesTop, { left: axisTitles.left, right: axisTitles.right, neutralColor: axisTitleColor })), jsxRuntime.jsxs(LineChartRow, { children: [hasSideTitles && (jsxRuntime.jsx(AxisTitleSideSlot, { side: "left", axisTitle: axisTitles.left, neutralColor: axisTitleColor, plotTop: plotTop, plotBottom: plotBottom })), jsxRuntime.jsx(LineChartBody, { ref: lineBodyRef, children: jsxRuntime.jsx(charts.LineChart, { data: lineChartData, labels: labels, markers: markers, width: lineBodyWidth, height: lineBodyHeight, xAxisPadding: lineXAxisPadding, yAxisPadding: 0, min: leftValues.length ? Math.min(...leftValues) : undefined, rightMin: rightValues.length ? Math.min(...rightValues) : undefined, customYAxisSelection: yAxis => {
|
|
19581
|
+
// Обе оси приходят в один колбэк, поэтому сторону определяем по классу.
|
|
19582
|
+
const isRight = yAxis.classed(charts.lineChartClassNames.lineChartYScaleRight);
|
|
19583
|
+
if (isRight ? hideRightAxis : hideLeftAxis) {
|
|
19584
|
+
yAxis.remove();
|
|
19585
|
+
}
|
|
19586
|
+
}, customYAxis: yAxis => yAxis.ticks(LINE_CHART_Y_TICKS).tickFormat(formatLeftTick), customYAxisRight: yAxis => yAxis.ticks(LINE_CHART_Y_TICKS).tickFormat(formatRightTick), renderTooltip: renderLineChartTooltip, customize: customizeLine, dotSnapping: dotSnapping, dynamicTooltipEnable: true, stackedTooltip: true, tooltipClassName: "dashboardLineChartTooltip", drawGridX: !isHidedY, margin: lineMargin }) }), hasSideTitles && (jsxRuntime.jsx(AxisTitleSideSlot, { side: "right", axisTitle: axisTitles.right, neutralColor: axisTitleColor, plotTop: plotTop, plotBottom: plotBottom }))] })] })] }));
|
|
19027
19587
|
}
|
|
19028
19588
|
if (isStackBar) {
|
|
19029
19589
|
// Вне fill chartHeight — высота самой полосы, и обёртка честно становится выше на строку
|
|
@@ -19053,7 +19613,7 @@ const Chart = React.memo(({ config, element, elementConfig, type, renderElement
|
|
|
19053
19613
|
const barRadius = options?.cornerRadius ?? DEFAULT_BARCHART_RADIUS;
|
|
19054
19614
|
bars.attr("rx", barRadius);
|
|
19055
19615
|
bars.attr("ry", barRadius);
|
|
19056
|
-
}, margin:
|
|
19616
|
+
}, margin: barMargin, xAxisPadding: 0, yAxisPadding: 0, formatTooltipValue: formatTooltipValue, formatTooltipName: formatTooltipName, hideTooltipGroupName: true, dynamicTooltipEnable: true, isBarTooltip: true, onBarClick: filterName ? item => onFilter(item.name) : undefined }) }) }));
|
|
19057
19617
|
}, [
|
|
19058
19618
|
customXAxisBottom,
|
|
19059
19619
|
customYAxisLeft,
|
|
@@ -19069,6 +19629,15 @@ const Chart = React.memo(({ config, element, elementConfig, type, renderElement
|
|
|
19069
19629
|
formatFilterColor,
|
|
19070
19630
|
chartWidth,
|
|
19071
19631
|
chartHeight,
|
|
19632
|
+
lineBodyWidth,
|
|
19633
|
+
lineBodyHeight,
|
|
19634
|
+
lineWrapperHeight,
|
|
19635
|
+
axisTitles,
|
|
19636
|
+
hasTopTitles,
|
|
19637
|
+
hasSideTitles,
|
|
19638
|
+
axisTitleColor,
|
|
19639
|
+
hideLeftAxis,
|
|
19640
|
+
hideRightAxis,
|
|
19072
19641
|
fitsHeight,
|
|
19073
19642
|
pieSide,
|
|
19074
19643
|
fill,
|
|
@@ -19078,11 +19647,14 @@ const Chart = React.memo(({ config, element, elementConfig, type, renderElement
|
|
|
19078
19647
|
formatTooltipValue,
|
|
19079
19648
|
formatTooltipName,
|
|
19080
19649
|
filterName,
|
|
19081
|
-
axes,
|
|
19082
19650
|
labels,
|
|
19083
19651
|
margin,
|
|
19652
|
+
barMargin,
|
|
19084
19653
|
renderLineChartTooltip,
|
|
19085
|
-
|
|
19654
|
+
customizeLine,
|
|
19655
|
+
lineBodyRef,
|
|
19656
|
+
formatLeftTick,
|
|
19657
|
+
formatRightTick,
|
|
19086
19658
|
primaryColor,
|
|
19087
19659
|
type,
|
|
19088
19660
|
elementConfig?.children,
|
|
@@ -19099,14 +19671,14 @@ const Chart = React.memo(({ config, element, elementConfig, type, renderElement
|
|
|
19099
19671
|
]);
|
|
19100
19672
|
if (!isVisibleContainer(id, expandable, expanded, expandedContainers))
|
|
19101
19673
|
return null;
|
|
19102
|
-
const chartBody = (jsxRuntime.jsxs(ChartWrapper, { width: isPieChart && fill ? pieSide : width, height: isStackBar ? "auto" : isPieChart ? (fill ? pieSide : width) : chartHeight, column: column, loading: loading, children: [jsxRuntime.jsx(ChartTooltipStyles, {}), renderChart] }));
|
|
19674
|
+
const chartBody = (jsxRuntime.jsxs(ChartWrapper, { width: isPieChart && fill ? pieSide : width, height: isStackBar ? "auto" : isPieChart ? (fill ? pieSide : width) : isLineChart ? lineWrapperHeight : chartHeight, column: column, loading: loading, children: [jsxRuntime.jsx(ChartTooltipStyles, {}), renderChart] }));
|
|
19103
19675
|
if (!fill)
|
|
19104
19676
|
return chartBody;
|
|
19105
19677
|
// fill: тело центрируется в измеряемой обёртке, ref которой питает ResizeObserver обеими осями.
|
|
19106
19678
|
return jsxRuntime.jsx(ChartFillMeasure, { ref: chartRef, children: chartBody });
|
|
19107
19679
|
});
|
|
19108
19680
|
|
|
19109
|
-
const ChartLegend = ({ data, chartElement, fontSize, type, twoColumns, loading }) => {
|
|
19681
|
+
const ChartLegend = ({ data, chartElement, fontSize, type, twoColumns, column, loading }) => {
|
|
19110
19682
|
const { t } = useGlobalContext();
|
|
19111
19683
|
const { sliceItems, showMore, onShowMore } = useShownOtherItems(chartElement?.options);
|
|
19112
19684
|
const axes = React.useMemo(() => getChartAxes(chartElement), [chartElement]);
|
|
@@ -19115,7 +19687,7 @@ const ChartLegend = ({ data, chartElement, fontSize, type, twoColumns, loading }
|
|
|
19115
19687
|
const showMoreBtn = !!shownItems && data.length > shownItems;
|
|
19116
19688
|
if (!loading && !data?.length && !!hideEmpty)
|
|
19117
19689
|
return null;
|
|
19118
|
-
return (jsxRuntime.jsxs(uilibGl.Flex, { flexDirection: "column", opacity: loading ? FILTERED_VALUE_OPACITY / 100 : 1, children: [jsxRuntime.jsx(ChartLegendContainer, { twoColumns: twoColumns, children: sliceItems(data)?.map(({ name, color }, index) => (jsxRuntime.jsxs(ChartLegendItem, { hasAnyFilter: hasAnyFilter, isFiltered: isFiltered(name), onClick: axes?.[0]?.filterName ? () => onFilter(name) : undefined, children: [jsxRuntime.jsx(ChartLegendColor, { color: color }), jsxRuntime.jsx(ChartLegendName, { "$fontSize": fontSize, "$fontColor": fontColor, children: name })] }, index))) }), showMoreBtn && (jsxRuntime.jsx(uilibGl.LegendToggler, { onClick: onShowMore, toggled: showMore, children: t("other", { ns: "dashboard", defaultValue: "Другое" }) }))] }));
|
|
19690
|
+
return (jsxRuntime.jsxs(uilibGl.Flex, { flexDirection: "column", opacity: loading ? FILTERED_VALUE_OPACITY / 100 : 1, children: [jsxRuntime.jsx(ChartLegendContainer, { twoColumns: twoColumns, "$column": column, children: sliceItems(data)?.map(({ name, color }, index) => (jsxRuntime.jsxs(ChartLegendItem, { hasAnyFilter: hasAnyFilter, isFiltered: isFiltered(name), onClick: axes?.[0]?.filterName ? () => onFilter(name) : undefined, children: [jsxRuntime.jsx(ChartLegendColor, { color: color }), jsxRuntime.jsx(ChartLegendName, { "$fontSize": fontSize, "$fontColor": fontColor, children: name })] }, index))) }), showMoreBtn && (jsxRuntime.jsx(uilibGl.LegendToggler, { onClick: onShowMore, toggled: showMore, children: t("other", { ns: "dashboard", defaultValue: "Другое" }) }))] }));
|
|
19119
19691
|
};
|
|
19120
19692
|
|
|
19121
19693
|
const ContainerChildren = React.memo(({ type, items, isColumn, isMain, renderElement }) => {
|
|
@@ -19973,6 +20545,10 @@ const DEFAULT_HEATMAP_STYLE = {
|
|
|
19973
20545
|
exports.ALIGNMENTS = ALIGNMENTS;
|
|
19974
20546
|
exports.ALIGN_ITEMS = ALIGN_ITEMS;
|
|
19975
20547
|
exports.ATTRIBUTE_ICON_ELEMENT_TYPES = ATTRIBUTE_ICON_ELEMENT_TYPES;
|
|
20548
|
+
exports.AXIS_TITLE_GAP = AXIS_TITLE_GAP;
|
|
20549
|
+
exports.AXIS_TITLE_LINE_HEIGHT = AXIS_TITLE_LINE_HEIGHT;
|
|
20550
|
+
exports.AXIS_TITLE_TOP_GRID_GAP = AXIS_TITLE_TOP_GRID_GAP;
|
|
20551
|
+
exports.AXIS_TITLE_TOP_HEIGHT = AXIS_TITLE_TOP_HEIGHT;
|
|
19976
20552
|
exports.AddButtonRow = AddButtonRow;
|
|
19977
20553
|
exports.AddFeatureButton = AddFeatureButton;
|
|
19978
20554
|
exports.AddFeatureContainer = AddFeatureContainer;
|
|
@@ -19982,6 +20558,7 @@ exports.AttributeGalleryContainer = AttributeGalleryContainer;
|
|
|
19982
20558
|
exports.AttributeLabel = AttributeLabel;
|
|
19983
20559
|
exports.BASE_CONTAINER_STYLE = BASE_CONTAINER_STYLE;
|
|
19984
20560
|
exports.BG_IMAGE_SLOT_ID = BG_IMAGE_SLOT_ID;
|
|
20561
|
+
exports.CHART_AXIS_ALIGN_JUSTIFY = CHART_AXIS_ALIGN_JUSTIFY;
|
|
19985
20562
|
exports.CHART_TYPES = CHART_TYPES;
|
|
19986
20563
|
exports.COMPACT_FRACTION_DIGITS = COMPACT_FRACTION_DIGITS;
|
|
19987
20564
|
exports.CONFIG_PAGES_ID = CONFIG_PAGES_ID;
|
|
@@ -20135,6 +20712,8 @@ exports.HiddenTitleItems = HiddenTitleItems;
|
|
|
20135
20712
|
exports.IconContainer = IconContainer;
|
|
20136
20713
|
exports.ImageContainer = ImageContainer;
|
|
20137
20714
|
exports.LEFT_PANEL_HEADER_HEIGHT = LEFT_PANEL_HEADER_HEIGHT;
|
|
20715
|
+
exports.LINE_CHART_X_LABELS_BLANK = LINE_CHART_X_LABELS_BLANK;
|
|
20716
|
+
exports.LINE_CHART_Y_TICKS = LINE_CHART_Y_TICKS;
|
|
20138
20717
|
exports.Layer = Layer;
|
|
20139
20718
|
exports.LayerDescription = LayerDescription;
|
|
20140
20719
|
exports.LayerGroupList = LayerGroupList;
|
|
@@ -20214,6 +20793,9 @@ exports.TwoColumnContainer = TwoColumnContainer;
|
|
|
20214
20793
|
exports.UploadContainer = UploadContainer;
|
|
20215
20794
|
exports.VIEW_MODES = VIEW_MODES;
|
|
20216
20795
|
exports.VoteContainer = VoteContainer;
|
|
20796
|
+
exports.X_AXIS_TITLE_GAP = X_AXIS_TITLE_GAP;
|
|
20797
|
+
exports.X_AXIS_TITLE_LEGEND_GAP = X_AXIS_TITLE_LEGEND_GAP;
|
|
20798
|
+
exports.Y_AXIS_LABEL_OVERHANG = Y_AXIS_LABEL_OVERHANG;
|
|
20217
20799
|
exports.ZOOM_FILTER_NAME = ZOOM_FILTER_NAME;
|
|
20218
20800
|
exports.addDataSource = addDataSource;
|
|
20219
20801
|
exports.addDataSources = addDataSources;
|
|
@@ -20273,6 +20855,7 @@ exports.getAttributeIconUrl = getAttributeIconUrl;
|
|
|
20273
20855
|
exports.getAttributeValue = getAttributeValue;
|
|
20274
20856
|
exports.getAttributesConfiguration = getAttributesConfiguration;
|
|
20275
20857
|
exports.getAverageTrackSize = getAverageTrackSize;
|
|
20858
|
+
exports.getAxisSide = getAxisSide;
|
|
20276
20859
|
exports.getChartAxes = getChartAxes;
|
|
20277
20860
|
exports.getChartFilterName = getChartFilterName;
|
|
20278
20861
|
exports.getChartMarkers = getChartMarkers;
|
|
@@ -20324,6 +20907,7 @@ exports.getTrackSize = getTrackSize;
|
|
|
20324
20907
|
exports.getTrackSizeKey = getTrackSizeKey;
|
|
20325
20908
|
exports.getTrackSizes = getTrackSizes;
|
|
20326
20909
|
exports.getWrapperSizeStyle = getWrapperSizeStyle;
|
|
20910
|
+
exports.hasAxisConfig = hasAxisConfig;
|
|
20327
20911
|
exports.hasContainerBgImage = hasContainerBgImage;
|
|
20328
20912
|
exports.hexToRgba = hexToRgba;
|
|
20329
20913
|
exports.isCrossOriginUrl = isCrossOriginUrl;
|
|
@@ -20340,8 +20924,10 @@ exports.isNotValidSelectedTab = isNotValidSelectedTab;
|
|
|
20340
20924
|
exports.isNumeric = isNumeric;
|
|
20341
20925
|
exports.isObject = isObject;
|
|
20342
20926
|
exports.isProxyService = isProxyService;
|
|
20927
|
+
exports.isRightAxis = isRightAxis;
|
|
20343
20928
|
exports.isRootOwningContainer = isRootOwningContainer;
|
|
20344
20929
|
exports.isTreeFilterValue = isTreeFilterValue;
|
|
20930
|
+
exports.isValueAxis = isValueAxis;
|
|
20345
20931
|
exports.isVisibleContainer = isVisibleContainer;
|
|
20346
20932
|
exports.mapNodeById = mapNodeById;
|
|
20347
20933
|
exports.mergeAttributeConfigurations = mergeAttributeConfigurations;
|
|
@@ -20357,6 +20943,7 @@ exports.pointOptions = pointOptions;
|
|
|
20357
20943
|
exports.removeDataSource = removeDataSource;
|
|
20358
20944
|
exports.removeTracks = removeTracks;
|
|
20359
20945
|
exports.replaceNodesByIds = replaceNodesByIds;
|
|
20946
|
+
exports.resolveAxis = resolveAxis;
|
|
20360
20947
|
exports.rgbToHex = rgbToHex;
|
|
20361
20948
|
exports.roundFr = roundFr;
|
|
20362
20949
|
exports.roundTotalSum = roundTotalSum;
|
|
@@ -20384,6 +20971,9 @@ exports.useAttachmentsView = useAttachmentsView;
|
|
|
20384
20971
|
exports.useAutoCompleteControl = useAutoCompleteControl;
|
|
20385
20972
|
exports.useBeforeSave = useBeforeSave;
|
|
20386
20973
|
exports.useBgImageHost = useBgImageHost;
|
|
20974
|
+
exports.useChartAxisAttributes = useChartAxisAttributes;
|
|
20975
|
+
exports.useChartAxisTickFormat = useChartAxisTickFormat;
|
|
20976
|
+
exports.useChartAxisTitles = useChartAxisTitles;
|
|
20387
20977
|
exports.useChartChange = useChartChange;
|
|
20388
20978
|
exports.useChartData = useChartData;
|
|
20389
20979
|
exports.useConfigDataSources = useConfigDataSources;
|