• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SAP / ui5-webcomponents-react / 10697974240

04 Sep 2024 08:27AM CUT coverage: 87.249% (-0.6%) from 87.88%
10697974240

Pull #6312

github

web-flow
Merge 9f106f5de into 98738d3e0
Pull Request #6312: feat: update to UI5 Web Components ~2.2.0

2835 of 3792 branches covered (74.76%)

5043 of 5780 relevant lines covered (87.25%)

95693.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.19
/packages/charts/src/components/ColumnChart/ColumnChart.tsx
1
'use client';
2

3
import { enrichEventWithDetails, ThemingParameters, useIsRTL, useSyncRef } from '@ui5/webcomponents-react-base';
4
import type { CSSProperties } from 'react';
5
import { forwardRef, useCallback } from 'react';
6
import {
7
  Bar as Column,
8
  BarChart as ColumnChartLib,
9
  Brush,
10
  CartesianGrid,
11
  Cell,
12
  LabelList,
13
  Legend,
14
  ReferenceLine,
15
  Tooltip,
16
  XAxis,
17
  YAxis
18
} from 'recharts';
19
import type { YAxisProps } from 'recharts';
20
import { getValueByDataKey } from 'recharts/lib/util/ChartUtils.js';
21
import { useCancelAnimationFallback } from '../../hooks/useCancelAnimationFallback.js';
22
import { useChartMargin } from '../../hooks/useChartMargin.js';
23
import { useLabelFormatter } from '../../hooks/useLabelFormatter.js';
24
import { useLegendItemClick } from '../../hooks/useLegendItemClick.js';
25
import { useLongestYAxisLabel } from '../../hooks/useLongestYAxisLabel.js';
26
import { useObserveXAxisHeights } from '../../hooks/useObserveXAxisHeights.js';
27
import { useOnClickInternal } from '../../hooks/useOnClickInternal.js';
28
import { usePrepareDimensionsAndMeasures } from '../../hooks/usePrepareDimensionsAndMeasures.js';
29
import { useTooltipFormatter } from '../../hooks/useTooltipFormatter.js';
30
import type { IChartBaseProps } from '../../interfaces/IChartBaseProps.js';
31
import type { IChartDimension } from '../../interfaces/IChartDimension.js';
32
import type { IChartMeasure } from '../../interfaces/IChartMeasure.js';
33
import { ChartContainer } from '../../internal/ChartContainer.js';
34
import { ChartDataLabel } from '../../internal/ChartDataLabel.js';
35
import { defaultFormatter } from '../../internal/defaults.js';
36
import { tickLineConfig, tooltipContentStyle, tooltipFillOpacity } from '../../internal/staticProps.js';
37
import { getCellColors, resolvePrimaryAndSecondaryMeasures } from '../../internal/Utils.js';
38
import { XAxisTicks } from '../../internal/XAxisTicks.js';
39
import { YAxisTicks } from '../../internal/YAxisTicks.js';
40
import { ColumnChartPlaceholder } from './Placeholder.js';
41

42
interface MeasureConfig extends IChartMeasure {
43
  /**
44
   * Column Width
45
   */
46
  width?: number;
47
  /**
48
   * Column Opacity
49
   */
50
  opacity?: number;
51
  /**
52
   * column Stack ID
53
   * @default undefined
54
   */
55
  stackId?: string;
56
  /**
57
   * Highlight color of defined elements
58
   * @param value {string | number} Current value of the highlighted measure
59
   * @param measure {IChartMeasure} Current measure object
60
   * @param dataElement {object} Current data element
61
   */
62
  highlightColor?: (value: number, measure: MeasureConfig, dataElement: Record<string, any>) => CSSProperties['color'];
63
}
64

65
interface DimensionConfig extends IChartDimension {
66
  /**
67
   * Interval of axis label
68
   */
69
  interval?: YAxisProps['interval'];
70
}
71

