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

SAP / ui5-webcomponents-react / 9773373921

03 Jul 2024 07:05AM CUT coverage: 81.216% (-0.02%) from 81.233%
9773373921

Pull #6011

github

web-flow
Merge 6dc3b61b2 into c2c3730e7
Pull Request #6011: fix(ObjectStatus): remove `HTMLDivElement` from `onClick` type

2629 of 3826 branches covered (68.71%)

4743 of 5840 relevant lines covered (81.22%)

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

391
ColumnChart.displayName = 'ColumnChart';
23✔
392

393
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