@pgcorp/ui-kit 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -79,6 +79,7 @@ export interface ChartLegendItem {
79
79
  export interface ChartTick {
80
80
  readonly value: number
81
81
  readonly label: string
82
+ readonly displayLabel: string
82
83
  readonly position: number
83
84
  }
84
85
 
@@ -87,6 +88,7 @@ export interface ChartCategoryLabel {
87
88
  readonly label: string
88
89
  readonly lines: readonly string[]
89
90
  readonly x: number
91
+ readonly textAnchor: 'start' | 'middle' | 'end'
90
92
  }
91
93
 
92
94
  export interface ChartGridLine {
@@ -158,6 +160,12 @@ interface PlotRect {
158
160
  readonly height: number
159
161
  }
160
162
 
163
+ interface ResolvedDomain {
164
+ readonly minimum: number
165
+ readonly maximum: number
166
+ readonly ticks: readonly number[]
167
+ }
168
+
161
169
  const SIZE_DIMENSIONS: Readonly<Record<SChartSize, ChartDimensions>> = {
162
170
  compact: { width: 360, height: 96 },
163
171
  sm: { width: 640, height: 240 },
@@ -393,6 +401,7 @@ function resolvePlot(
393
401
  xAxis: boolean,
394
402
  yAxis: boolean,
395
403
  dataLabels: SChartDataLabels,
404
+ yTickLabels: readonly string[],
396
405
  ): PlotRect {
397
406
  if (type === 'pie' || type === 'donut') {
398
407
  const inset = size === 'compact' ? 4 : 12
@@ -403,7 +412,15 @@ function resolvePlot(
403
412
  : (size === 'compact' ? 14 : 24)
404
413
  const right = size === 'compact' ? 6 : 16
405
414
  const bottom = xAxis ? (size === 'compact' ? 32 : 48) : (size === 'compact' ? 5 : 12)
406
- const left = yAxis ? (size === 'compact' ? 34 : 54) : (size === 'compact' ? 6 : 14)
415
+ const defaultLeft = yAxis ? (size === 'compact' ? 34 : 54) : (size === 'compact' ? 6 : 14)
416
+ const tickFontSize = size === 'compact' ? 9 : 11
417
+ const tickWidth = yAxis
418
+ ? Math.max(0, ...yTickLabels.map((label) => estimateAxisTextWidth(label, tickFontSize)))
419
+ : 0
420
+ const desiredLeft = yAxis ? Math.max(defaultLeft, Math.ceil(tickWidth + 16)) : defaultLeft
421
+ const minimumPlotWidth = Math.min(size === 'compact' ? 72 : 120, dimensions.width * 0.4)
422
+ const maximumLeft = Math.max(0, dimensions.width - right - minimumPlotWidth)
423
+ const left = Math.min(desiredLeft, maximumLeft)
407
424
  return {
408
425
  x: left,
409
426
  y: top,
@@ -412,6 +429,28 @@ function resolvePlot(
412
429
  }
413
430
  }
414
431
 
432
+ function estimateAxisTextWidth(label: string, fontSize: number): number {
433
+ const units = Array.from(label).reduce((total, character) => {
434
+ if (/\s/u.test(character)) return total + 0.36
435
+ if (/[.,:;!|ilI1'`]/u.test(character)) return total + 0.34
436
+ if (/[MW@%ШЩЖЮ]/u.test(character)) return total + 0.92
437
+ return total + 0.62
438
+ }, 0)
439
+ return units * fontSize
440
+ }
441
+
442
+ function truncateAxisLabelToWidth(label: string, availableWidth: number, fontSize: number): string {
443
+ if (estimateAxisTextWidth(label, fontSize) <= availableWidth) return label
444
+ const ellipsis = '…'
445
+ const characters = Array.from(label)
446
+ while (characters.length > 0) {
447
+ const candidate = `${characters.join('')}${ellipsis}`
448
+ if (estimateAxisTextWidth(candidate, fontSize) <= availableWidth) return candidate
449
+ characters.pop()
450
+ }
451
+ return ellipsis
452
+ }
453
+
415
454
  function truncateAxisWord(word: string, maximumCharacters: number): string {
416
455
  if (word.length <= maximumCharacters) return word
417
456
  return `${word.slice(0, Math.max(1, maximumCharacters - 1))}…`
@@ -461,16 +500,9 @@ function cartesianModel(
461
500
  series: readonly SChartSeries[],
462
501
  dimensions: ChartDimensions,
463
502
  plot: PlotRect,
503
+ resolvedDomain: ResolvedDomain,
504
+ yTickLabels: readonly string[],
464
505
  ): Omit<ChartModel, 'empty' | 'emptyReason' | 'slices' | 'centerX' | 'centerY'> {
465
- const domainValues = input.type === 'bar' && input.barMode === 'stacked'
466
- ? resolveStackedDomainValues(series, categories.length)
467
- : series.flatMap((item) => [...item.values])
468
- const resolvedDomain = resolveDomain(
469
- input.domain,
470
- domainValues,
471
- input.type === 'bar' || input.type === 'area',
472
- input.yTickCount,
473
- )
474
506
  const range = resolvedDomain.maximum - resolvedDomain.minimum
475
507
  const yAt = (value: number): number => plot.y + plot.height - ((value - resolvedDomain.minimum) / range) * plot.height
476
508
  const baselineY = Math.max(plot.y, Math.min(plot.y + plot.height, yAt(0)))
@@ -552,17 +584,57 @@ function cartesianModel(
552
584
  if (input.xLabels === 'endpoints') return index === 0 || index === categories.length - 1
553
585
  return categories.length <= 6 || index === 0 || index === categories.length - 1
554
586
  }
555
- const xLabels = categories.flatMap((category, index) => showXLabel(index)
556
- ? [{
587
+ const visibleCategories = categories
588
+ .map((category, index) => ({ category, index, x: xAt(index) }))
589
+ .filter(({ index }) => showXLabel(index))
590
+ const xLabels = visibleCategories.map(({ category, x }, visibleIndex): ChartCategoryLabel => {
591
+ const previous = visibleCategories[visibleIndex - 1]
592
+ const next = visibleCategories[visibleIndex + 1]
593
+ if (visibleCategories.length === 1) {
594
+ return {
595
+ key: category.key,
596
+ label: category.label,
597
+ lines: wrapAxisLabel(category.label, plot.width),
598
+ x: plot.x + plot.width / 2,
599
+ textAnchor: 'middle',
600
+ }
601
+ }
602
+ if (!previous) {
603
+ const rightBoundary = (x + next!.x) / 2
604
+ return {
605
+ key: category.key,
606
+ label: category.label,
607
+ lines: wrapAxisLabel(category.label, rightBoundary - plot.x),
608
+ x: plot.x,
609
+ textAnchor: 'start',
610
+ }
611
+ }
612
+ if (!next) {
613
+ const leftBoundary = (previous.x + x) / 2
614
+ return {
557
615
  key: category.key,
558
616
  label: category.label,
559
- lines: wrapAxisLabel(category.label, categoryBand),
560
- x: xAt(index),
561
- }]
562
- : [])
563
- const yTicks = resolvedDomain.ticks.map((value) => ({
617
+ lines: wrapAxisLabel(category.label, plot.x + plot.width - leftBoundary),
618
+ x: plot.x + plot.width,
619
+ textAnchor: 'end',
620
+ }
621
+ }
622
+ const leftBoundary = (previous.x + x) / 2
623
+ const rightBoundary = (x + next.x) / 2
624
+ return {
625
+ key: category.key,
626
+ label: category.label,
627
+ lines: wrapAxisLabel(category.label, 2 * Math.min(x - leftBoundary, rightBoundary - x)),
628
+ x,
629
+ textAnchor: 'middle',
630
+ }
631
+ })
632
+ const tickFontSize = input.size === 'compact' ? 9 : 11
633
+ const tickLabelWidth = Math.max(1, plot.x - 16)
634
+ const yTicks = resolvedDomain.ticks.map((value, index) => ({
564
635
  value,
565
- label: formatValue(input.valueFormatter, value, `yTick.${value}`),
636
+ label: yTickLabels[index]!,
637
+ displayLabel: truncateAxisLabelToWidth(yTickLabels[index]!, tickLabelWidth, tickFontSize),
566
638
  position: yAt(value),
567
639
  }))
568
640
  const horizontalGrid = yTicks.map((tick) => ({
@@ -725,6 +797,32 @@ export function resolveChartModel(input: ResolveChartModelInput): ChartModel {
725
797
  centerY: dimensions.height / 2,
726
798
  }
727
799
  }
800
+ if (input.type === 'pie' || input.type === 'donut') {
801
+ const plot = resolvePlot(
802
+ dimensions,
803
+ input.type,
804
+ input.size,
805
+ input.xAxis,
806
+ input.yAxis,
807
+ input.dataLabels,
808
+ [],
809
+ )
810
+ return radialModel(input, categories, series, dimensions, plot)
811
+ }
812
+ const domainValues = input.type === 'bar' && input.barMode === 'stacked'
813
+ ? resolveStackedDomainValues(series, categories.length)
814
+ : series.flatMap((item) => [...item.values])
815
+ const resolvedDomain = resolveDomain(
816
+ input.domain,
817
+ domainValues,
818
+ input.type === 'bar' || input.type === 'area',
819
+ input.yTickCount,
820
+ )
821
+ const yTickLabels = resolvedDomain.ticks.map((value) => formatValue(
822
+ input.valueFormatter,
823
+ value,
824
+ `yTick.${value}`,
825
+ ))
728
826
  const plot = resolvePlot(
729
827
  dimensions,
730
828
  input.type,
@@ -732,11 +830,17 @@ export function resolveChartModel(input: ResolveChartModelInput): ChartModel {
732
830
  input.xAxis,
733
831
  input.yAxis,
734
832
  input.dataLabels,
833
+ yTickLabels,
834
+ )
835
+ const model = cartesianModel(
836
+ input,
837
+ categories,
838
+ series,
839
+ dimensions,
840
+ plot,
841
+ resolvedDomain,
842
+ yTickLabels,
735
843
  )
736
- if (input.type === 'pie' || input.type === 'donut') {
737
- return radialModel(input, categories, series, dimensions, plot)
738
- }
739
- const model = cartesianModel(input, categories, series, dimensions, plot)
740
844
  return {
741
845
  ...model,
742
846
  empty: false,
@@ -116,6 +116,9 @@
116
116
  --s-control-hitbox-md: 2.5rem;
117
117
  --s-control-hitbox-lg: 3rem;
118
118
  --s-control-indicator-size: 1.25rem;
119
+ --s-resize-handle-hitbox-compact: 0.375rem;
120
+ --s-resize-handle-hitbox-standard: 0.75rem;
121
+ --s-resize-handle-hitbox-spacious: 2rem;
119
122
  --s-icon-size-2xs: 0.75rem;
120
123
  --s-icon-size-xs: 1rem;
121
124
  --s-icon-size-sm: 1.25rem;