72
export interface ColumnChartProps extends IChartBaseProps {
73
  /**
74
   * An array of config objects. Each object will define one dimension of the chart.
75
   *
76
   * **Required Properties**
77
   * - `accessor`: string containing the path to the dataset key the dimension should display. Supports object structures by using <code>'parent.child'</code>.
78
   *   Can also be a getter.
79
   *
80
   * **Optional Properties**
81
   * - `formatter`: function will be called for each data label and allows you to format it according to your needs
82
   * - `interval`: number that controls how many ticks are rendered on the x axis
83
   *
84
   */
85
  dimensions: DimensionConfig[];
86
  /**
87
   * An array of config objects. Each object is defining one column in the chart.
88
   *
89
   * **Required properties**
90
   * - `accessor`: string containing the path to the dataset key this column should display. Supports object structures by using <code>'parent.child'</code>.
91
   *   Can also be a getter.
92
   *
93
   * **Optional properties**
94
   *
95
   * - `label`: Label to display in legends or tooltips. Falls back to the <code>accessor</code> if not present.
96
   * - `color`: any valid CSS Color or CSS Variable. Defaults to the `sapChart_Ordinal` colors
97
   * - `formatter`: function will be called for each data label and allows you to format it according to your needs
98
   * - `hideDataLabel`: flag whether the data labels should be hidden in the chart for this column.
99
   * - `DataLabel`: a custom component to be used for the data label
100
   * - `width`: column width, defaults to `auto`
101
   * - `opacity`: column opacity, defaults to `1`
102
   * - `stackId`: columns with the same stackId will be stacked
103
   * - `highlightColor`: function will be called to define a custom color of a specific element which matches the
104
   *    defined condition. Overwrites code>color</code> of the element.
105
   *
106
   */
107
  measures: MeasureConfig[];
108
}
109

110
const dimensionDefaults = {
23✔
111
  formatter: defaultFormatter
112
};
113

114
const measureDefaults = {
23✔
115
  formatter: defaultFormatter,
116
  opacity: 1
117
};
118

119
const valueAccessor =
120
  (attribute) =>
23✔
121
  ({ payload }) => {
27✔
122
    return getValueByDataKey(payload, attribute);
×
123
  };
124

125
/**
126
 * A `ColumnChart` is a data visualization where each category is represented by a rectangle, with the height of the rectangle being proportional to the values being plotted.
127
 */
