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

SAP / ui5-webcomponents-react / 9838204332

08 Jul 2024 10:49AM CUT coverage: 80.782% (-0.05%) from 80.836%
9838204332

Pull #6039

github

web-flow
Merge aca8815b9 into b908544e7
Pull Request #6039: refactor(AnalyticalTable): remove unnecessary `portalContainer` prop

2606 of 3822 branches covered (68.18%)

2 of 2 new or added lines in 1 file covered. (100.0%)

2 existing lines in 2 files now uncovered.

4708 of 5828 relevant lines covered (80.78%)

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

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

395
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