@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.
Files changed (28) hide show
  1. package/dist/components/Dashboard/componentTypes.d.ts +3 -3
  2. package/dist/components/Dashboard/components/Chart/ChartPlotContext.d.ts +24 -0
  3. package/dist/components/Dashboard/components/Chart/components/AxisTitles.d.ts +23 -0
  4. package/dist/components/Dashboard/components/Chart/styled.d.ts +33 -0
  5. package/dist/components/Dashboard/components/ChartLegend/index.d.ts +1 -0
  6. package/dist/components/Dashboard/components/ChartLegend/styled.d.ts +1 -0
  7. package/dist/components/Dashboard/constants.d.ts +37 -1
  8. package/dist/components/Dashboard/containers/AttachmentContainer/constants.d.ts +1 -0
  9. package/dist/components/Dashboard/containers/ChartContainer/types.d.ts +3 -1
  10. package/dist/components/Dashboard/containers/FiltersContainer/constants.d.ts +1 -0
  11. package/dist/components/Dashboard/containers/styled.d.ts +38 -0
  12. package/dist/components/Dashboard/elements/ElementIcon/components/AttachmentIcon/index.d.ts +7 -0
  13. package/dist/components/Dashboard/elements/ElementIcon/components/AttachmentIcon/styled.d.ts +3 -0
  14. package/dist/components/Dashboard/elements/ElementIcon/components/AttachmentIcon/types.d.ts +15 -0
  15. package/dist/components/Dashboard/elements/ElementIcon/constants.d.ts +2 -0
  16. package/dist/components/Dashboard/elements/ElementIcon/utils/getAttachmentIcon.d.ts +7 -0
  17. package/dist/components/Dashboard/hooks/index.d.ts +3 -0
  18. package/dist/components/Dashboard/hooks/useChartAxisAttributes.d.ts +19 -0
  19. package/dist/components/Dashboard/hooks/useChartAxisTickFormat.d.ts +18 -0
  20. package/dist/components/Dashboard/hooks/useChartAxisTitles.d.ts +32 -0
  21. package/dist/components/Dashboard/types.d.ts +59 -1
  22. package/dist/components/Dashboard/utils/chartAxis.d.ts +29 -0
  23. package/dist/components/Dashboard/utils/index.d.ts +1 -0
  24. package/dist/index.js +679 -89
  25. package/dist/index.js.map +1 -1
  26. package/dist/react.esm.js +662 -90
  27. package/dist/react.esm.js.map +1 -1
  28. package/package.json +3 -3
package/dist/react.esm.js CHANGED
@@ -3356,6 +3356,46 @@ const MAX_CHART_WIDTH = 300;
3356
3356
  const DEFAULT_CHART_ANGLE = 4;
3357
3357
  const DEFAULT_CHART_HEIGHT = 90;
3358
3358
  const STACK_BAR_TOTAL_HEIGHT = 20;
3359
+ /** Высота строки текста подписи оси (шрифт 10px) — по макету. */
3360
+ const AXIS_TITLE_LINE_HEIGHT = 12;
3361
+ /** Зазор между подписью оси и значениями шкалы — по макету. */
3362
+ const AXIS_TITLE_GAP = 4;
3363
+ /**
3364
+ * Поле SVG графика под выступ крайних делений шкалы значений.
3365
+ *
3366
+ * d3 центрирует подпись деления на его линии (`dy: 0.32em`), поэтому у крайнего деления половина подписи
3367
+ * выходит за поле графика. Если поле вплотную к краю SVG, половина оказывается за SVG, и её срезает
3368
+ * обёртка: у линейного графика в fill — `ChartFillMeasure` (`overflow: hidden`), у барчарта всегда —
3369
+ * `BarChartContainer` (`overflow-x: hidden`, отчего и по вертикали содержимое не `visible`).
3370
+ */
3371
+ const Y_AXIS_LABEL_OVERHANG = 6;
3372
+ /** Зазор от строки горизонтальных подписей осей до верхней линии сетки — по макету. */
3373
+ const AXIS_TITLE_TOP_GRID_GAP = 9;
3374
+ /** Число делений шкал линейного графика; у левой и правой шкал оно общее, чтобы совпадала сетка. */
3375
+ const LINE_CHART_Y_TICKS = 4;
3376
+ /**
3377
+ * Высота строки горизонтальных подписей осей (`axis.titlePosition: "top"`). Вместе с верхним полем SVG
3378
+ * (Y_AXIS_LABEL_OVERHANG) даёт строку текста и 9px до верхней линии сетки: деление центрировано на
3379
+ * линии и выступает над ней на ~5px, так что между подписью и значением шкалы остаются те же 4px,
3380
+ * что и у вертикальной подписи.
3381
+ */
3382
+ const AXIS_TITLE_TOP_HEIGHT = AXIS_TITLE_LINE_HEIGHT + AXIS_TITLE_TOP_GRID_GAP - Y_AXIS_LABEL_OVERHANG;
3383
+ /**
3384
+ * Пустая полоса под подписями оси X внутри SVG линейного графика: под ось зарезервировано
3385
+ * LINE_CHART_LABEL_MARGIN (20px), а подписи сдвинуты вниз на +16px, поэтому строка подписи
3386
+ * (шрифт d3 10px) кончается примерно в 14px от низа SVG.
3387
+ */
3388
+ const LINE_CHART_X_LABELS_BLANK = 14;
3389
+ /** Зазор между подписями оси X и подписью самой оси — по макету. */
3390
+ const X_AXIS_TITLE_GAP = 4;
3391
+ /** Зазор между подписью оси X и легендой, идущей отдельной строкой, — по макету. */
3392
+ const X_AXIS_TITLE_LEGEND_GAP = 8;
3393
+ /** Выравнивание подписи оси X → `justify-content` строки-подвала графика. */
3394
+ const CHART_AXIS_ALIGN_JUSTIFY = {
3395
+ left: "flex-start",
3396
+ center: "center",
3397
+ right: "flex-end",
3398
+ };
3359
3399
  const FILTER_PREFIX = "%";
3360
3400
  /** Имя системного геометрического фильтра — выделение, нарисованное пользователем на карте. */
3361
3401
  const GEOMETRY_FILTER_NAME = "geometry";
