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

SAP / ui5-webcomponents-react / 7045436957

30 Nov 2023 10:52AM CUT coverage: 87.839% (+0.03%) from 87.809%
7045436957

Pull #5258

github

web-flow
Merge f1154a682 into cb55a0764
Pull Request #5258: feat(ExpandableText): introduce component

2846 of 3803 branches covered (0.0%)

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

2 existing lines in 2 files now uncovered.

5150 of 5863 relevant lines covered (87.84%)

22671.46 hits per line

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

85.0
/packages/charts/src/components/BarChart/BarChart.tsx
1
'use client';
2

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

41
const dimensionDefaults = {
23✔
42
  formatter: defaultFormatter
43
};
44

45
const measureDefaults = {
23✔
46
  formatter: defaultFormatter,
47
  opacity: 1
48
};
49

50
const valueAccessor =
51
  (attribute) =>
23✔
52
  ({ payload }) => {
27✔
UNCOV
53
    return getValueByDataKey(payload, attribute);
×
54
  };
55

56
interface MeasureConfig extends IChartMeasure {
57
  /**
58
   * Bar Width
59
   * @default auto
60
   */
61
  width?: number;
62
  /**
63
   * Bar Opacity
64
   * @default 1
65
   */
66
  opacity?: number;
67
  /**
68
   * Bar Stack ID
69
   * @default undefined
70
   */
71
  stackId?: string;
72
  /**
73
   * Highlight color of defined elements
74
   * @param value {string | number} Current value of the highlighted measure
75
   * @param measure {IChartMeasure} Current measure object
76
   * @param dataElement {object} Current data element
77
   */
78
  highlightColor?: (value: number, measure: MeasureConfig, dataElement: Record<string, any>) => CSSProperties['color'];
79
}
80

81
interface DimensionConfig extends IChartDimension {
82
  /**
83
   * Interval of dimension axis labels
84
   * @default 0
85
   */
86
  interval?: number;
87
}
88

89
export interface BarChartProps extends IChartBaseProps {
90
  /**
91
   * An array of config objects. Each object will define one dimension of the chart.
92
   *
93
   * **Required Properties**
94
   * - `accessor`: string containing the path to the dataset key the dimension should display. Supports object structures by using <code>'parent.child'</code>.
95
   *   Can also be a getter.
96
   *
97
   * **Optional Properties**
98
   * - `formatter`: function will be called for each data label and allows you to format it according to your needs
99
   * - `interval`: number that controls how many ticks are rendered on the x axis
100
   *
101
   */
102
  dimensions: DimensionConfig[];
103
  /**
104
   * An array of config objects. Each object is defining one bar in the chart.
105
   *
106
   * **Required properties**
107
   * - `accessor`: string containing the path to the dataset key this bar should display. Supports object structures by using <code>'parent.child'</code>.
108
   *   Can also be a getter.
109
   *
110
   * **Optional properties**
111
   *
112
   * - `label`: Label to display in legends or tooltips. Falls back to the <code>accessor</code> if not present.
113
   * - `color`: any valid CSS Color or CSS Variable. Defaults to the `sapChart_OrderedColor_` colors
114
   * - `formatter`: function will be called for each data label and allows you to format it according to your needs
115
   * - `hideDataLabel`: flag whether the data labels should be hidden in the chart for this bar.
116
   * - `DataLabel`: a custom component to be used for the data label
117
   * - `width`: bar width, defaults to `auto`
118
   * - `opacity`: bar opacity, defaults to `1`
119
   * - `stackId`: bars with the same stackId will be stacked
120
   * - `highlightColor`: function will be called to define a custom color of a specific element which matches the
121
   *    defined condition. Overwrites code>color</code> of the element.
122
   *
123
   */
124
  measures: MeasureConfig[];
125
}
126

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

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

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

175
  const tooltipValueFormatter = useTooltipFormatter(measures);
14✔
176

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

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

188
  const [componentRef, chartRef] = useSyncRef<any>(ref);
14✔
189

190
  const onItemLegendClick = useLegendItemClick(onLegendClick);
14✔
191
  const labelFormatter = useLabelFormatter(primaryDimension);
14✔
192

193
  const onDataPointClickInternal = useCallback(
14✔
194
    (payload, i, event) => {
195
      if (payload && onDataPointClick) {
4!
196
        const value = payload.value.length ? payload.value[1] - payload.value[0] : payload.value;
×
197
        onDataPointClick(
×
198
          enrichEventWithDetails(event, {
199
            dataKey: Object.keys(payload)
200
              .filter((key) => key !== 'value')
×
201
              .find((key) => payload[key] === value),
×
202
            value,
203
            payload: payload.payload,
204
            dataIndex: i
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 [width, legendPosition] = useLongestYAxisLabelBar(dataset, dimensions, chartConfig.legendPosition);
14✔
218
  const marginChart = useChartMargin(chartConfig.margin, chartConfig.zoomingTool);
14✔
219
  const [xAxisHeight] = useObserveXAxisHeights(chartRef, 1);
14✔
220
  const isRTL = useIsRTL(chartRef);
14✔
221

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

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

395
BarChart.defaultProps = {
23✔
396
  noLegend: false,
397
  noAnimation: false
398
};
399

400
BarChart.displayName = 'BarChart';
23✔
401

402
export { BarChart };
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