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

SAP / ui5-webcomponents-react / 9396814913

06 Jun 2024 07:25AM CUT coverage: 88.613% (-0.03%) from 88.645%
9396814913

Pull #5887

github

web-flow
Merge d6e0f8157 into 2bba948ab
Pull Request #5887: chore: remove deprecated `AnalyticalCard` component

3071 of 4031 branches covered (76.18%)

5533 of 6244 relevant lines covered (88.61%)

65627.87 hits per line

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

89.47
/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
    dataset,
132
    noLegend,
133
    noAnimation,
134
    tooltipConfig,
135
    onDataPointClick,
136
    onLegendClick,
137
    onClick,
138
    style,
139
    className,
140
    slot,
141
    ChartPlaceholder,
142
    syncId,
143
    children,
144
    ...rest
145
  } = props;
19✔
146

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

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

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

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

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

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

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

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

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

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

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

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

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

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

390
ColumnChart.defaultProps = {
23✔
391
  noLegend: false,
392
  noAnimation: false
393
};
394

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

397
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