@@ -3465,7 +3505,76 @@ const CONTAINER_BODY_FILL_STYLE = {
3465
3505
  */
3466
3506
  const CONTAINERS_GROUP_DEFAULTS = { boxSizing: "border-box" };
3467
3507
 
3468
- const ChartTooltipTable = styled.table.withConfig({ displayName: "ChartTooltipTable", componentId: "sc-1fk3ts5" }) `
3508
+ /** Обёртка линейного графика: строка горизонтальных подписей над телом, вертикальные — по бокам. */
3509
+ const LineChartLayout = styled.div.withConfig({ displayName: "LineChartLayout", componentId: "sc-gmzloe" }) `
3510
+ display: flex;
3511
+ flex-direction: column;
3512
+ width: 100%;
3513
+ height: 100%;
3514
+ min-width: 0;
3515
+ min-height: 0;
3516
+ `;
3517
+ const LineChartRow = styled.div.withConfig({ displayName: "LineChartRow", componentId: "sc-dxzi1m" }) `
3518
+ display: flex;
3519
+ flex: 1 1 auto;
3520
+ align-items: stretch;
3521
+ min-width: 0;
3522
+ min-height: 0;
3523
+ `;
3524
+ /**
3525
+ * Тело линейного графика между вертикальными подписями осей. Забирает всю оставшуюся ширину строки —
3526
+ * её и измеряем, чтобы отдать d3 пиксельную ширину: подписи бывают в одну и в две строки.
3527
+ */
3528
+ const LineChartBody = styled.div.withConfig({ displayName: "LineChartBody", componentId: "sc-1sbsc7u" }) `
3529
+ flex: 1 1 auto;
3530
+ min-width: 0;
3531
+ `;
3532
+ const AxisTitlesRow = styled.div.withConfig({ displayName: "AxisTitlesRow", componentId: "sc-1hskx6f" }) `
3533
+ display: flex;
3534
+ flex: 0 0 ${AXIS_TITLE_TOP_HEIGHT}px;
3535
+ justify-content: space-between;
3536
+ align-items: flex-start;
3537
+ gap: 0.5rem;
3538
+ height: ${AXIS_TITLE_TOP_HEIGHT}px;
3539
+ min-width: 0;
3540
+ `;
3541
+ const AxisTitleText = styled.div.withConfig({ displayName: "AxisTitleText", componentId: "sc-9shs4l" }) `
3542
+ font-size: 0.625rem;
3543
+ line-height: ${AXIS_TITLE_LINE_HEIGHT}px;
3544
+ color: ${({ $color, theme: { palette } }) => $color || palette.iconDisabled};
3545
+ `;
3546
+ const AxisTitleTop = styled(AxisTitleText).withConfig({ displayName: "AxisTitleTop", componentId: "sc-pv3dgm" }) `
3547
+ overflow: hidden;
3548
+ white-space: nowrap;
3549
+ text-overflow: ellipsis;
3550
+ `;
3551
+ /**
3552
+ * Колонка вертикальной подписи оси: ширина — по тексту (одна или две строки) плюс зазор до значений
3553
+ * шкалы. Вертикальные отступы колонки повторяют поля SVG, поэтому подпись центрируется по полю
3554
+ * графика, а не по всей высоте вместе с подписями оси X.
3555
+ */
3556
+ const AxisTitleSideColumn = styled.div.withConfig({ displayName: "AxisTitleSideColumn", componentId: "sc-11nmkfu" }) `
3557
+ display: flex;
3558
+ flex: 0 0 auto;
3559
+ align-items: center;
3560
+ justify-content: center;
3561
+ box-sizing: border-box;
3562
+ padding: ${({ $plotTop }) => $plotTop}px 0 ${({ $plotBottom }) => $plotBottom}px;
3563
+ ${({ $side }) => ($side === "left" ? "padding-right" : "padding-left")}: ${AXIS_TITLE_GAP}px;
3564
+ `;
3565
+ /**
3566
+ * Сам текст вертикальной подписи. Поворот вынесен на внутренний элемент: повёрнутая целиком колонка
3567
+ * перенесла бы зазор до шкалы на внешнюю сторону. Левая подпись читается снизу вверх, правая — сверху
3568
+ * вниз, как на макете.
3569
+ */
3570
+ const AxisTitleSide = styled(AxisTitleText).withConfig({ displayName: "AxisTitleSide", componentId: "sc-lretfl" }) `
3571
+ max-height: 100%;
3572
+ overflow: hidden;
3573
+ text-align: center;
3574
+ writing-mode: vertical-rl;
3575
+ transform: ${({ $side }) => ($side === "left" ? "rotate(180deg)" : "none")};
3576
+ `;
3577
+ const ChartTooltipTable = styled.table.withConfig({ displayName: "ChartTooltipTable", componentId: "sc-1j0a1z" }) `
3469
3578
  td {
3470
3579
  padding: 0;
3471
3580
  }
@@ -3474,7 +3583,7 @@ const ChartTooltipTable = styled.table.withConfig({ displayName: "ChartTooltipTa
3474
3583
  padding-bottom: 0.375rem;
3475
3584
  }
3476
3585
  `;
3477
- const ChartTooltip = styled(Flex).withConfig({ displayName: "ChartTooltip", componentId: "sc-1iyyyv7" }) `
3586
+ const ChartTooltip = styled(Flex).withConfig({ displayName: "ChartTooltip", componentId: "sc-1ps09qa" }) `
3478
3587
  flex-wrap: nowrap;
3479
3588
  background: rgb(48, 69, 79);
3480
3589
  border-radius: 0.25rem;
@@ -3484,26 +3593,26 @@ const ChartTooltip = styled(Flex).withConfig({ displayName: "ChartTooltip", comp
3484
3593
  margin-bottom: 0.25rem;
3485
3594
  }
3486
3595
  `;
3487
- const ChartTooltipLabel = styled.div.withConfig({ displayName: "ChartTooltipLabel", componentId: "sc-gl0qxz" }) `
3596
+ const ChartTooltipLabel = styled.div.withConfig({ displayName: "ChartTooltipLabel", componentId: "sc-1chgokj" }) `
3488
3597
  margin-right: 0.25rem;
3489
3598
  `;
3490
- const ChartTooltipColor = styled.div.withConfig({ displayName: "ChartTooltipColor", componentId: "sc-tmvvbh" }) `
3599
+ const ChartTooltipColor = styled.div.withConfig({ displayName: "ChartTooltipColor", componentId: "sc-1un94mw" }) `
3491
3600
  width: 0.625rem;
3492
3601
  height: 0.625rem;
3493
3602
  background-color: ${({ $color }) => $color};
3494
3603
  border-radius: ${({ theme: { borderRadius } }) => borderRadius.smallest};
3495
3604
  margin-right: 0.25rem;
3496
3605
  `;
3497
- const ChartLegendColor$1 = styled.div.withConfig({ displayName: "ChartLegendColor", componentId: "sc-l66k4q" }) `
3606
+ const ChartLegendColor$1 = styled.div.withConfig({ displayName: "ChartLegendColor", componentId: "sc-sw1cfw" }) `
3498
3607
  width: 0.5rem;
3499
3608
  height: 0.5rem;
3500
3609
  background-color: ${({ $color }) => $color};
3501
3610
  border-radius: 0.125rem;
3502
3611
  `;
3503
- const ChartLegendValue = styled.div.withConfig({ displayName: "ChartLegendValue", componentId: "sc-1d7z775" }) `
3612
+ const ChartLegendValue = styled.div.withConfig({ displayName: "ChartLegendValue", componentId: "sc-1s1kjum" }) `
3504
3613
  opacity: 0.65;
3505
3614
  `;
3506
- const ChartTooltipRow = styled(Flex).withConfig({ displayName: "ChartTooltipRow", componentId: "sc-ih1uc5" }) `
3615
+ const ChartTooltipRow = styled(Flex).withConfig({ displayName: "ChartTooltipRow", componentId: "sc-ftllia" }) `
3507
3616
  display: flex;
3508
3617
  flex-direction: row;
3509
3618
  flex-wrap: nowrap;
@@ -3515,11 +3624,11 @@ const ChartTooltipRow = styled(Flex).withConfig({ displayName: "ChartTooltipRow"
3515
3624
  margin-right: 0.25rem;
3516
3625
  }
3517
3626
  `;
3518
- const ChartTooltipName = styled.div.withConfig({ displayName: "ChartTooltipName", componentId: "sc-sm4g0t" }) `
3627
+ const ChartTooltipName = styled.div.withConfig({ displayName: "ChartTooltipName", componentId: "sc-16ov53z" }) `
3519
3628
  margin-right: 1rem;
3520
3629
  opacity: 0.65;
3521
3630
  `;
3522
- const PieChartCenter = styled.div.withConfig({ displayName: "PieChartCenter", componentId: "sc-1le93jy" }) `
3631
+ const PieChartCenter = styled.div.withConfig({ displayName: "PieChartCenter", componentId: "sc-5m536c" }) `
3523
3632
  position: absolute;
3524
3633
  top: 50%;
3525
3634
  left: 50%;
@@ -3528,7 +3637,7 @@ const PieChartCenter = styled.div.withConfig({ displayName: "PieChartCenter", co
3528
3637
  font-weight: bold;
3529
3638
  font-size: 1.5rem;
3530
3639
  `;
3531
- const ChartWrapperContainer = styled.div.withConfig({ displayName: "ChartWrapperContainer", componentId: "sc-1f4x9zv" }) `
3640
+ const ChartWrapperContainer = styled.div.withConfig({ displayName: "ChartWrapperContainer", componentId: "sc-1ixngl" }) `
3532
3641
  position: relative;
3533
3642
  width: 100%;
3534
3643
  `;
@@ -3537,7 +3646,7 @@ const ChartWrapperContainer = styled.div.withConfig({ displayName: "ChartWrapper
3537
3646
  * на неё вешается ref для ResizeObserver — меряем обе оси без цикла. Плоский div (не Flex uilib), как соседний
3538
3647
  * ChartWrapperContainer, чтобы ref гарантированно дошёл до DOM-узла. Тело графика центрируется внутри.
3539
3648
  */
3540
- const ChartFillMeasure = styled.div.withConfig({ displayName: "ChartFillMeasure", componentId: "sc-1r4oerb" }) `
3649
+ const ChartFillMeasure = styled.div.withConfig({ displayName: "ChartFillMeasure", componentId: "sc-3rqqmt" }) `
3541
3650
  position: relative;
3542
3651
  display: flex;
3543
3652
  align-items: center;
@@ -3555,7 +3664,7 @@ const ChartFillMeasure = styled.div.withConfig({ displayName: "ChartFillMeasure"
3555
3664
  display: block;
3556
3665
  }
3557
3666
  `;
3558
- const Tooltip = styled.div.withConfig({ displayName: "Tooltip", componentId: "sc-1va8cy3" }) `
3667
+ const Tooltip = styled.div.withConfig({ displayName: "Tooltip", componentId: "sc-k91v0" }) `
3559
3668
  position: relative;
3560
3669
  border-radius: 0.25rem;
3561
3670
  background-color: rgba(28, 33, 48);
@@ -3660,7 +3769,7 @@ const LineChartStyles = createGlobalStyle `
3660
3769
  fill: ${({ theme: { palette } }) => palette.textDisabled};
3661
3770
  }
3662
3771
  `;
3663
- const StyledBarChart = styled(BarChart$1).withConfig({ displayName: "StyledBarChart", componentId: "sc-1l1kx80" }) `
3772
+ const StyledBarChart = styled(BarChart$1).withConfig({ displayName: "StyledBarChart", componentId: "sc-16gz38d" }) `
3664
3773
  .domain {
3665
3774
  display: none;
3666
3775
  }
@@ -4548,6 +4657,79 @@ const getAttributeConfigurationByName = (attributes, name) => {
4548
4657
  return attributes[name];
4549
4658
  };
4550
4659
 
4660
+ const EMPTY_AXIS = {};
4661
+ const warnedLegacySources = new Set();
4662
+ // Сборщик консьюмера подставляет `process.env.NODE_ENV` строкой, поэтому выражение должно быть
4663
+ // ровно таким — без `typeof process` и без `?.`. Сам `process` webpack 5 в браузер не завозит:
4664
+ // проверка `typeof process` отключила бы предупреждения и в dev. Консьюмер без сборщика получит
4665
+ // ReferenceError — он гасится, и предупреждений просто нет.
4666
+ const isDevEnvironment = (() => {
4667
+ try {
4668
+ return process.env.NODE_ENV !== "production";
4669
+ }
4670
+ catch {
4671
+ return false;
4672
+ }
4673
+ })();
4674
+ /**
4675
+ * Один раз на источник сообщает, что конфиг остался на устаревшей плоской форме.
4676
+ *
4677
+ * Без такого маркера момент «легаси закончилось» не наступает никогда, и временный слой
4678
+ * совместимости становится постоянным.
4679
+ */
4680
+ const warnLegacyAxis = ({ dataSourceName, attributeName }) => {
4681
+ if (!isDevEnvironment)
4682
+ return;
4683
+ const key = `${dataSourceName ?? "?"}:${attributeName ?? "?"}`;
4684
+ if (warnedLegacySources.has(key))
4685
+ return;
4686
+ warnedLegacySources.add(key);
4687
+ // eslint-disable-next-line no-console
4688
+ console.warn(`[Dashboard] Источник "${key}" использует устаревшие поля оси (chartAxis/axisColor/hideAxis). ` +
4689
+ "Перепишите их в объект axis: { type, side, color, title, hide }. " +
4690
+ "Слой совместимости временный.");
4691
+ };
4692
+ /**
4693
+ * Настройки оси источника.
4694
+ *
4695
+ * Читает `axis`, а при его отсутствии собирает объект из устаревших плоских полей
4696
+ * `chartAxis` / `axisColor` / `hideAxis`. Заданный `axis` имеет приоритет: узел, который уже
4697
+ * правили в редакторе, не должен откатываться к старым значениям.
4698
+ */
4699
+ const resolveAxis = (source) => {
4700
+ if (!source)
4701
+ return EMPTY_AXIS;
4702
+ const { axis, chartAxis, axisColor, hideAxis } = source;
4703
+ if (axis)
4704
+ return axis;
4705
+ if (chartAxis === undefined && axisColor === undefined && hideAxis === undefined)
4706
+ return EMPTY_AXIS;
4707
+ warnLegacyAxis(source);
4708
+ return {
4709
+ type: chartAxis === "x" ? "x" : "y",
4710
+ color: axisColor,
4711
+ hide: hideAxis,
4712
+ };
4713
+ };
4714
+ /**
4715
+ * Источник питает ось значений.
4716
+ *
4717
+ * Отсутствие `axis.type` означает `"y"`: конфиг без явного типа — самый частый случай,
4718
+ * и раньше такой источник молча выпадал из графика.
4719
+ */
4720
+ const isValueAxis = (source) => (resolveAxis(source).type ?? "y") === "y";
4721
+ /** Сторона оси; отсутствие `axis.side` означает `"left"`. */
4722
+ const getAxisSide = (source) => resolveAxis(source).side ?? "left";
4723
+ /** Источник выводится на правой оси. */
4724
+ const isRightAxis = (source) => getAxisSide(source) === "right";
4725
+ /**
4726
+ * Запись описывает ось чарта, а не источник фильтра.
4727
+ *
4728
+ * `BarChartFilter` собирает `relatedConfig` вручную и осей не задаёт — ему нужна исходная палитра,
4729
+ * а не растянутая под число элементов.
4730
+ */
4731
+ const hasAxisConfig = ({ axis, chartAxis }) => axis !== undefined || chartAxis !== undefined;
4732
+
4551
4733
  const getAttributesConfiguration = (layer) => {
4552
4734
  const layerAttributeConfiguration = layer?.configuration?.attributesConfiguration ?? {};
4553
4735
  const { geometryAttribute, idAttribute } = layerAttributeConfiguration;
@@ -4572,7 +4754,7 @@ const getAttributesConfiguration = (layer) => {
4572
4754
 
4573
4755
  const formatChartRelatedValue = (t, value, layerInfo, relatedAttributes) => {
4574
4756
  const layerDefinition = getAttributesConfiguration(layerInfo);
4575
- const relatedAxis = relatedAttributes.find(({ chartAxis }) => chartAxis === "y");
4757
+ const relatedAxis = relatedAttributes.find(isValueAxis);
4576
4758
  const attribute = getAttributeConfigurationByName(layerDefinition.attributes, relatedAxis?.attributeName);
4577
4759
  return attribute
4578
4760
  ? formatAttributeValue({ t, type: attribute.type, value, stringFormat: attribute.stringFormat })
@@ -5417,11 +5599,11 @@ const getAttributeValue = (element, attributes) => {
5417
5599
  return typeof value === "string" && maxLength && maxLength < value.length ? (jsx(TextTrim, { maxLength: maxLength, wordBreak: wordBreak, expandable: expandable, lineBreak: lineBreak, children: value })) : (value);
5418
5600
  };
5419
5601
 
5420
- const getChartAxes = (chartElement) => chartElement?.options?.relatedDataSources?.filter(({ chartAxis }) => chartAxis === "y");
5602
+ const getChartAxes = (chartElement) => chartElement?.options?.relatedDataSources?.filter(isValueAxis);
5421
5603
 
5422
5604
  const getChartFilterName = (relatedDataSources) => {
5423
5605
  const relatedAttributes = relatedDataSources || [];
5424
- const axes = relatedAttributes.filter(({ chartAxis }) => chartAxis === "y");
5606
+ const axes = relatedAttributes.filter(isValueAxis);
5425
5607
  return axes?.[0]?.filterName;
5426
5608
  };
5427
5609
 
@@ -5640,13 +5822,14 @@ const GRID_TILE_SIZE = "4.5rem";
5640
5822
  const LIST_ICON_SIZE = "1.5rem";
5641
5823
  const JPG_MIME_TYPE = "image/jpeg";
5642
5824
  const PNG_MIME_TYPE = "image/png";
5825
+ const SVG_MIME_TYPE = "image/svg+xml";
5643
5826
  const IMAGE_MIME_TYPES = [
5644
5827
  "image/apng",
5645
5828
  "image/avif",
5646
5829
  "image/gif",
5647
5830
  "image/jpeg",
5648
5831
  "image/png",
5649
- "image/svg+xml",
5832
+ SVG_MIME_TYPE,
5650
5833
  "image/webp",
5651
5834
  ];
5652
5835
  const XLSX_MIME_TYPES = [
@@ -7051,6 +7234,96 @@ const useFetchImageWithAuth = (url) => {
7051
7234
  return isExternal ? url : blobUrl;
7052
7235
  };
7053
7236
 
7237
+ /**
7238
+ * Оси значений линейного графика и атрибуты, по `stringFormat` которых форматируются деления.
7239
+ *
7240
+ * У левой и правой осей это разные атрибуты, поэтому они резолвятся по отдельности: иначе на
7241
+ * правой оси окажутся единицы измерения левой.
7242
+ */
7243
+ const useChartAxisAttributes = ({ dataSources, relatedAttributes }) => {
7244
+ const { layerInfos, dataSources: fetchedDataSources } = useWidgetContext();
7245
+ const axes = useMemo(() => (relatedAttributes || []).filter(isValueAxis), [relatedAttributes]);
7246
+ const strokeColors = useMemo(() => axes.map(related => resolveAxis(related).color), [axes]);
7247
+ const leftAxis = useMemo(() => axes.find(related => !isRightAxis(related)) ?? axes[0] ?? relatedAttributes?.[0], [axes, relatedAttributes]);
7248
+ const rightAxis = useMemo(() => axes.find(isRightAxis), [axes]);
7249
+ const resolveAxisAttribute = useCallback((related) => {
7250
+ if (!related)
7251
+ return undefined;
7252
+ const layerInfo = getDataSourceLayerInfo({
7253
+ layerInfos,
7254
+ configDataSource: dataSources?.find(({ name }) => name === related.dataSourceName),
7255
+ fetchedDataSource: getDataSource(related.dataSourceName, fetchedDataSources),
7256
+ });
7257
+ return getLayerInfoAttribute(layerInfo, related.attributeName);
7258
+ }, [dataSources, fetchedDataSources, layerInfos]);
7259
+ const leftAxisAttribute = useMemo(() => resolveAxisAttribute(leftAxis), [resolveAxisAttribute, leftAxis]);
7260
+ const rightAxisAttribute = useMemo(() => resolveAxisAttribute(rightAxis), [resolveAxisAttribute, rightAxis]);
7261
+ return { axes, strokeColors, leftAxisAttribute, rightAxisAttribute };
7262
+ };
7263
+
7264
+ /**
7265
+ * Форматтеры делений левой и правой шкал линейного графика — по `stringFormat` своего атрибута.
7266
+ *
7267
+ * Отдаются в `tickFormat` генератора оси, а не подменяют текст после отрисовки: d3 измеряет ширину
7268
+ * шкалы по уже нарисованным подписям и по ней сдвигает поле графика. Подмена текста задним числом
7269
+ * (`30` → `30,0`) делала подпись шире замера, и она заезжала на подпись оси слева.
7270
+ */
7271
+ const useChartAxisTickFormat = ({ dataSources, relatedAttributes }) => {
7272
+ const { t } = useGlobalContext();
7273
+ const { leftAxisAttribute, rightAxisAttribute } = useChartAxisAttributes({ dataSources, relatedAttributes });
7274
+ const createTickFormat = useCallback((attribute) => (value) => {
7275
+ if (!value)
7276
+ return "0";
7277
+ return attribute
7278
+ ? String(formatAttributeValue({
7279
+ t,
7280
+ type: attribute.type,
7281
+ value: Number(value),
7282
+ stringFormat: attribute.stringFormat,
7283
+ noUnits: true,
7284
+ }))
7285
+ : String(value);
7286
+ }, [t]);
7287
+ const formatLeftTick = useMemo(() => createTickFormat(leftAxisAttribute), [createTickFormat, leftAxisAttribute]);
7288
+ const formatRightTick = useMemo(() => createTickFormat(rightAxisAttribute), [createTickFormat, rightAxisAttribute]);
7289
+ return { formatLeftTick, formatRightTick };
7290
+ };
7291
+
7292
+ /**
7293
+ * Подписи осей линейного графика и место, которое они занимают над телом графика.
7294
+ *
7295
+ * Режим размещения общий для всех осей (`options.axis.titlePosition`), текст — у каждой оси свой.
7296
+ * Цвет подписи отдельной опцией не задаётся: одна серия на стороне — подпись в её цвет, несколько —
7297
+ * нейтральная, потому что тогда подпись описывает всю ось, а не одну из линий. Ширину вертикальных
7298
+ * подписей здесь не считаем — она зависит от числа строк текста и измеряется по факту.
7299
+ */
7300
+ const useChartAxisTitles = ({ options, axes, isLineChart }) => {
7301
+ const axisTitlePosition = options?.axis?.titlePosition ?? "side";
7302
+ const axisTitles = useMemo(() => {
7303
+ const pick = (side) => {
7304
+ const sideAxes = axes.filter(related => getAxisSide(related) === side);
7305
+ const title = sideAxes.map(resolveAxis).find(axis => axis.title)?.title;
7306
+ if (!title)
7307
+ return undefined;
7308
+ return { title, color: sideAxes.length === 1 ? resolveAxis(sideAxes[0]).color : undefined };
7309
+ };
7310
+ return { left: pick("left"), right: pick("right") };
7311
+ }, [axes]);
7312
+ const hasAxisTitles = !!(isLineChart && (axisTitles.left || axisTitles.right));
7313
+ const hasTopTitles = hasAxisTitles && axisTitlePosition === "top";
7314
+ const hasSideTitles = hasAxisTitles && axisTitlePosition === "side";
7315
+ const hideLeftAxis = useMemo(() => axes.filter(related => !isRightAxis(related)).every(related => resolveAxis(related).hide), [axes]);
7316
+ const hideRightAxis = useMemo(() => axes.filter(isRightAxis).every(related => resolveAxis(related).hide), [axes]);
7317
+ return {
7318
+ axisTitles,
7319
+ hasTopTitles,
7320
+ hasSideTitles,
7321
+ axisTitlesHeight: hasTopTitles ? AXIS_TITLE_TOP_HEIGHT : 0,
7322
+ hideLeftAxis,
7323
+ hideRightAxis,
7324
+ };
7325
+ };
7326
+
7054
7327
  const HEIGHT_OFFSET = 20;
7055
7328
  const FILL_OPACITY = 0.28;
7056
7329
  const lineGenerator = line()
@@ -7096,20 +7369,10 @@ function wrap() {
7096
7369
  }
7097
7370
  }
7098
7371
  const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes, defaultColor, fontColor, markers, showMarkers, }) => {
7099
- const { t } = useGlobalContext();
7100
- const { layerInfos, dataSources: fetchedDataSources } = useWidgetContext();
7101
- const strokeColors = relatedAttributes
7102
- .filter(({ chartAxis }) => chartAxis === "y")
7103
- .map(({ axisColor }) => axisColor);
7104
- const yAxis = useMemo(() => relatedAttributes?.find(({ chartAxis }) => chartAxis === "y") ?? relatedAttributes?.[0], [relatedAttributes]);
7105
- const yAxisAttribute = useMemo(() => {
7106
- const layerInfo = getDataSourceLayerInfo({
7107
- layerInfos,
7108
- configDataSource: dataSources?.find(({ name }) => name === yAxis?.dataSourceName),
7109
- fetchedDataSource: getDataSource(yAxis?.dataSourceName, fetchedDataSources),
7110
- });
7111
- return getLayerInfoAttribute(layerInfo, yAxis?.attributeName);
7112
- }, [dataSources, fetchedDataSources, layerInfos, yAxis]);
7372
+ const { strokeColors } = useChartAxisAttributes({
7373
+ dataSources,
7374
+ relatedAttributes,
7375
+ });
7113
7376
  const ref = useRef({
7114
7377
  path: null,
7115
7378
  area: null,
@@ -7162,22 +7425,9 @@ const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes
7162
7425
  svg
7163
7426
  .selectAll(`.${lineChartClassNames.lineChartXScaleGlobal} .tick text`)
7164
7427
  .attr("y", 16);
7165
- svg
7166
- .selectAll(`.${lineChartClassNames.lineChartYScaleGlobal} .tick text`)
7167
- .text(item => {
7168
- if (!item)
7169
- return 0;
7170
- return yAxisAttribute
7171
- ? formatAttributeValue({
7172
- t,
7173
- type: yAxisAttribute.type,
7174
- value: item,
7175
- stringFormat: yAxisAttribute.stringFormat,
7176
- noUnits: true,
7177
- })
7178
- : item;
7179
- })
7180
- .each(wrap);
7428
+ // Текст делений форматируется ещё в генераторе оси (useChartAxisTickFormat), до замера ширины
7429
+ // шкалы; здесь его только укорачиваем.
7430
+ svg.selectAll(`.${lineChartClassNames.lineChartYScaleGlobal} .tick text`).each(wrap);
7181
7431
  svg.selectAll(".tick text").attr("style", `color: ${fontColor}`);
7182
7432
  const global = svg.select(`.${lineChartClassNames.lineChartLinesGlobal}`);
7183
7433
  const lineChartLines = svg.selectAll(`.${lineChartClassNames.lineChartLine}`);
@@ -7213,7 +7463,6 @@ const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes
7213
7463
  svg.append("defs").html(() => defs.join(""));
7214
7464
  }, [
7215
7465
  fontColor,
7216
- yAxisAttribute,
7217
7466
  chartId,
7218
7467
  strokeColors,
7219
7468
  defaultColor,
@@ -7221,7 +7470,6 @@ const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes
7221
7470
  onChange,
7222
7471
  showMarkers,
7223
7472
  markers,
7224
- t,
7225
7473
  ]);
7226
7474
  return [customize, onChange];
7227
7475
  };
@@ -7378,10 +7626,10 @@ const useChartData = ({ element, type }) => {
7378
7626
  const configDataSources = useConfigDataSources(type);
7379
7627
  const relatedAttributes = useMemo(() => element?.options?.relatedDataSources || [], [element?.options?.relatedDataSources]);
7380
7628
  const loading = useMemo(() => !!relatedAttributes?.length &&
7381
- !dataSources?.some(({ name }) => relatedAttributes.some(({ chartAxis, dataSourceName }) => chartAxis === "y" && dataSourceName === name)), [dataSources, relatedAttributes]);
7629
+ !dataSources?.some(({ name }) => relatedAttributes.some(related => isValueAxis(related) && related.dataSourceName === name)), [dataSources, relatedAttributes]);
7382
7630
  const fetchedData = useMemo(() => {
7383
7631
  const isRelated = !!relatedAttributes?.length;
7384
- const filteredAttributes = relatedAttributes.filter(({ chartAxis }) => chartAxis === "y");
7632
+ const filteredAttributes = relatedAttributes.filter(isValueAxis);
7385
7633
  if (!element)
7386
7634
  return [];
7387
7635
  if (isRelated) {
@@ -7389,7 +7637,7 @@ const useChartData = ({ element, type }) => {
7389
7637
  return [];
7390
7638
  return filteredAttributes
7391
7639
  .map(relatedAxis => {
7392
- const { dataSourceName, axisColor, attributeName, attributeUnits } = relatedAxis;
7640
+ const { dataSourceName, attributeName, attributeUnits } = relatedAxis;
7393
7641
  const dataSource = configDataSources.find(({ name }) => name === dataSourceName);
7394
7642
  const featureDataSource = getDataSource(dataSourceName, dataSources);
7395
7643
  const layerInfo = getDataSourceLayerInfo({
@@ -7409,7 +7657,10 @@ const useChartData = ({ element, type }) => {
7409
7657
  layerInfo,
7410
7658
  })
7411
7659
  : null,
7412
- color: axisColor,
7660
+ color: resolveAxis(relatedAxis).color,
7661
+ // Сторона едет вместе с данными: серии без значений отсеиваются ниже, и сопоставлять
7662
+ // их с осями по индексу уже нельзя.
7663
+ axisSide: getAxisSide(relatedAxis),
7413
7664
  attributeName,
7414
7665
  attributeUnits,
7415
7666
  dataSourceName,
@@ -7422,6 +7673,7 @@ const useChartData = ({ element, type }) => {
7422
7673
  return [
7423
7674
  {
7424
7675
  items: getDataFromAttributes(t, element, attributes),
7676
+ axisSide: "left",
7425
7677
  layerInfo: null,
7426
7678
  attributeName: null,
7427
7679
  attributeUnits: null,
@@ -7441,7 +7693,7 @@ const useChartData = ({ element, type }) => {
7441
7693
  ]);
7442
7694
  const isResolved = useMemo(() => !relatedAttributes?.length ||
7443
7695
  relatedAttributes
7444
- .filter(({ chartAxis }) => chartAxis === "y")
7696
+ .filter(isValueAxis)
7445
7697
  .every(({ dataSourceName }) => Array.isArray(getDataSource(dataSourceName, dataSources)?.features)), [relatedAttributes, dataSources]);
7446
7698
  const [data, setData] = useState(fetchedData);
7447
7699
  useEffect(() => {
@@ -8973,6 +9225,15 @@ const ChartLegendContainer = styled(Flex).withConfig({ displayName: "ChartLegend
8973
9225
  flex-direction: column;
8974
9226
  flex-wrap: wrap;
8975
9227
  justify-content: ${({ twoColumns }) => (twoColumns ? "flex-start" : "center")};
9228
+
9229
+ /* Явная опция column перебивает раскладку, которую легенде навязывает ChartContainer
9230
+ (под графиком — в ряд, сбоку — столбиком). Без опции поведение прежнее. */
9231
+ ${({ $column }) => $column !== undefined &&
9232
+ css `
9233
+ &&& {
9234
+ flex-direction: ${$column ? "column" : "row"};
9235
+ }
9236
+ `}
8976
9237
  `;
8977
9238
  const ChartLegendItem = styled(Flex).withConfig({ displayName: "ChartLegendItem", componentId: "sc-16m9piw" }) `
8978
9239
  cursor: ${({ onClick }) => (onClick ? "pointer" : "default")};
@@ -9041,12 +9302,129 @@ const ContainerChart = styled(Flex).withConfig({ displayName: "ContainerChart",
9041
9302
  `}
9042
9303
  `;
9043
9304
  const ContainerLegend = styled(Flex).withConfig({ displayName: "ContainerLegend", componentId: "sc-z1n1s8" }) ``;
9044
- const ContainerUnits = styled.div.withConfig({ displayName: "ContainerUnits", componentId: "sc-1vurgy9" }) `
9305
+ /**
9306
+ * Выравнивание строки по полю графика: ширина тела графика, а внутренние отступы — до краёв поля
9307
+ * (колонка подписи оси и значения шкалы). Тело графика центрируется в слоте, поэтому и строка
9308
+ * центрируется. Правило поднято `&&&` над шириной, которую строкам задаёт `ContainerValue`.
9309
+ */
9310
+ const plotInsetsMixin = css `
9311
+ ${({ $insets }) => $insets &&
9312
+ css `
9313
+ &&& {
9314
+ box-sizing: border-box;
9315
+ align-self: center;
9316
+ width: ${$insets.width}px;
9317
+ max-width: 100%;
9318
+ padding-left: ${$insets.left}px;
9319
+ padding-right: ${$insets.right}px;
9320
+ }
9321
+ `}
9322
+ `;
9323
+ /**
9324
+ * Строка ждёт, пока график сообщит положение поля: место занимает, но не видна. Иначе до первой
9325
+ * отрисовки d3 она стояла бы по краю контейнера и потом прыгала на место.
9326
+ */
9327
+ const awaitingPlotMixin = css `
9328
+ ${({ $awaitingPlot }) => $awaitingPlot &&
9329
+ css `
9330
+ visibility: hidden;
9331
+ `}
9332
+ `;
9333
+ /**
9334
+ * Строка под телом графика: подпись оси X и, при `legendInline`, легенда справа от неё.
9335
+ *
9336
+ * `$offsetTop` подтягивает строку к подписям оси X: под ними внутри SVG остаётся пустая полоса,
9337
+ * и без поправки зазор получился бы втрое больше макетного.
9338
+ */
9339
+ const ChartFooter = styled(Flex).withConfig({ displayName: "ChartFooter", componentId: "sc-nii28w" }) `
9340
+ align-items: center;
9341
+ flex-wrap: nowrap;
9342
+ gap: 0.5rem;
9343
+ width: 100%;
9344
+ margin-top: ${({ $offsetTop }) => $offsetTop ?? 0}px;
9345
+ justify-content: ${({ $align }) => $align};
9346
+
9347
+ ${ContainerLegend} {
9348
+ justify-content: flex-end;
9349
+ margin-left: auto;
9350
+ }
9351
+
9352
+ /* Легенда в строке с подписью: записи прижаты вправо, без отступа сверху, который ей задаёт
9353
+ ContainerValue, и без нижнего отступа записей — иначе она съедет вниз относительно подписи.
9354
+ Прижимать нужно сами записи: обёртки легенды — Flex из uilib-gl с width: 100%, и выравнивание
9355
+ внешнего контейнера до записей не доходит, а своё у легенды — по центру. */
9356
+ &&& ${ChartLegendContainer} {
9357
+ justify-content: flex-end;
9358
+ margin-top: 0;
9359
+ }
9360
+
9361
+ &&& ${ChartLegendItem} {
9362
+ margin-bottom: 0;
9363
+ }
9364
+
9365
+ ${plotInsetsMixin};
9366
+ ${awaitingPlotMixin};
9367
+ `;
9368
+ const ChartXAxisTitle = styled.div.withConfig({ displayName: "ChartXAxisTitle", componentId: "sc-1vsgzi9" }) `
9369
+ font-size: 0.625rem;
9370
+ line-height: 0.75rem;
9371
+ white-space: nowrap;
9372
+ color: ${({ $color, theme: { palette } }) => $color || palette.textDisabled};
9373
+ `;
9374
+ /**
9375
+ * Строка легенды под графиком. `$gapTop` заменяет отступ сверху, который легенде задаёт `ContainerValue`;
9376
+ * `$offsetTop` сдвигает саму строку — подтягивает её к подписям оси X, как строку с подписью оси.
9377
+ *
9378
+ * `$justify` выравнивает сами записи. Выравнивание строки (`justifyContent`) до них не доходит: обёртки
9379
+ * легенды — Flex из uilib-gl с `width: 100%`, а записи внутри легенда всегда центрирует.
9380
+ */
9381
+ const ChartLegendRow = styled(ContainerLegend).withConfig({ displayName: "ChartLegendRow", componentId: "sc-1a12wrz" }) `
9382
+ ${({ $offsetTop }) => $offsetTop !== undefined &&
9383
+ css `
9384
+ margin-top: ${$offsetTop}px;
9385
+ `}
9386
+
9387
+ ${({ $gapTop }) => $gapTop !== undefined &&
9388
+ css `
9389
+ &&& ${ChartLegendContainer} {
9390
+ margin-top: ${$gapTop}px;
9391
+ }
9392
+ `}
9393
+
9394
+ ${({ $justify }) => $justify &&
9395
+ css `
9396
+ &&& ${ChartLegendContainer} {
9397
+ justify-content: ${$justify};
9398
+ }
9399
+ `}
9400
+
9401
+ ${plotInsetsMixin};
9402
+ ${awaitingPlotMixin};
9403
+ `;
9404
+ /**
9405
+ * Колонка «график + подпись оси X» для легенды сбоку (`twoColumns`). Без неё подпись встала бы третьим
9406
+ * элементом ряда рядом с легендой, а не под графиком.
9407
+ */
9408
+ const ChartWithFooter = styled(Flex).withConfig({ displayName: "ChartWithFooter", componentId: "sc-140gv2s" }) `
9409
+ flex-direction: column;
9410
+ flex-wrap: nowrap;
9411
+ min-width: 0;
9412
+
9413
+ ${({ $fill }) => $fill &&
9414
+ css `
9415
+ min-height: 0;
9416
+ `}
9417
+
9418
+ &&& ${ContainerChart} {
9419
+ width: 100%;
9420
+ }
9421
+ `;
9422
+ const ContainerUnits = styled.div.withConfig({ displayName: "ContainerUnits", componentId: "sc-vj15z6" }) `
9045
9423
  margin-left: 0.5rem;
9046
9424
  white-space: nowrap;
9047
9425
  font-size: 0.75rem;
9048
9426
  `;
9049
- const ContainerValue = styled(Flex).withConfig({ displayName: "ContainerValue", componentId: "sc-aj1rs9" }) `
9427
+ const ContainerValue = styled(Flex).withConfig({ displayName: "ContainerValue", componentId: "sc-1bnfj5v" }) `
9050
9428
  justify-content: flex-end;
9051
9429
  align-items: center;
9052
9430
  flex-wrap: nowrap;
@@ -9065,7 +9443,7 @@ const ContainerValue = styled(Flex).withConfig({ displayName: "ContainerValue",
9065
9443
  width: ${({ column }) => (column ? "100%" : "auto")};
9066
9444
  }
9067
9445
 
9068
- ${ContainerChart}, ${ContainerLegend} {
9446
+ ${ContainerChart}, ${ContainerLegend}, ${ChartWithFooter} {
9069
9447
  width: ${({ column }) => (column ? "100%" : "50%")};
9070
9448
  }
9071
9449
 
@@ -9088,7 +9466,7 @@ const SizeIconMixin = css `
9088
9466
  font-size: ${({ $fontSize }) => $fontSize}px !important;
9089
9467
  }
9090
9468
  `;
9091
- styled(Icon).withConfig({ displayName: "ContainerIcon", componentId: "sc-1w9w0bq" }) `
9469
+ styled(Icon).withConfig({ displayName: "ContainerIcon", componentId: "sc-18399bz" }) `
9092
9470
  width: auto;
9093
9471
  height: auto;
9094
9472
  margin-bottom: 0.5rem;
@@ -9102,7 +9480,7 @@ const SvgContainerColorMixin$1 = css `
9102
9480
  fill: ${({ $fontColor }) => $fontColor};
9103
9481
  }
9104
9482
  `;
9105
- const SvgContainer$1 = styled.div.withConfig({ displayName: "SvgContainer", componentId: "sc-1qs5gmx" }) `
9483
+ const SvgContainer$1 = styled.div.withConfig({ displayName: "SvgContainer", componentId: "sc-8l0ajh" }) `
9106
9484
  &&& {
9107
9485
  min-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
9108
9486
  max-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
@@ -9114,7 +9492,7 @@ const SvgContainer$1 = styled.div.withConfig({ displayName: "SvgContainer", comp
9114
9492
  }
9115
9493
  }
9116
9494
  `;
9117
- const TwoColumnContainerWrapper = styled(Container).withConfig({ displayName: "TwoColumnContainerWrapper", componentId: "sc-1ogv4d3" }) `
9495
+ const TwoColumnContainerWrapper = styled(Container).withConfig({ displayName: "TwoColumnContainerWrapper", componentId: "sc-1yxeknf" }) `
9118
9496
  flex-direction: row;
9119
9497
  flex-wrap: nowrap;
9120
9498
  align-items: center;
@@ -9141,6 +9519,16 @@ const CameraContainer = memo(({ elementConfig, type, renderElement }) => {
9141
9519
  return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxs(Container, { ...body, children: [jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsx(ContainerValue, { children: renderElement({ id: "value", wrap: false }) })] }))] }));
9142
9520
  });
9143
9521
 
9522
+ /**
9523
+ * Контекст, через который элемент `chart` сообщает контейнеру, где лежит поле графика.
9524
+ *
9525
+ * По макету подпись оси X и легенда выравниваются по полю графика, а не по краям контейнера.
9526
+ * Ширину шкал d3 вычисляет только при отрисовке, а подвал с подписью и легендой рисует
9527
+ * `ChartContainer` — снаружи элемента. Без контейнера (элемент сам по себе) провайдера нет,
9528
+ * и отчёт просто не отправляется.
9529
+ */
9530
+ const ChartPlotContext = createContext({});
9531
+
9144
9532
  /**
9145
9533
  * Контекст вписывания графика (`options.fill` контейнера Chart).
9146
9534
  *
@@ -9150,14 +9538,43 @@ const CameraContainer = memo(({ elementConfig, type, renderElement }) => {
9150
9538
  */
9151
9539
  const FillContext = createContext({ fill: false, fitHeight: false });
9152
9540
 
9541
+ const isSameInsets = (prev, next) => !!prev && prev.left === next.left && prev.right === next.right && prev.width === next.width;
9153
9542
  const ChartContainer = memo(({ elementConfig, isVisible, type, renderElement }) => {
9154
9543
  const { options, children } = elementConfig || {};
9155
- const { twoColumns, hideEmpty, height } = options || {};
9544
+ const { twoColumns, hideEmpty, height, legendInline } = options || {};
9156
9545
  const fill = options?.fill;
9157
9546
  const fillContextValue = useMemo(() => ({ fill: !!fill, fitHeight: !!fill && height != null }), [fill, height]);
9547
+ // Поле графика отчитывается после каждой отрисовки d3; одинаковые значения не пишем в стейт,
9548
+ // иначе отчёт и ре-рендер гоняли бы друг друга.
9549
+ const [plotInsets, setPlotInsets] = useState(null);
9550
+ const reportPlotInsets = useCallback((next) => setPlotInsets(prev => (isSameInsets(prev, next) ? prev : next)), []);
9551
+ const plotContextValue = useMemo(() => ({ reportPlotInsets }), [reportPlotInsets]);
9158
9552
  const aliasElement = children.find(child => child.id === "alias");
9159
9553
  const chartElement = children.find(child => child.id === "chart");
9160
9554
  const legendElement = children.find(child => child.id === "legend");
9555
+ const isLineChart = chartElement?.options?.chartType === "line";
9556
+ // Подпись оси X — часть макета осей линейного графика, как и подписи осей Y в `Chart`. У других типов
9557
+ // её нет, даже если она осталась в конфиге после смены типа: редактор показывает настройки осей только
9558
+ // линейному графику, и убрать там такую подпись было бы нечем.
9559
+ const xAxisTitle = useMemo(() => (isLineChart ? chartElement?.options?.axis?.x?.title : undefined), [isLineChart, chartElement?.options?.axis?.x?.title]);
9560
+ const footerJustify = useMemo(() => CHART_AXIS_ALIGN_JUSTIFY[chartElement?.options?.axis?.x?.align ?? "center"], [chartElement?.options?.axis?.x?.align]);
9561
+ // Легенда встаёт в строку с подписью оси X только когда та есть и легенда не вынесена вбок.
9562
+ const isLegendInline = useMemo(() => !!legendInline && !twoColumns && !!xAxisTitle, [legendInline, twoColumns, xAxisTitle]);
9563
+ // По макету подпись оси X и легенда у линейного графика выравниваются по полю графика, а не по
9564
+ // контейнеру. Легенда сбоку (twoColumns) живёт в своей колонке и в выравнивании не участвует.
9565
+ const footerInsets = useMemo(() => (isLineChart ? plotInsets : null), [isLineChart, plotInsets]);
9566
+ const isPlotLegend = isLineChart && !twoColumns;
9567
+ // Положение поля становится известно только после отрисовки: LineChart рисует d3 в useEffect, уже
9568
+ // после первого кадра. До отчёта подвал встал бы по краю контейнера и затем прыгнул на место, поэтому
9569
+ // прячем его — место под ним сохраняется, а появляется он вместе с линиями графика.
9570
+ const isAwaitingPlot = isLineChart && !plotInsets;
9571
+ // Пока подпись оси X не задана, легенда выравнивается прежней опцией `center` (по умолчанию — к левой
9572
+ // границе поля графика); с подписью — по ней, иначе подпись и легенда разъезжаются по разным краям.
9573
+ const legendJustify = useMemo(() => {
9574
+ if (xAxisTitle)
9575
+ return footerJustify;
9576
+ return legendElement?.options?.center ? "center" : "flex-start";
9577
+ }, [xAxisTitle, footerJustify, legendElement?.options?.center]);
9161
9578
  const { root, body } = useContainerRoot({ elementConfig });
9162
9579
  const { data, loading } = useChartData({
9163
9580
  element: chartElement,
@@ -9169,7 +9586,12 @@ const ChartContainer = memo(({ elementConfig, isVisible, type, renderElement })
9169
9586
  const hasItems = !!data[0]?.items?.length;
9170
9587
  if (!loading && !hasItems && hideEmpty)
9171
9588
  return null;
9172
- return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxs(Container, { ...body, isColumn: true, children: [aliasElement && jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsx(ContainerValue, { column: !twoColumns, alignItems: twoColumns && fill ? "stretch" : "center", "$fill": !!fill, children: hasItems ? (jsxs(Fragment$1, { children: [jsx(ContainerChart, { "$fill": !!fill, children: jsx(FillContext.Provider, { value: fillContextValue, children: renderElement({ id: "chart" }) }) }), jsx(ContainerLegend, { justifyContent: legendElement?.options?.center ? "center" : "flex-start", children: renderElement({ id: "legend" }) })] })) : (jsx(Fragment$1, { children: "\u2014" })) })] }))] }));
9589
+ const chartSlot = (jsx(ContainerChart, { "$fill": !!fill, children: jsx(FillContext.Provider, { value: fillContextValue, children: jsx(ChartPlotContext.Provider, { value: plotContextValue, children: renderElement({ id: "chart" }) }) }) }));
9590
+ const footer = xAxisTitle ? (jsxs(ChartFooter, { "$align": footerJustify, "$insets": footerInsets, "$offsetTop": X_AXIS_TITLE_GAP - LINE_CHART_X_LABELS_BLANK, "$awaitingPlot": isAwaitingPlot, children: [jsx(ChartXAxisTitle, { children: xAxisTitle }), isLegendInline && jsx(ContainerLegend, { children: renderElement({ id: "legend" }) })] })) : null;
9591
+ // Легенда линейного графика: под подписью оси X — через X_AXIS_TITLE_LEGEND_GAP, без подписи — на её
9592
+ // месте, вплотную под подписями делений оси X. У остальных графиков — прежняя раскладка общей легенды.
9593
+ const legendRow = isLegendInline ? null : (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" }) }));
9594
+ return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxs(Container, { ...body, isColumn: true, children: [aliasElement && jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsx(ContainerValue, { column: !twoColumns, alignItems: twoColumns && fill ? "stretch" : "center", "$fill": !!fill, children: hasItems ? (jsxs(Fragment$1, { children: [twoColumns && footer ? (jsxs(ChartWithFooter, { "$fill": !!fill, children: [chartSlot, footer] })) : (jsxs(Fragment$1, { children: [chartSlot, footer] })), legendRow] })) : (jsx(Fragment$1, { children: "\u2014" })) })] }))] }));
9173
9595
  });
9174
9596
 
9175
9597
  const InnerContainerWrapper = styled.div.withConfig({ displayName: "InnerContainerWrapper", componentId: "sc-1a1fl7q" }) `
@@ -9527,6 +9949,7 @@ const DEFAULT_BAR_CHART_HEIGHT = 100;
9527
9949
  const BAR_CHART_HEADER_HEIGHT = 40;
9528
9950
  const BAR_CHART_FOOTER_MARGIN = 16;
9529
9951
  const LINE_CHART_LABEL_MARGIN = 20;
9952
+ const LINE_CHART_X_AXIS_PADDING = 15;
9530
9953
  const DEFAULT_BAR_CHART_MARGINS = {
9531
9954
  top: 0,
9532
9955
  left: 0,
@@ -10925,6 +11348,13 @@ const StyledSvg = styled(Flex).withConfig({ displayName: "StyledSvg", componentI
10925
11348
  ${({ $fontColor }) => !!$fontColor && StyledSvgColorMixin};
10926
11349
  `;
10927
11350
 
11351
+ const AttachmentIconImg = styled.img.withConfig({ displayName: "AttachmentIconImg", componentId: "sc-1qtgzdf" }) `
11352
+ flex-shrink: 0;
11353
+ width: ${({ $size }) => $size}px;
11354
+ height: ${({ $size }) => $size}px;
11355
+ object-fit: contain;
11356
+ `;
11357
+
10928
11358
  const ContainerIconValue = styled(Flex).withConfig({ displayName: "ContainerIconValue", componentId: "sc-1adlcb4" }) ``;
10929
11359
  /**
10930
11360
  * Обёртка иконки плитки. Собственный styled нужен, чтобы до иконки можно было дотянуться стилями:
@@ -11029,6 +11459,12 @@ const RoundedBackgroundContainerWrapper = styled(Flex).withConfig({ displayName:
11029
11459
  width: ${BIG_ICON_SIZE} !important;
11030
11460
  height: ${BIG_ICON_SIZE} !important;
11031
11461
  }
11462
+
11463
+ /* Растровая иконка из атрибута-вложения: её размер тоже задаёт контейнер. */
11464
+ ${AttachmentIconImg} {
11465
+ width: ${BIG_ICON_SIZE};
11466
+ height: ${BIG_ICON_SIZE};
11467
+ }
11032
11468
  }
11033
11469
  `};
11034
11470
 
@@ -15126,14 +15562,55 @@ const StyledIcon = styled(Icon).withConfig({ displayName: "StyledIcon", componen
15126
15562
  ${({ fontColor }) => !!fontColor && StyledIconFontColorMixin};
15127
15563
  `;
15128
15564
 
15565
+ /** Размер иконки-вложения, px, когда в конфиге не задан `options.fontSize`. */
15566
+ const ATTACHMENT_ICON_DEFAULT_SIZE = 16;
15567
+
15568
+ /**
15569
+ * Иконка из значения атрибута-вложения: первое изображение списка, остальные файлы пропускаются.
15570
+ * Значение приходит JSON-строкой с сервера или массивом после правки — `parseAttachments` понимает оба.
15571
+ * SVG помечается отдельно: его рисуют инлайном, чтобы перекрасить цветом иконки.
15572
+ */
15573
+ const getAttachmentIcon = (value) => {
15574
+ const image = parseAttachments(value).find(({ mimeType, name }) => IMAGE_FILE_TYPES.includes(getFileType(mimeType, name)));
15575
+ if (!image)
15576
+ return null;
15577
+ return {
15578
+ url: getResourceUrl(image.link),
15579
+ name: image.name,
15580
+ isSvg: image.mimeType === SVG_MIME_TYPE,
15581
+ };
15582
+ };
15583
+
15584
+ /**
15585
+ * Иконка из атрибута-вложения. SVG рисуется инлайном и перекрашивается `fontColor`, как у ElementSvg;
15586
+ * растровый файл — картинкой как есть. Изображений среди вложений нет — не рендерится ничего.
15587
+ */
15588
+ const AttachmentIcon = memo(({ value, fontSize, fontColor, style }) => {
15589
+ const icon = useMemo(() => getAttachmentIcon(value), [value]);
15590
+ const size = useMemo(() => Number(fontSize) || ATTACHMENT_ICON_DEFAULT_SIZE, [fontSize]);
15591
+ const rasterUrl = useMemo(() => (icon && !icon.isSvg ? icon.url : null), [icon]);
15592
+ const imageSrc = useFetchImageWithAuth(rasterUrl);
15593
+ if (!icon)
15594
+ return null;
15595
+ if (icon.isSvg) {
15596
+ return jsx(SvgImage, { url: icon.url, width: size, height: size, fontColor: fontColor });
15597
+ }
15598
+ if (!imageSrc)
15599
+ return null;
15600
+ return jsx(AttachmentIconImg, { src: imageSrc, alt: icon.name, style: style, "$size": size });
15601
+ });
15602
+
15129
15603
  const ElementIcon = memo(({ type, elementConfig, ...rest }) => {
15130
15604
  const ctx = useWidgetContext(type);
15131
15605
  const attributes = rest.attributes || ctx.attributes;
15132
15606
  const { value, attributeName, options, style } = elementConfig || {};
15133
15607
  const { fontSize, fontColor } = options || {};
15134
- const iconValue = useMemo(() => attributeName
15135
- ? attributes?.find(item => item.attributeName === attributeName)?.value
15136
- : value, [attributeName, attributes, value]);
15608
+ const attribute = useMemo(() => (attributeName ? getAttributeByName(attributeName, attributes) : undefined), [attributeName, attributes]);
15609
+ const iconValue = useMemo(() => (attributeName ? attribute?.value : value), [attributeName, attribute, value]);
15610
+ // Во вложениях лежит список файлов, а не имя глифа: иконкой становится первое изображение.
15611
+ if (attribute?.subType === StringSubType.Attachments) {
15612
+ return jsx(AttachmentIcon, { value: attribute.value, fontSize: fontSize, fontColor: fontColor, style: style });
15613
+ }
15137
15614
  return (jsx(StyledIcon, { kind: iconValue, fontSize: fontSize, fontColor: fontColor, style: style }));
15138
15615
  });
15139
15616
 
@@ -15190,9 +15667,9 @@ const ElementImage = memo(({ type, elementConfig }) => {
15190
15667
  });
15191
15668
 
15192
15669
  const ElementLegend = memo(({ type, element, elementConfig }) => {
15193
- const { attributes, dataSources, expandedContainers } = useWidgetContext(type);
15670
+ const { attributes, expandedContainers } = useWidgetContext(type);
15194
15671
  const { options } = elementConfig || {};
15195
- const { twoColumns, chartId, relatedDataSources, fontSize } = options || {};
15672
+ const { twoColumns, column, chartId, relatedDataSources, fontSize } = options || {};
15196
15673
  const chartElement = useMemo(() => returnFound(element?.children, { id: chartId }), [chartId, element?.children]);
15197
15674
  const { data, loading } = useChartData({
15198
15675
  element: chartElement,
@@ -15203,10 +15680,10 @@ const ElementLegend = memo(({ type, element, elementConfig }) => {
15203
15680
  if (!data[0]?.items?.length)
15204
15681
  return [];
15205
15682
  if (chartElement?.options?.chartType === "line") {
15206
- return axes.map(({ alias, axisColor }) => {
15683
+ return axes.map(related => {
15207
15684
  return {
15208
- name: alias,
15209
- color: axisColor,
15685
+ name: related.alias,
15686
+ color: resolveAxis(related).color,
15210
15687
  };
15211
15688
  });
15212
15689
  }
@@ -15220,7 +15697,7 @@ const ElementLegend = memo(({ type, element, elementConfig }) => {
15220
15697
  });
15221
15698
  }, [data, chartElement?.options?.chartType, relatedDataSources, axes, attributes]);
15222
15699
  return !chartElement?.options?.expanded ||
15223
- expandedContainers?.[chartElement.id] ? (jsx(ChartLegend, { data: legendData, loading: loading, chartElement: chartElement, twoColumns: twoColumns, fontSize: fontSize, type: type })) : null;
15700
+ expandedContainers?.[chartElement.id] ? (jsx(ChartLegend, { data: legendData, loading: loading, chartElement: chartElement, twoColumns: twoColumns, column: column, fontSize: fontSize, type: type })) : null;
15224
15701
  });
15225
15702
 
15226
15703
  const ExternalLink = styled(IconButton).attrs(() => ({
@@ -16788,7 +17265,11 @@ const getDataFromRelatedFeatures = ({ t, config, filters, relatedConfig, dataSou
16788
17265
  if (isOtherSliced) {
16789
17266
  data = data.slice(0, config?.options?.otherItems);
16790
17267
  }
16791
- const gradientArray = relatedConfig.chartAxis
17268
+ // Источник чарта отличается от источника фильтра наличием осей: BarChartFilter собирает
17269
+ // relatedConfig вручную и оси не задаёт, потому что ему нужна исходная палитра, а не растянутая
17270
+ // под число элементов. Гейт смотрит и на устаревший chartAxis — иначе у старых конфигов
17271
+ // молча поехали бы цвета столбцов и долей.
17272
+ const gradientArray = hasAxisConfig(relatedConfig)
16792
17273
  ? stretchPalette(colors, data.length)
16793
17274
  : colors;
16794
17275
  const filter = getConfigFilter(relatedConfig?.filterName, filters);
@@ -18854,6 +19335,15 @@ const StackBar = ({ data, filterName, type, alias, options, fill, renderElement,
18854
19335
  return (jsxs(Fragment$1, { children: [(alias || showTotal) && (jsxs(StackBarHeader, { children: [jsx(StackBarAlias, { children: renderElement({ id: "alias" }) }), showTotal && (jsxs(StackBarTotal, { children: [jsx(StackBarValue, { children: formatValue(total) }), !!units && jsx(StackBarUnits, { children: units })] }))] })), groupTooltip ? (jsx(Tooltip$1, { placement: "top", arrow: true, content: renderGroupTooltip, children: ref => (jsx(StackBarContainer, { ref: ref, "$fill": fill, children: renderItems })) })) : (jsx(StackBarContainer, { "$fill": fill, children: renderItems }))] }));
18855
19336
  };
18856
19337
 
19338
+ /** Горизонтальные подписи осей над телом графика: левая прижата влево, правая — вправо. */
19339
+ const AxisTitlesTop = ({ left, right, neutralColor }) => (jsxs(AxisTitlesRow, { children: [jsx(AxisTitleTop, { "$color": left?.color || neutralColor, title: left?.title, children: left?.title }), jsx(AxisTitleTop, { "$color": right?.color || neutralColor, title: right?.title, children: right?.title })] }));
19340
+ /** Вертикальная подпись оси сбоку от тела графика. Без подписи ничего не рендерит. */
19341
+ const AxisTitleSideSlot = ({ side, axisTitle, neutralColor, plotTop, plotBottom, }) => {
19342
+ if (!axisTitle)
19343
+ return null;
19344
+ return (jsx(AxisTitleSideColumn, { "$side": side, "$plotTop": plotTop, "$plotBottom": plotBottom, children: jsx(AxisTitleSide, { "$side": side, "$color": axisTitle.color || neutralColor, title: axisTitle.title, children: axisTitle.title }) }));
19345
+ };
19346
+
18857
19347
  const Chart = memo(({ config, element, elementConfig, type, renderElement }) => {
18858
19348
  const theme = useTheme();
18859
19349
  const { palette } = theme;
@@ -18874,12 +19364,18 @@ const Chart = memo(({ config, element, elementConfig, type, renderElement }) =>
18874
19364
  const radius = options?.radius || DEFAULT_BARCHART_RADIUS;
18875
19365
  const padding = options?.padding || DEFAULT_BAR_PADDING;
18876
19366
  const fontColor = options?.fontColor || palette.iconDisabled;
19367
+ // Нейтральная подпись оси — тем же цветом, что и значения шкалы (на макете у них один стиль).
19368
+ const axisTitleColor = options?.fontColor || palette.textDisabled;
18877
19369
  const angle = options?.angle || DEFAULT_CHART_ANGLE;
18878
19370
  const barWidth = options?.barWidth || DEFAULT_BAR_WIDTH;
18879
19371
  const cornerRadius = options?.cornerRadius || 0;
18880
19372
  const relatedAttributes = useMemo(() => relatedDataSources || [], [relatedDataSources]);
18881
- const axes = useMemo(() => relatedAttributes.filter(({ chartAxis }) => chartAxis === "y"), [relatedAttributes]);
19373
+ const axes = useMemo(() => relatedAttributes.filter(isValueAxis), [relatedAttributes]);
18882
19374
  const filterName = getChartFilterName(relatedDataSources);
19375
+ const { axisTitles, hasTopTitles, hasSideTitles, axisTitlesHeight, hideLeftAxis, hideRightAxis, } = useChartAxisTitles({ options, axes, isLineChart });
19376
+ // Ширина вертикальных подписей зависит от числа строк текста, поэтому тело между ними меряем
19377
+ // по факту, а не вычитаем константу.
19378
+ const [lineBodyRef, { width: measuredBodyWidth }] = useResizeBox(hasSideTitles);
18883
19379
  const isRelated = !!relatedAttributes?.length;
18884
19380
  const { data, loading } = useChartData({ element, type });
18885
19381
  const totalWidthFromBarWidth = (data[0]?.items?.length || 0) * (barWidth + padding);
@@ -18907,17 +19403,54 @@ const Chart = memo(({ config, element, elementConfig, type, renderElement }) =>
18907
19403
  const chartHeight = useMemo(() => (fitsHeight && measuredHeight ? measuredHeight : height), [fitsHeight, measuredHeight, height]);
18908
19404
  // Круглый график вписываем по короткой стороне (object-fit: contain), центрируя в ячейке.
18909
19405
  const pieSide = useMemo(() => (fitsHeight && measuredHeight ? Math.min(chartWidth, measuredHeight) : chartWidth), [fitsHeight, measuredHeight, chartWidth]);
19406
+ // Тело линейного графика — это ячейка за вычетом подписей осей. По высоте вычитаем только в
19407
+ // fill: вне fill chartHeight означает высоту самого тела, и обёртка честно растёт на подписи.
19408
+ const lineBodyWidth = useMemo(() => {
19409
+ if (!hasSideTitles)
19410
+ return chartWidth;
19411
+ if (measuredBodyWidth)
19412
+ return measuredBodyWidth;
19413
+ // До первого замера — оценка: одна строка текста и зазор на каждую подпись.
19414
+ const sideTitlesCount = Number(!!axisTitles.left) + Number(!!axisTitles.right);
19415
+ return Math.max(chartWidth - sideTitlesCount * (AXIS_TITLE_LINE_HEIGHT + AXIS_TITLE_GAP), 0);
19416
+ }, [hasSideTitles, measuredBodyWidth, chartWidth, axisTitles]);
19417
+ const lineBodyHeight = useMemo(() => (fitsHeight ? Math.max(chartHeight - axisTitlesHeight, 0) : chartHeight), [fitsHeight, chartHeight, axisTitlesHeight]);
19418
+ // Обёртка выше тела на строку горизонтальных подписей. В fill вся высота уже отмерена ячейкой,
19419
+ // поэтому прибавлять нечего — иначе тело вылезло бы за нижнюю границу.
19420
+ const lineWrapperHeight = useMemo(() => (fitsHeight ? chartHeight : chartHeight + axisTitlesHeight), [fitsHeight, chartHeight, axisTitlesHeight]);
18910
19421
  const [customize] = useChartChange({
18911
19422
  dataSources: config.dataSources,
18912
19423
  chartId: elementConfig?.id,
18913
- width: chartWidth,
18914
- height: chartHeight,
19424
+ width: lineBodyWidth,
19425
+ height: lineBodyHeight,
18915
19426
  fontColor,
18916
19427
  relatedAttributes,
18917
19428
  defaultColor: primaryColor,
18918
19429
  markers: configMarkers,
18919
19430
  showMarkers,
18920
19431
  });
19432
+ const { formatLeftTick, formatRightTick } = useChartAxisTickFormat({
19433
+ dataSources: config.dataSources,
19434
+ relatedAttributes,
19435
+ });
19436
+ const { reportPlotInsets } = useContext(ChartPlotContext);
19437
+ // Положение поля графика известно только после отрисовки: ширину шкал вычисляет d3. Смещение тела
19438
+ // берём из offset-размеров, а не из getBoundingClientRect: они не искажаются масштабом превью и
19439
+ // transform предков, а координаты шкалы всегда в пикселях SVG.
19440
+ const customizeLine = useCallback(args => {
19441
+ customize?.(args);
19442
+ const body = lineBodyRef.current;
19443
+ const layout = body?.parentElement?.parentElement;
19444
+ if (!reportPlotInsets || !body || !layout)
19445
+ return;
19446
+ const bodyLeft = body.offsetLeft - layout.offsetLeft;
19447
+ const [plotLeft, plotRight] = args.xScale.range();
19448
+ reportPlotInsets({
19449
+ left: Math.round(bodyLeft + plotLeft),
19450
+ right: Math.round(layout.offsetWidth - bodyLeft - plotRight),
19451
+ width: layout.offsetWidth,
19452
+ });
19453
+ }, [customize, reportPlotInsets, lineBodyRef]);
18921
19454
  const { formatFilterColor, onFilter } = useWidgetFilters(type, filterName, data?.[0]?.items);
18922
19455
  const formattedAttributes = useMemo(() => getFormattedAttributes(t, (data[0]?.items || []), attributes, element), [attributes, data, element, t]);
18923
19456
  const totalSum = useMemo(() => isRelated
@@ -18976,6 +19509,17 @@ const Chart = memo(({ config, element, elementConfig, type, renderElement }) =>
18976
19509
  left: markersMargin,
18977
19510
  };
18978
19511
  }, [showMarkers, markers?.length]);
19512
+ // Значения шкалы барчарта d3 центрирует на делении, и крайние деления наполовину выступают за поле.
19513
+ // При нулевом поле SVG выступ уходит за SVG, где его срезает BarChartContainer (overflow-x: hidden,
19514
+ // отчего и по вертикали содержимое не visible). Место под выступ резервируем внутри SVG — внешние
19515
+ // размеры обёрток не меняются. Без значений шкалы (showLabels) выступать нечему.
19516
+ const barMargin = useMemo(() => showLabels
19517
+ ? {
19518
+ ...margin,
19519
+ top: Math.max(margin.top, Y_AXIS_LABEL_OVERHANG),
19520
+ bottom: Math.max(margin.bottom, Y_AXIS_LABEL_OVERHANG),
19521
+ }
19522
+ : margin, [showLabels, margin]);
18979
19523
  const formatTooltipName = useCallback((name) => isRelated ? name : tooltipNameFromAttributes(name, formattedAttributes), [formattedAttributes, isRelated]);
18980
19524
  const formatTooltipValue = useCallback((value, name) => isRelated
18981
19525
  ? tooltipValueFromRelatedFeatures(t, value, relatedAttributes, data[0]?.layerInfo)
@@ -19002,26 +19546,42 @@ const Chart = memo(({ config, element, elementConfig, type, renderElement }) =>
19002
19546
  attributeUnits: item.attributeUnits,
19003
19547
  dataSourceName: item.dataSourceName,
19004
19548
  stroke: item.color || primaryColor,
19549
+ axis: item.axisSide ?? "left",
19005
19550
  values: item.items
19006
19551
  ?.filter(({ value }) => !isEmptyValue(value))
19007
19552
  ?.map(({ value }) => Number(value)),
19008
19553
  }));
19009
- const isHidedY = axes.every(({ hideAxis }) => hideAxis);
19010
- const chartValues = lineChartData
19011
- .flat()
19554
+ const isHidedY = hideLeftAxis && hideRightAxis;
19555
+ const valuesOfSide = (side) => lineChartData
19556
+ .filter(({ axis }) => axis === side)
19012
19557
  .map(({ values }) => values)
19013
19558
  .flat();
19559
+ const leftValues = valuesOfSide("left");
19560
+ const rightValues = valuesOfSide("right");
19014
19561
  // Резервируем нижнюю полосу под подписи оси X: без неё ось прижата к нижней границе,
19015
19562
  // подписи (сдвинутые на +16px) вылезают за тело и режутся overflow: hidden у ChartFillMeasure.
19563
+ // Сверху резервируем выступ верхнего деления шкалы: оно центрировано на линии сетки и при нулевом
19564
+ // поле наполовину уходит за SVG, где в fill его срезает overflow: hidden у ChartFillMeasure.
19565
+ const topOverhang = Math.max(Y_AXIS_LABEL_OVERHANG - margin.top, 0);
19016
19566
  const lineMargin = {
19017
19567
  ...margin,
19568
+ top: margin.top + topOverhang,
19018
19569
  bottom: Math.max(margin.bottom, LINE_CHART_LABEL_MARGIN),
19019
19570
  };
19020
- return (jsxs(AnyChartWrapper, { height: chartHeight, children: [jsx(LineChartStyles, {}), jsx(LineChart, { data: lineChartData, labels: labels, markers: markers, width: chartWidth, height: chartHeight, xAxisPadding: 15, yAxisPadding: 0, min: Math.min(...chartValues), customYAxisSelection: yAxis => {
19021
- if (isHidedY) {
19022
- yAxis.remove();
19023
- }
19024
- }, customYAxis: yAxis => yAxis.ticks(4), renderTooltip: renderLineChartTooltip, customize: customize, dotSnapping: dotSnapping, dynamicTooltipEnable: true, stackedTooltip: true, tooltipClassName: "dashboardLineChartTooltip", drawGridX: !isHidedY, margin: lineMargin })] }));
19571
+ // draw.ts вычитает верхнее поле и из нижней границы поля графика — компенсируем добавленное,
19572
+ // чтобы низ поля и зазор до подписей оси X остались на месте.
19573
+ const lineXAxisPadding = LINE_CHART_X_AXIS_PADDING - topOverhang;
19574
+ // Колонки вертикальных подписей повторяют поля SVG, чтобы подпись центрировалась по полю
19575
+ // графика, а не по всей высоте вместе с подписями оси X.
19576
+ const plotTop = lineMargin.top;
19577
+ const plotBottom = lineMargin.top + lineMargin.bottom + lineXAxisPadding;
19578
+ return (jsxs(AnyChartWrapper, { height: lineWrapperHeight, children: [jsx(LineChartStyles, {}), jsxs(LineChartLayout, { children: [hasTopTitles && (jsx(AxisTitlesTop, { left: axisTitles.left, right: axisTitles.right, neutralColor: axisTitleColor })), jsxs(LineChartRow, { children: [hasSideTitles && (jsx(AxisTitleSideSlot, { side: "left", axisTitle: axisTitles.left, neutralColor: axisTitleColor, plotTop: plotTop, plotBottom: plotBottom })), jsx(LineChartBody, { ref: lineBodyRef, children: jsx(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 => {
19579
+ // Обе оси приходят в один колбэк, поэтому сторону определяем по классу.
19580
+ const isRight = yAxis.classed(lineChartClassNames.lineChartYScaleRight);
19581
+ if (isRight ? hideRightAxis : hideLeftAxis) {
19582
+ yAxis.remove();
19583
+ }
19584
+ }, 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 && (jsx(AxisTitleSideSlot, { side: "right", axisTitle: axisTitles.right, neutralColor: axisTitleColor, plotTop: plotTop, plotBottom: plotBottom }))] })] })] }));
19025
19585
  }
19026
19586
  if (isStackBar) {
19027
19587
  // Вне fill chartHeight — высота самой полосы, и обёртка честно становится выше на строку
@@ -19051,7 +19611,7 @@ const Chart = memo(({ config, element, elementConfig, type, renderElement }) =>
19051
19611
  const barRadius = options?.cornerRadius ?? DEFAULT_BARCHART_RADIUS;
19052
19612
  bars.attr("rx", barRadius);
19053
19613
  bars.attr("ry", barRadius);
19054
- }, margin: margin, xAxisPadding: 0, yAxisPadding: 0, formatTooltipValue: formatTooltipValue, formatTooltipName: formatTooltipName, hideTooltipGroupName: true, dynamicTooltipEnable: true, isBarTooltip: true, onBarClick: filterName ? item => onFilter(item.name) : undefined }) }) }));
19614
+ }, margin: barMargin, xAxisPadding: 0, yAxisPadding: 0, formatTooltipValue: formatTooltipValue, formatTooltipName: formatTooltipName, hideTooltipGroupName: true, dynamicTooltipEnable: true, isBarTooltip: true, onBarClick: filterName ? item => onFilter(item.name) : undefined }) }) }));
19055
19615
  }, [
19056
19616
  customXAxisBottom,
19057
19617
  customYAxisLeft,
@@ -19067,6 +19627,15 @@ const Chart = memo(({ config, element, elementConfig, type, renderElement }) =>
19067
19627
  formatFilterColor,
19068
19628
  chartWidth,
19069
19629
  chartHeight,
19630
+ lineBodyWidth,
19631
+ lineBodyHeight,
19632
+ lineWrapperHeight,
19633
+ axisTitles,
19634
+ hasTopTitles,
19635
+ hasSideTitles,
19636
+ axisTitleColor,
19637
+ hideLeftAxis,
19638
+ hideRightAxis,
19070
19639
  fitsHeight,
19071
19640
  pieSide,
19072
19641
  fill,
@@ -19076,11 +19645,14 @@ const Chart = memo(({ config, element, elementConfig, type, renderElement }) =>
19076
19645
  formatTooltipValue,
19077
19646
  formatTooltipName,
19078
19647
  filterName,
19079
- axes,
19080
19648
  labels,
19081
19649
  margin,
19650
+ barMargin,
19082
19651
  renderLineChartTooltip,
19083
- customize,
19652
+ customizeLine,
19653
+ lineBodyRef,
19654
+ formatLeftTick,
19655
+ formatRightTick,
19084
19656
  primaryColor,
19085
19657
  type,
19086
19658
  elementConfig?.children,
@@ -19097,14 +19669,14 @@ const Chart = memo(({ config, element, elementConfig, type, renderElement }) =>
19097
19669
  ]);
19098
19670
  if (!isVisibleContainer(id, expandable, expanded, expandedContainers))
19099
19671
  return null;
19100
- const chartBody = (jsxs(ChartWrapper, { width: isPieChart && fill ? pieSide : width, height: isStackBar ? "auto" : isPieChart ? (fill ? pieSide : width) : chartHeight, column: column, loading: loading, children: [jsx(ChartTooltipStyles, {}), renderChart] }));
19672
+ const chartBody = (jsxs(ChartWrapper, { width: isPieChart && fill ? pieSide : width, height: isStackBar ? "auto" : isPieChart ? (fill ? pieSide : width) : isLineChart ? lineWrapperHeight : chartHeight, column: column, loading: loading, children: [jsx(ChartTooltipStyles, {}), renderChart] }));
19101
19673
  if (!fill)
19102
19674
  return chartBody;
19103
19675
  // fill: тело центрируется в измеряемой обёртке, ref которой питает ResizeObserver обеими осями.
19104
19676
  return jsx(ChartFillMeasure, { ref: chartRef, children: chartBody });
19105
19677
  });
19106
19678
 
19107
- const ChartLegend = ({ data, chartElement, fontSize, type, twoColumns, loading }) => {
19679
+ const ChartLegend = ({ data, chartElement, fontSize, type, twoColumns, column, loading }) => {
19108
19680
  const { t } = useGlobalContext();
19109
19681
  const { sliceItems, showMore, onShowMore } = useShownOtherItems(chartElement?.options);
19110
19682
  const axes = useMemo(() => getChartAxes(chartElement), [chartElement]);
@@ -19113,7 +19685,7 @@ const ChartLegend = ({ data, chartElement, fontSize, type, twoColumns, loading }
19113
19685
  const showMoreBtn = !!shownItems && data.length > shownItems;
19114
19686
  if (!loading && !data?.length && !!hideEmpty)
19115
19687
  return null;
19116
- return (jsxs(Flex, { flexDirection: "column", opacity: loading ? FILTERED_VALUE_OPACITY / 100 : 1, children: [jsx(ChartLegendContainer, { twoColumns: twoColumns, children: sliceItems(data)?.map(({ name, color }, index) => (jsxs(ChartLegendItem, { hasAnyFilter: hasAnyFilter, isFiltered: isFiltered(name), onClick: axes?.[0]?.filterName ? () => onFilter(name) : undefined, children: [jsx(ChartLegendColor, { color: color }), jsx(ChartLegendName, { "$fontSize": fontSize, "$fontColor": fontColor, children: name })] }, index))) }), showMoreBtn && (jsx(LegendToggler, { onClick: onShowMore, toggled: showMore, children: t("other", { ns: "dashboard", defaultValue: "Другое" }) }))] }));
19688
+ return (jsxs(Flex, { flexDirection: "column", opacity: loading ? FILTERED_VALUE_OPACITY / 100 : 1, children: [jsx(ChartLegendContainer, { twoColumns: twoColumns, "$column": column, children: sliceItems(data)?.map(({ name, color }, index) => (jsxs(ChartLegendItem, { hasAnyFilter: hasAnyFilter, isFiltered: isFiltered(name), onClick: axes?.[0]?.filterName ? () => onFilter(name) : undefined, children: [jsx(ChartLegendColor, { color: color }), jsx(ChartLegendName, { "$fontSize": fontSize, "$fontColor": fontColor, children: name })] }, index))) }), showMoreBtn && (jsx(LegendToggler, { onClick: onShowMore, toggled: showMore, children: t("other", { ns: "dashboard", defaultValue: "Другое" }) }))] }));
19117
19689
  };
19118
19690
 
19119
19691
  const ContainerChildren = memo(({ type, items, isColumn, isMain, renderElement }) => {
@@ -19968,5 +20540,5 @@ const DEFAULT_HEATMAP_STYLE = {
19968
20540
  ],
19969
20541
  };
19970
20542
 
19971
- export { ALIGNMENTS, ALIGN_ITEMS, ATTRIBUTE_ICON_ELEMENT_TYPES, AddButtonRow, AddFeatureButton, AddFeatureContainer, AlertIconContainer, AttachmentContainer, AttributeGalleryContainer, AttributeLabel, BASE_CONTAINER_STYLE, BG_IMAGE_SLOT_ID, BaseMapTheme, CHART_TYPES, COMPACT_FRACTION_DIGITS, CONFIG_PAGES_ID, CONFIG_PAGE_ID, CONTAINERS_GROUP_DEFAULTS, CONTAINER_BODY_ATTRIBUTE, CONTAINER_BODY_FILL_STYLE, CameraContainer, Chart, ChartContainer, ChartLegend, ChartLoading, Container, ContainerBackground, ContainerChildren, ContainerLoading, ContainerRoot, ContainerTemplate, ContainerWrapper, ContainersGroupContainer, DASHBOARD_OVERLAY_Z_INDEX, DEFAULT_ATTRIBUTE_NAME, DEFAULT_BARCHART_RADIUS, DEFAULT_BASE_MAP, DEFAULT_BLUR, DEFAULT_CHART_ANGLE, DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH, DEFAULT_CIRCLE_RADIUS, DEFAULT_CIRCLE_STROKE_WIDTH, DEFAULT_CIRCLE_STYLE, DEFAULT_COLOR, DEFAULT_DASHBOARD_CONFIG, DEFAULT_DATA_SOURCE_LIMIT, DEFAULT_DROPDOWN_WIDTH, DEFAULT_FILL_EXTRUSION_BASE, DEFAULT_FILL_EXTRUSION_HEIGHT, DEFAULT_FILL_EXTRUSION_STYLE, DEFAULT_FILL_EXTRUSION_VERTICAL_GRADIENT, DEFAULT_FILL_STYLE, DEFAULT_FILTER_PADDING, DEFAULT_GRID_GAP, DEFAULT_HEATMAP_COLOR, DEFAULT_HEATMAP_INTENSITY, DEFAULT_HEATMAP_RADIUS, DEFAULT_HEATMAP_STYLE, DEFAULT_HEATMAP_WEIGHT, DEFAULT_ICON_ANCHOR, DEFAULT_ICON_OVERLAP, DEFAULT_ICON_PADDING, DEFAULT_ICON_ROTATE, DEFAULT_ICON_SIZE, DEFAULT_ID_ATTRIBUTE_NAME, DEFAULT_LAT, DEFAULT_LINE_CAP, DEFAULT_LINE_JOIN, DEFAULT_LINE_STYLE, DEFAULT_LINE_WIDTH, DEFAULT_LNG, DEFAULT_OPACITY, DEFAULT_PAGES_CONFIG, DEFAULT_PIECHART_RADIUS, DEFAULT_SYMBOL_COLOR, DEFAULT_SYMBOL_HALO_COLOR, DEFAULT_SYMBOL_PLACEMENT, DEFAULT_SYMBOL_SPACING, DEFAULT_SYMBOL_STYLE, DEFAULT_TEXT_ANCHOR, DEFAULT_TEXT_JUSTIFY, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_TRANSFORM, DEFAULT_TRACK_FR, DEFAULT_TRANSLATE, DEFAULT_ZOOM, DRAG_THRESHOLD_PX, Dashboard, DashboardCheckbox, DashboardChip, DashboardContent, DashboardContext, DashboardDefaultHeader, DashboardHeader, DashboardLoading, DashboardPlaceholder, DashboardPlaceholderWrap, DashboardProvider, DashboardWrapper, DataSourceContainer, DataSourceError, DataSourceErrorContainer, DataSourceInnerContainer, DataSourceProgressContainer, DateFormat, DefaultAttributesContainer, DefaultHeaderContainer, DefaultHeaderWrapper, DividerContainer, EMPTY_DATA_SOURCE_LAYER_INFO, EXTENT_FILTER_NAME, EditGeometryType, ElementButton, ElementCamera, ElementChart, ElementChips, ElementControl, ElementIcon, ElementImage, ElementLegend, ElementLink, ElementMarkdown, ElementSlideshow, ElementSvg, ElementTable, ElementTooltip, ElementValueWrapper, ExpandableTitle, FEATURE_CARD_DEFAULT_COLORS, FEATURE_CARD_OTHER_COLOR, FILL_SIZE, FILTERED_VALUE_OPACITY, FILTER_PREFIX, FR_PRECISION, FeatureCardBackgroundHeader, FeatureCardButtons, FeatureCardContext, FeatureCardDefaultHeader, FeatureCardHeader, FeatureCardProvider, FeatureCardSlideshowHeader, FeatureCardTitle, FeatureControls, FeatureTitleContainer, FiltersContainer, GEOMETRY_ATTRIBUTE, GEOMETRY_FILTER_NAME, GRID_AUTO_FILL_DEFAULTS, GRID_CELL_ATTR, GRID_CELL_ID_PREFIX, GRID_DRAGGING_ATTR, GRID_DRAG_SOURCE_ATTR, GRID_DROP_TARGET_ATTR, GRID_FILL_DEFAULTS, GRID_ROW_ID_PREFIX, GlobalContext, GlobalProvider, GridRowContainer, HANDLE_BLEED_PX, Header, HeaderContainer, HeaderFontColorMixin, HeaderFrontView, HeaderTemplate, HeaderTitleContainer, HiddenTitleItems, IconContainer, ImageContainer, LEFT_PANEL_HEADER_HEIGHT, Layer, LayerDescription, LayerGroupList, LayerIcon, LayerIconContainer, LayerListContainer, LayerTree, LayersContainer, LayersListWrapper, LinearProgressContainer, LogTerminal, LogoContainer, MAP_VIEW_FILTER_NAMES, MAX_CHART_WIDTH, MAX_TRACKS, MIN_TRACK_PX, MIN_TRACK_RATIO, Map$1 as Map, MapContext, MapProvider, NON_TRACK_SLOT_IDS, NO_CELL_DRAG_SELECTOR, NO_CONTENT_VALUE, NUMERIC_ATTRIBUTE_TYPES, NoLiveSnapshotContainer, OBJECT_FITS, OVERFLOWS, OneColumnContainer, PIE_CHART_TOOLTIP_STYLE, POLL_SUBTASK_INTERVAL_MS, POLL_SUBTASK_TIMEOUT_MS, PROJECT_ALIAS_PROP, PROJECT_FILTER_NAME, PROJECT_NAME_PROP, PROJECT_PROPS, PROVIDER_PREFIX, PageNavigator, PageTitle, PageTitleContainer, PagesContainer, Pagination, PresentationHeader, PresentationHeaderButtons, PresentationHeaderTools, PresentationPanelContainer, PresentationPanelWrapper, PresentationWrapper, ProgressContainer, ProviderPrefix, QUERY_DESCRIPTION_CACHE_TTL, RESIZE_HANDLE_ATTR, ResizeHandle, RoundedBackgroundContainer, SAVE_HOOK_RESULT_DURATION, SERVER_NOTIFICATION_EVENT, STACK_BAR_TOTAL_HEIGHT, ScalingFactor, ServerNotificationsContext, ServerNotificationsProvider, SlideshowContainer, SmallPreviewContainer$1 as SmallPreviewContainer, SmallPreviewControl, SmallPreviewCounter, SmallPreviewImages, SmallPreviewLeft, SmallPreviewRight, StackBar, StructuredDataContainer, SvgImage, TILE_ALIGNMENTS, TIME_ZONE_FORMAT, TITLE_SLOT_IDS, TabsContainer, TextTrim, ThemeName, TitleContainer, TopContainer, TopContainerButtons, TwoColumnContainer, UploadContainer, VIEW_MODES, VoteContainer, WidgetType, ZOOM_FILTER_NAME, addDataSource, addDataSources, adjustColor, applyFiltersToCondition, applyQueryFilters, applyTreeFilterToCondition, applyVarsToCondition, asAttributeName, asChartId, asContainerId, asDataSourceName, asFilterName, asLayerName, asModalId, asResourceId, asTabId, buildGridTemplate, buildTrackTemplate, checkEqualOrIncludes, checkIsLoading, collectConfigIds, colorToHex, containsNodeId, createConfigLayer, createConfigPage, createGridCell, createGridIdFactory, createGridRow, createNewPageId, createSaveNotificationId, dateOptions, debounce, decimalOpacityToHex, enrichStyleItemsWithIds, enrichStyleModelsWithIds, eqlParametersToPayload, fetchQueryDescription, findAttributeInExpression, findCellContext, formatArea, formatAttributeValue, formatChartRelatedValue, formatConditionValue, formatDataSourceCondition, formatDate$1 as formatDate, formatElementValue, formatLength, formatNumber, formatPolygonMeasure, geometryToEwkt, getActualExtrusionHeight, getAttributeByName, getAttributeConfigurationByName, getAttributeIconElement, getAttributeIconUrl, getAttributeValue, getAttributesConfiguration, getAverageTrackSize, getChartAxes, getChartFilterName, getChartMarkers, getConfigFilter, getContainerComponent, getDashboardHeader, getDataFromAttributes, getDataFromRelatedFeatures, getDataSource, getDataSourceFilterValue, getDataSourceLayerInfo, getDate, getDefaultConfig, getDisplayTemplateNameFromAttribute, getElementValue, getFeatureAttributes, getFeatureCardHeader, getFilterComponent, getFilterSelectedItems, getFilterValue, getFormattedAttributes, getGradientColors, getImageUrl, getLayerClientStyle, getLayerInfo, getLayerInfoAttribute, getLayerInfoFromDataSources, getLayoutChildren, getMapViewDataSources, getModalsDataSources, getPagesFromConfig, getPagesFromProjectInfo, getProjectValue, getProxyService, getRelatedAttribute, getRenderElement, getResourceUrl, getRootElementId, getSelectedFilterValue, getShownItemsLimit, getSlideshowImages, getStyleAttributes, getSvgUrl, getTemplateNameFromAttribute, getThemeByName, getTotalFromAttributes, getTotalFromRelatedFeatures, getTrackSize, getTrackSizeKey, getTrackSizes, getWrapperSizeStyle, hasContainerBgImage, hexToRgba, isCrossOriginUrl, isEmptyElementValue, isEmptyValue, isFeaturesFilterValue, isFillSize, isFrSize, isGridNode, isHiddenEmptyValue, isHookActive, isLayerService, isNotValidSelectedTab, isNumeric, isObject, isProxyService, isRootOwningContainer, isTreeFilterValue, isVisibleContainer, mapNodeById, mergeAttributeConfigurations, metersPerPixel, noMarginMixin, numberOptions, parseFrValue, parseIconNames, parseIconNamesFromClientStyle, pieChartTooltipFromAttributes, pieChartTooltipFromRelatedFeatures, pointOptions, removeDataSource, removeTracks, replaceNodesByIds, rgbToHex, roundFr, roundTotalSum, setLayoutChildren, sizeCssMixin, sliceShownOtherItems, stretchPalette, timeOptions, toConditionsArray, toCssSize, toFrSize, toPxNumber, toRenderableValue, tooltipNameFromAttributes, tooltipValueFromAttributes, tooltipValueFromRelatedFeatures, transparentizeColor, updateDataSource, useAfterSave, useAppHeight, useAttachmentDownload, useAttachmentItems, useAttachmentPreviewImages, useAttachmentsView, useAutoCompleteControl, useBeforeSave, useBgImageHost, useChartChange, useChartData, useConfigDataSources, useContainerAttributes, useContainerRoot, useCurrentPageLayers, useCustomFeatureSelect, useDashboardHeader, useDataSourceLoading, useDataSources, useDebouncedCallback, useDiffPage, useEditGroupAttributes, useEqualTileWidth, useExpandableContainers, useExportPdf, useFeatureSaveHooks, useFetchImageWithAuth, useFetchWithAuth, useGetConfigLayer, useGlobalContext, useHeaderRender, useHideIfEmptyDataSource, useLayerHiddenAttributes, useLayerParams, useMapContext, useMapDraw, useMapImages, useMaxZoomTo, useProjectDashboardInit, usePythonSandbox, usePythonTask, useRedrawLayer, useRelatedDataSourceAttributes, useRemoteTask, useRenderElement, useResizeBox, useResizeDrag, useSavePrototypeBuilder, useServerNotificationsContext, useShownOtherItems, useToggle, useUpdateDataSource, useVisibleProjectItems, useWidgetConfig, useWidgetContext, useWidgetFilters, useWidgetPage, useWindowResize, useWrapperSize, useZoomToFeatures, useZoomToPoint, withTrackSize };
20543
+ export { ALIGNMENTS, ALIGN_ITEMS, ATTRIBUTE_ICON_ELEMENT_TYPES, AXIS_TITLE_GAP, AXIS_TITLE_LINE_HEIGHT, AXIS_TITLE_TOP_GRID_GAP, AXIS_TITLE_TOP_HEIGHT, AddButtonRow, AddFeatureButton, AddFeatureContainer, AlertIconContainer, AttachmentContainer, AttributeGalleryContainer, AttributeLabel, BASE_CONTAINER_STYLE, BG_IMAGE_SLOT_ID, BaseMapTheme, CHART_AXIS_ALIGN_JUSTIFY, CHART_TYPES, COMPACT_FRACTION_DIGITS, CONFIG_PAGES_ID, CONFIG_PAGE_ID, CONTAINERS_GROUP_DEFAULTS, CONTAINER_BODY_ATTRIBUTE, CONTAINER_BODY_FILL_STYLE, CameraContainer, Chart, ChartContainer, ChartLegend, ChartLoading, Container, ContainerBackground, ContainerChildren, ContainerLoading, ContainerRoot, ContainerTemplate, ContainerWrapper, ContainersGroupContainer, DASHBOARD_OVERLAY_Z_INDEX, DEFAULT_ATTRIBUTE_NAME, DEFAULT_BARCHART_RADIUS, DEFAULT_BASE_MAP, DEFAULT_BLUR, DEFAULT_CHART_ANGLE, DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH, DEFAULT_CIRCLE_RADIUS, DEFAULT_CIRCLE_STROKE_WIDTH, DEFAULT_CIRCLE_STYLE, DEFAULT_COLOR, DEFAULT_DASHBOARD_CONFIG, DEFAULT_DATA_SOURCE_LIMIT, DEFAULT_DROPDOWN_WIDTH, DEFAULT_FILL_EXTRUSION_BASE, DEFAULT_FILL_EXTRUSION_HEIGHT, DEFAULT_FILL_EXTRUSION_STYLE, DEFAULT_FILL_EXTRUSION_VERTICAL_GRADIENT, DEFAULT_FILL_STYLE, DEFAULT_FILTER_PADDING, DEFAULT_GRID_GAP, DEFAULT_HEATMAP_COLOR, DEFAULT_HEATMAP_INTENSITY, DEFAULT_HEATMAP_RADIUS, DEFAULT_HEATMAP_STYLE, DEFAULT_HEATMAP_WEIGHT, DEFAULT_ICON_ANCHOR, DEFAULT_ICON_OVERLAP, DEFAULT_ICON_PADDING, DEFAULT_ICON_ROTATE, DEFAULT_ICON_SIZE, DEFAULT_ID_ATTRIBUTE_NAME, DEFAULT_LAT, DEFAULT_LINE_CAP, DEFAULT_LINE_JOIN, DEFAULT_LINE_STYLE, DEFAULT_LINE_WIDTH, DEFAULT_LNG, DEFAULT_OPACITY, DEFAULT_PAGES_CONFIG, DEFAULT_PIECHART_RADIUS, DEFAULT_SYMBOL_COLOR, DEFAULT_SYMBOL_HALO_COLOR, DEFAULT_SYMBOL_PLACEMENT, DEFAULT_SYMBOL_SPACING, DEFAULT_SYMBOL_STYLE, DEFAULT_TEXT_ANCHOR, DEFAULT_TEXT_JUSTIFY, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_TRANSFORM, DEFAULT_TRACK_FR, DEFAULT_TRANSLATE, DEFAULT_ZOOM, DRAG_THRESHOLD_PX, Dashboard, DashboardCheckbox, DashboardChip, DashboardContent, DashboardContext, DashboardDefaultHeader, DashboardHeader, DashboardLoading, DashboardPlaceholder, DashboardPlaceholderWrap, DashboardProvider, DashboardWrapper, DataSourceContainer, DataSourceError, DataSourceErrorContainer, DataSourceInnerContainer, DataSourceProgressContainer, DateFormat, DefaultAttributesContainer, DefaultHeaderContainer, DefaultHeaderWrapper, DividerContainer, EMPTY_DATA_SOURCE_LAYER_INFO, EXTENT_FILTER_NAME, EditGeometryType, ElementButton, ElementCamera, ElementChart, ElementChips, ElementControl, ElementIcon, ElementImage, ElementLegend, ElementLink, ElementMarkdown, ElementSlideshow, ElementSvg, ElementTable, ElementTooltip, ElementValueWrapper, ExpandableTitle, FEATURE_CARD_DEFAULT_COLORS, FEATURE_CARD_OTHER_COLOR, FILL_SIZE, FILTERED_VALUE_OPACITY, FILTER_PREFIX, FR_PRECISION, FeatureCardBackgroundHeader, FeatureCardButtons, FeatureCardContext, FeatureCardDefaultHeader, FeatureCardHeader, FeatureCardProvider, FeatureCardSlideshowHeader, FeatureCardTitle, FeatureControls, FeatureTitleContainer, FiltersContainer, GEOMETRY_ATTRIBUTE, GEOMETRY_FILTER_NAME, GRID_AUTO_FILL_DEFAULTS, GRID_CELL_ATTR, GRID_CELL_ID_PREFIX, GRID_DRAGGING_ATTR, GRID_DRAG_SOURCE_ATTR, GRID_DROP_TARGET_ATTR, GRID_FILL_DEFAULTS, GRID_ROW_ID_PREFIX, GlobalContext, GlobalProvider, GridRowContainer, HANDLE_BLEED_PX, Header, HeaderContainer, HeaderFontColorMixin, HeaderFrontView, HeaderTemplate, HeaderTitleContainer, HiddenTitleItems, IconContainer, ImageContainer, LEFT_PANEL_HEADER_HEIGHT, LINE_CHART_X_LABELS_BLANK, LINE_CHART_Y_TICKS, Layer, LayerDescription, LayerGroupList, LayerIcon, LayerIconContainer, LayerListContainer, LayerTree, LayersContainer, LayersListWrapper, LinearProgressContainer, LogTerminal, LogoContainer, MAP_VIEW_FILTER_NAMES, MAX_CHART_WIDTH, MAX_TRACKS, MIN_TRACK_PX, MIN_TRACK_RATIO, Map$1 as Map, MapContext, MapProvider, NON_TRACK_SLOT_IDS, NO_CELL_DRAG_SELECTOR, NO_CONTENT_VALUE, NUMERIC_ATTRIBUTE_TYPES, NoLiveSnapshotContainer, OBJECT_FITS, OVERFLOWS, OneColumnContainer, PIE_CHART_TOOLTIP_STYLE, POLL_SUBTASK_INTERVAL_MS, POLL_SUBTASK_TIMEOUT_MS, PROJECT_ALIAS_PROP, PROJECT_FILTER_NAME, PROJECT_NAME_PROP, PROJECT_PROPS, PROVIDER_PREFIX, PageNavigator, PageTitle, PageTitleContainer, PagesContainer, Pagination, PresentationHeader, PresentationHeaderButtons, PresentationHeaderTools, PresentationPanelContainer, PresentationPanelWrapper, PresentationWrapper, ProgressContainer, ProviderPrefix, QUERY_DESCRIPTION_CACHE_TTL, RESIZE_HANDLE_ATTR, ResizeHandle, RoundedBackgroundContainer, SAVE_HOOK_RESULT_DURATION, SERVER_NOTIFICATION_EVENT, STACK_BAR_TOTAL_HEIGHT, ScalingFactor, ServerNotificationsContext, ServerNotificationsProvider, SlideshowContainer, SmallPreviewContainer$1 as SmallPreviewContainer, SmallPreviewControl, SmallPreviewCounter, SmallPreviewImages, SmallPreviewLeft, SmallPreviewRight, StackBar, StructuredDataContainer, SvgImage, TILE_ALIGNMENTS, TIME_ZONE_FORMAT, TITLE_SLOT_IDS, TabsContainer, TextTrim, ThemeName, TitleContainer, TopContainer, TopContainerButtons, TwoColumnContainer, UploadContainer, VIEW_MODES, VoteContainer, WidgetType, X_AXIS_TITLE_GAP, X_AXIS_TITLE_LEGEND_GAP, Y_AXIS_LABEL_OVERHANG, ZOOM_FILTER_NAME, addDataSource, addDataSources, adjustColor, applyFiltersToCondition, applyQueryFilters, applyTreeFilterToCondition, applyVarsToCondition, asAttributeName, asChartId, asContainerId, asDataSourceName, asFilterName, asLayerName, asModalId, asResourceId, asTabId, buildGridTemplate, buildTrackTemplate, checkEqualOrIncludes, checkIsLoading, collectConfigIds, colorToHex, containsNodeId, createConfigLayer, createConfigPage, createGridCell, createGridIdFactory, createGridRow, createNewPageId, createSaveNotificationId, dateOptions, debounce, decimalOpacityToHex, enrichStyleItemsWithIds, enrichStyleModelsWithIds, eqlParametersToPayload, fetchQueryDescription, findAttributeInExpression, findCellContext, formatArea, formatAttributeValue, formatChartRelatedValue, formatConditionValue, formatDataSourceCondition, formatDate$1 as formatDate, formatElementValue, formatLength, formatNumber, formatPolygonMeasure, geometryToEwkt, getActualExtrusionHeight, getAttributeByName, getAttributeConfigurationByName, getAttributeIconElement, getAttributeIconUrl, getAttributeValue, getAttributesConfiguration, getAverageTrackSize, getAxisSide, getChartAxes, getChartFilterName, getChartMarkers, getConfigFilter, getContainerComponent, getDashboardHeader, getDataFromAttributes, getDataFromRelatedFeatures, getDataSource, getDataSourceFilterValue, getDataSourceLayerInfo, getDate, getDefaultConfig, getDisplayTemplateNameFromAttribute, getElementValue, getFeatureAttributes, getFeatureCardHeader, getFilterComponent, getFilterSelectedItems, getFilterValue, getFormattedAttributes, getGradientColors, getImageUrl, getLayerClientStyle, getLayerInfo, getLayerInfoAttribute, getLayerInfoFromDataSources, getLayoutChildren, getMapViewDataSources, getModalsDataSources, getPagesFromConfig, getPagesFromProjectInfo, getProjectValue, getProxyService, getRelatedAttribute, getRenderElement, getResourceUrl, getRootElementId, getSelectedFilterValue, getShownItemsLimit, getSlideshowImages, getStyleAttributes, getSvgUrl, getTemplateNameFromAttribute, getThemeByName, getTotalFromAttributes, getTotalFromRelatedFeatures, getTrackSize, getTrackSizeKey, getTrackSizes, getWrapperSizeStyle, hasAxisConfig, hasContainerBgImage, hexToRgba, isCrossOriginUrl, isEmptyElementValue, isEmptyValue, isFeaturesFilterValue, isFillSize, isFrSize, isGridNode, isHiddenEmptyValue, isHookActive, isLayerService, isNotValidSelectedTab, isNumeric, isObject, isProxyService, isRightAxis, isRootOwningContainer, isTreeFilterValue, isValueAxis, isVisibleContainer, mapNodeById, mergeAttributeConfigurations, metersPerPixel, noMarginMixin, numberOptions, parseFrValue, parseIconNames, parseIconNamesFromClientStyle, pieChartTooltipFromAttributes, pieChartTooltipFromRelatedFeatures, pointOptions, removeDataSource, removeTracks, replaceNodesByIds, resolveAxis, rgbToHex, roundFr, roundTotalSum, setLayoutChildren, sizeCssMixin, sliceShownOtherItems, stretchPalette, timeOptions, toConditionsArray, toCssSize, toFrSize, toPxNumber, toRenderableValue, tooltipNameFromAttributes, tooltipValueFromAttributes, tooltipValueFromRelatedFeatures, transparentizeColor, updateDataSource, useAfterSave, useAppHeight, useAttachmentDownload, useAttachmentItems, useAttachmentPreviewImages, useAttachmentsView, useAutoCompleteControl, useBeforeSave, useBgImageHost, useChartAxisAttributes, useChartAxisTickFormat, useChartAxisTitles, useChartChange, useChartData, useConfigDataSources, useContainerAttributes, useContainerRoot, useCurrentPageLayers, useCustomFeatureSelect, useDashboardHeader, useDataSourceLoading, useDataSources, useDebouncedCallback, useDiffPage, useEditGroupAttributes, useEqualTileWidth, useExpandableContainers, useExportPdf, useFeatureSaveHooks, useFetchImageWithAuth, useFetchWithAuth, useGetConfigLayer, useGlobalContext, useHeaderRender, useHideIfEmptyDataSource, useLayerHiddenAttributes, useLayerParams, useMapContext, useMapDraw, useMapImages, useMaxZoomTo, useProjectDashboardInit, usePythonSandbox, usePythonTask, useRedrawLayer, useRelatedDataSourceAttributes, useRemoteTask, useRenderElement, useResizeBox, useResizeDrag, useSavePrototypeBuilder, useServerNotificationsContext, useShownOtherItems, useToggle, useUpdateDataSource, useVisibleProjectItems, useWidgetConfig, useWidgetContext, useWidgetFilters, useWidgetPage, useWindowResize, useWrapperSize, useZoomToFeatures, useZoomToPoint, withTrackSize };
19972
20544
  //# sourceMappingURL=react.esm.js.map