128
const ColumnChart = forwardRef<HTMLDivElement, ColumnChartProps>((props, ref) => {
23✔
129
  const {
130
    loading,
131
    loadingDelay,
132
    dataset,
133
    noLegend,
134
    noAnimation,
135
    tooltipConfig,
136
    onDataPointClick,
137
    onLegendClick,
138
    onClick,
139
    style,
140
    className,
141
    slot,
142
    ChartPlaceholder,
143
    syncId,
144
    children,
145
    ...rest
146
  } = props;
19✔
147

148
  const chartConfig: ColumnChartProps['chartConfig'] = {
14✔
149
    yAxisVisible: false,
150
    xAxisVisible: true,
151
    gridStroke: ThemingParameters.sapList_BorderColor,
152
    gridHorizontal: true,
153
    gridVertical: false,
154
    legendPosition: 'bottom',
155
    legendHorizontalAlign: 'left',
156
    barGap: 3,
157
    zoomingTool: false,
158
    resizeDebounce: 250,
159
    yAxisConfig: {},
160
    xAxisConfig: {},
161
    secondYAxisConfig: {},
162
    ...props.chartConfig
163
  };
164
  const { referenceLine } = chartConfig;
14✔
165

166
  const { dimensions, measures } = usePrepareDimensionsAndMeasures(
14✔
167
    props.dimensions,
168
    props.measures,
169
    dimensionDefaults,
170
    measureDefaults
171
  );
172

173
  const tooltipValueFormatter = useTooltipFormatter(measures);
14✔
174

175
  const [yAxisWidth, legendPosition] = useLongestYAxisLabel(dataset, measures, chartConfig.legendPosition);
14✔
176

177
  const primaryDimension = dimensions[0];
14✔
178
  const { primaryMeasure, secondaryMeasure } = resolvePrimaryAndSecondaryMeasures(
14✔
179
    measures,
180
    chartConfig?.secondYAxis?.dataKey
181
  );
182

183
  const labelFormatter = useLabelFormatter(primaryDimension);
14✔
184
  const [componentRef, chartRef] = useSyncRef<any>(ref);
14✔
185

186
  const dataKeys = measures.map(({ accessor }) => accessor);
27✔
187
  const colorSecondY = chartConfig.secondYAxis
14!
188
    ? dataKeys.findIndex((key) => key === chartConfig.secondYAxis?.dataKey)
×
189
    : 0;
190

191
  const onItemLegendClick = useLegendItemClick(onLegendClick);
14✔
192

193
  const onDataPointClickInternal = useCallback(
14✔
194
    (payload, eventOrIndex, event) => {
195
      if (payload && onDataPointClick) {
4!
196
        onDataPointClick(
×
197
          enrichEventWithDetails(event, {
198
            dataKey: Object.keys(payload).filter((key) =>
199
              payload.value.length
×
200
                ? payload[key] === payload.value[1] - payload.value[0]
201
                : payload[key] === payload.value && key !== 'value'
×
202
            )[0],
203
            value: payload.value.length ? payload.value[1] - payload.value[0] : payload.value,
×
204
            dataIndex: eventOrIndex,
205
            payload: payload.payload
206
          })
207
        );
208
      }
209
    },
210
    [onDataPointClick]
211
  );
212

213
  const onClickInternal = useOnClickInternal(onClick);
14✔
214

215
  const isBigDataSet = dataset?.length > 30;
14✔
216
  const primaryDimensionAccessor = primaryDimension?.accessor;
14✔
217

218
  const marginChart = useChartMargin(chartConfig.margin, chartConfig.zoomingTool);
14✔
219
  const xAxisHeights = useObserveXAxisHeights(chartRef, props.dimensions.length);
14✔
220
  const isRTL = useIsRTL(chartRef);
14✔
221
  const { chartConfig: _0, dimensions: _1, measures: _2, ...propsWithoutOmitted } = rest;
14✔
222

223
  const { isMounted, handleBarAnimationStart, handleBarAnimationEnd } = useCancelAnimationFallback(noAnimation);
14✔
224

225
  return (
14✔
226
    <ChartContainer
227
      dataset={dataset}
228
      loading={loading}
229
      loadingDelay={loadingDelay}
230
      Placeholder={ChartPlaceholder ?? ColumnChartPlaceholder}
28✔
231
      ref={componentRef}
232
      style={style}
233
      className={className}
234
      slot={slot}
235
      resizeDebounce={chartConfig.resizeDebounce}
236
      {...propsWithoutOmitted}
237
    >
238
      {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
239
      {/*// @ts-ignore:: todo not yet compatible with React19*/}
240
      <ColumnChartLib
241
        syncId={syncId}
242
        onClick={onClickInternal}
243
        stackOffset="sign"
244
        margin={marginChart}
245
        data={dataset}
246
        barGap={chartConfig.barGap}
247
        className={
248
          typeof onDataPointClick === 'function' || typeof onClick === 'function' ? 'has-click-handler' : undefined
42✔
249
        }
250
      >
251
        <CartesianGrid
252
          vertical={chartConfig.gridVertical}
253
          horizontal={chartConfig.gridHorizontal}
254
          stroke={chartConfig.gridStroke}
255
        />
256
        {chartConfig.xAxisVisible &&
28✔
257
          dimensions.map((dimension, index) => {
258
            return (
9✔
259
              <XAxis
260
                key={dimension.reactKey}
261
                dataKey={dimension.accessor}
262
                xAxisId={index}
263
                interval={dimension?.interval ?? (isBigDataSet ? 'preserveStart' : 0)}
9!
264
                tick={<XAxisTicks config={dimension} />}
265
                tickLine={index < 1}
266
                axisLine={index < 1}
267
                height={xAxisHeights[index]}
268
                allowDuplicatedCategory={index === 0}
269
                reversed={isRTL}
270
                {...chartConfig.xAxisConfig}
271
              />
272
            );
273
          })}
274
        <YAxis
275
          orientation={isRTL === true ? 'right' : 'left'}
14!
276
          axisLine={chartConfig.yAxisVisible}
277
          tickLine={tickLineConfig}
278
          yAxisId="left"
279
          interval={0}
280
          tick={<YAxisTicks config={primaryMeasure} />}
281
          width={yAxisWidth}
282
          {...chartConfig.yAxisConfig}
283
        />
284
        {chartConfig.secondYAxis?.dataKey && (
14!
285
          <YAxis
286
            dataKey={chartConfig.secondYAxis.dataKey}
287
            axisLine={{
288
              stroke: chartConfig.secondYAxis.color ?? `var(--sapChart_OrderedColor_${(colorSecondY % 11) + 1})`
×
289
            }}
290
            tick={
291
              <YAxisTicks
292
                config={secondaryMeasure}
293
                secondYAxisConfig={{
294
                  color: chartConfig.secondYAxis.color ?? `var(--sapChart_OrderedColor_${(colorSecondY % 11) + 1})`
×
295
                }}
296
              />
297
            }
298
            tickLine={{
299
              stroke: chartConfig.secondYAxis.color ?? `var(--sapChart_OrderedColor_${(colorSecondY % 11) + 1})`
×
300
            }}
301
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
302
            // @ts-ignore
303
            label={{ value: chartConfig.secondYAxis.name, offset: 2, angle: +90, position: 'center' }}
304
            orientation={isRTL === true ? 'left' : 'right'}
×
305
            yAxisId="right"
306
            interval={0}
307
            {...chartConfig.secondYAxisConfig}
308
          />
309
        )}
310
        {isMounted &&
28✔
311
          measures.map((element, index) => {
312
            return (
27✔
313
              <Column
314
                yAxisId={chartConfig.secondYAxis?.dataKey === element.accessor ? 'right' : 'left'}
27!
315
                stackId={element.stackId}
316
                fillOpacity={element.opacity}
317
                key={element.reactKey}
318
                name={element.label ?? element.accessor}
27!
319
                strokeOpacity={element.opacity}
320
                type="monotone"
321
                dataKey={element.accessor}
322
                fill={element.color ?? `var(--sapChart_OrderedColor_${(index % 11) + 1})`}
54✔
323
                stroke={element.color ?? `var(--sapChart_OrderedColor_${(index % 11) + 1})`}
54✔
324
                barSize={element.width}
325
                // eslint-disable-next-line @typescript-eslint/ban-ts-comment
326
                // @ts-ignore
327
                onClick={onDataPointClickInternal}
328
                isAnimationActive={!noAnimation}
329
                onAnimationStart={handleBarAnimationStart}
330
                onAnimationEnd={handleBarAnimationEnd}
331
              >
332
                <LabelList
333
                  data={dataset}
334
                  valueAccessor={valueAccessor(element.accessor)}
335
                  content={<ChartDataLabel config={element} chartType="column" position={'insideTop'} />}
336
                />
337
                {dataset.map((data, i) => {
338
                  return (
324✔
339
                    <Cell
340
                      key={i}
341
                      fill={getCellColors(element, data, index)}
342
                      stroke={getCellColors(element, data, index)}
343
                    />
344
                  );
345
                })}
346
              </Column>
347
            );
348
          })}
349
        {!noLegend && (
28✔
350
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
351
          // @ts-ignore
352
          <Legend
353
            verticalAlign={chartConfig.legendPosition}
354
            align={chartConfig.legendHorizontalAlign}
355
            onClick={onItemLegendClick}
356
            wrapperStyle={legendPosition}
357
          />
358
        )}
359
        {referenceLine && (
14!
360
          <ReferenceLine
361
            {...referenceLine}
362
            stroke={referenceLine?.color ?? referenceLine?.stroke}
×
363
            y={referenceLine?.value ?? referenceLine?.y}
×
364
            yAxisId={referenceLine?.yAxisId ?? 'left'}
×
365
            label={referenceLine?.label}
366
          />
367
        )}
368
        {/*ToDo: remove conditional rendering once `active` is working again (https://github.com/recharts/recharts/issues/2703)*/}
369
        {tooltipConfig?.active !== false && (
28✔
370
          <Tooltip
371
            cursor={tooltipFillOpacity}
372
            formatter={tooltipValueFormatter}
373
            contentStyle={tooltipContentStyle}
374
            labelFormatter={labelFormatter}
375
            {...tooltipConfig}
376
          />
377
        )}
378
        {chartConfig.zoomingTool && (
14!
379
          <Brush
380
            y={10}
381
            dataKey={primaryDimensionAccessor}
382
            tickFormatter={primaryDimension?.formatter}
383
            stroke={ThemingParameters.sapObjectHeader_BorderColor}
384
            travellerWidth={10}
385
            height={20}
386
          />
387
        )}
388
        {children}
389
      </ColumnChartLib>
390
    </ChartContainer>
391
  );
392
});
393

394
ColumnChart.displayName = 'ColumnChart';
23✔
395

396
export { ColumnChart };
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc