Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,11 @@
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/d3-scale": "^4.0.9",
"@types/jest": "^30.0.0",
"@types/jest-axe": "^3.5.9",
"@types/lodash.clamp": "^4.0.9",
"@types/lodash.debounce": "^4.0.9",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@types/react-transition-group": "^4.4.11",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* limitations under the License.
*/

/* @flow strict */


import BpkBarchart from './src/BpkBarchart';
import BpkBarchartBars from './src/BpkBarchartBars';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* limitations under the License.
*/

/* @flow strict */


import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
Expand Down Expand Up @@ -86,7 +86,7 @@ describe('BpkBarchart', () => {
initialWidth={size}
initialHeight={size}
data={prices}
getBarLabel={() => null}
getBarLabel={() => ''}
/>,
);
expect(asFragment()).toMatchSnapshot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
* limitations under the License.
*/

/* @flow strict */

import PropTypes from 'prop-types';
import type { ComponentType, MouseEvent, FocusEvent } from 'react';
import { Component } from 'react';

import { scaleLinear, scaleBand } from 'd3-scale';
Expand Down Expand Up @@ -46,68 +45,70 @@ import dataProp from './customPropTypes';
import { ORIENTATION_X, ORIENTATION_Y } from './orientation';
import { identity, remToPx } from './utils';

import type { DataPoint, BarComponentProps, TickValueFn } from './types';
import type { ScaleBand, ScaleLinear } from 'd3-scale';

import STYLES from './BpkBarchart.module.scss';

const getClassName = cssModules(STYLES);

const spacing = remToPx('.375rem');
const lineHeight = remToPx(lineHeightSm);

const getMaxYValue = (
dataPoints: Array<number>,
outlierPercentage: ?number,
) => {
const meanValue = dataPoints.reduce((d, t) => d + t, 0) / dataPoints.length;
const maxYValue = Math.max(...dataPoints);

return outlierPercentage
? Math.min(maxYValue, meanValue * (outlierPercentage / 100) + meanValue)
: maxYValue;
};

type Props = {
data: Array<any>, // We pass any here as the array can contain free form data depending on the user
const defaultGetBarLabel = (
point: DataPoint,
xScaleDataKey: string,
yScaleDataKey: string,
xAxisLabel: string,
yAxisLabel: string,
initialWidth: number,
initialHeight: number,
leadingScrollIndicatorClassName: ?string,
trailingScrollIndicatorClassName: ?string,
outlierPercentage: ?number,
showGridlines: boolean,
xAxisMargin: number,
xAxisTickValue: () => mixed,
xAxisTickOffset: number,
xAxisTickEvery: number,
yAxisMargin: number,
yAxisTickValue: () => mixed,
yAxisNumTicks: ?number,
yAxisDomain: Array<?number>,
onBarClick: ?() => mixed,
onBarHover: ?() => mixed,
onBarFocus: ?() => mixed,
getBarLabel: (any, string, string) => ?string,
getBarSelection: () => mixed,
BarComponent: typeof BpkBarchartBar,
disableDataTable: boolean,
) => `${point[xScaleDataKey]} - ${point[yScaleDataKey]}`;

type Props = {
data: DataPoint[];
xScaleDataKey: string;
yScaleDataKey: string;
xAxisLabel: string;
yAxisLabel: string;
initialWidth: number;
initialHeight: number;
leadingScrollIndicatorClassName?: string | null;
trailingScrollIndicatorClassName?: string | null;
outlierPercentage?: number | null;
showGridlines?: boolean;
xAxisMargin?: number;
xAxisTickValue?: TickValueFn;
xAxisTickOffset?: number;
xAxisTickEvery?: number;
yAxisMargin?: number;
yAxisTickValue?: TickValueFn;
yAxisNumTicks?: number | null;
yAxisDomain?: Array<number | null>;
onBarClick?: ((event: MouseEvent, dataPoint: DataPoint) => void) | null;
onBarHover?: ((event: MouseEvent, dataPoint: DataPoint) => void) | null;
onBarFocus?: ((event: FocusEvent, dataPoint: DataPoint) => void) | null;
getBarLabel?: (point: DataPoint, xScaleDataKey: string, yScaleDataKey: string) => string;
getBarSelection?: (point: DataPoint) => boolean;
BarComponent?: ComponentType<BarComponentProps>;
disableDataTable?: boolean;
};

type State = {
width: number,
height: number,
width: number;
height: number;
};

class BpkBarchart extends Component<Props, State> {
xScale: typeof scaleBand;

yScale: typeof scaleLinear;

onWindowResize: () => mixed;
const getMaxYValue = (
dataPoints: number[],
outlierPercentage: number | null | undefined,
): number => {
const meanValue = dataPoints.reduce((d: number, t: number) => d + t, 0) / dataPoints.length;
const maxYValue = Math.max(...dataPoints);

svgEl: ?Element;
return outlierPercentage
? Math.min(maxYValue, meanValue * (outlierPercentage / 100) + meanValue)
: maxYValue;
};

class BpkBarchart extends Component<Props, State> {
// eslint-disable-next-line react/sort-comp
static defaultProps = {
leadingScrollIndicatorClassName: null,
trailingScrollIndicatorClassName: null,
Expand All @@ -124,14 +125,21 @@ class BpkBarchart extends Component<Props, State> {
onBarClick: null,
onBarHover: null,
onBarFocus: null,
// Using type any here as xScaleDataKey or yScaleDataKey are strings and there is an issue that strings are not valid keys
getBarLabel: (point: any, xScaleDataKey: string, yScaleDataKey: string) =>
`${point[xScaleDataKey]} - ${point[yScaleDataKey]}`,
getBarLabel: defaultGetBarLabel,
getBarSelection: () => false,
BarComponent: BpkBarchartBar,
disableDataTable: false,
};

xScale: ScaleBand<string>;

yScale: ScaleLinear<number, number>;

onWindowResize: ReturnType<typeof debounce>;

// eslint-disable-next-line react/sort-comp
svgEl: SVGSVGElement | null = null;

constructor(props: Props) {
super(props);

Expand Down Expand Up @@ -199,27 +207,28 @@ class BpkBarchart extends Component<Props, State> {
const transformedData = applyArrayRTLTransform(data);
const margin = applyMarginRTLTransform({
top: spacing,
left: yAxisMargin,
left: yAxisMargin ?? 0,
right: 0,
bottom: xAxisMargin,
bottom: xAxisMargin ?? 0,
});

const width = this.state.width - margin.left - margin.right;
const height = this.state.height - margin.bottom - margin.top;
const maxYValue = getMaxYValue(
data.map((d) => d[yScaleDataKey]),
data.map((d) => d[yScaleDataKey] as number),
outlierPercentage,
);

this.xScale.rangeRound([0, width]);
this.xScale.domain(transformedData.map((d) => d[xScaleDataKey]));
this.xScale.domain(transformedData.map((d) => d[xScaleDataKey] as string));
this.yScale.rangeRound([height, 0]);
this.yScale.domain([yAxisDomain[0] || 0, yAxisDomain[1] || maxYValue]);
const domain = yAxisDomain ?? [null, null];
this.yScale.domain([domain[0] || 0, domain[1] || maxYValue]);

return (
<BpkMobileScrollContainer
leadingIndicatorClassName={leadingScrollIndicatorClassName}
trailingIndicatorClassName={trailingScrollIndicatorClassName}
leadingIndicatorClassName={leadingScrollIndicatorClassName ?? undefined}
trailingIndicatorClassName={trailingScrollIndicatorClassName ?? undefined}
>
{!disableDataTable && (
<BpkChartDataTable
Expand Down Expand Up @@ -286,9 +295,9 @@ class BpkBarchart extends Component<Props, State> {
onBarClick={onBarClick}
onBarHover={onBarHover}
onBarFocus={onBarFocus}
getBarLabel={getBarLabel}
getBarLabel={getBarLabel ?? defaultGetBarLabel}
getBarSelection={getBarSelection}
BarComponent={BarComponent}
BarComponent={BarComponent ?? BpkBarchartBar}
/>
</BpkChartMargin>
</svg>
Expand All @@ -297,6 +306,7 @@ class BpkBarchart extends Component<Props, State> {
}
}

// @ts-expect-error - propTypes are kept for backwards compatibility
BpkBarchart.propTypes = {
/**
* **Required**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* limitations under the License.
*/

/* @flow strict */


import { render } from '@testing-library/react';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@
* limitations under the License.
*/

/* @flow strict */

import PropTypes from 'prop-types';
import type { KeyboardEvent } from 'react';

import { borderRadiusXs } from '@skyscanner/bpk-foundations-web/tokens/base.es6';

import { cssModules } from '../../bpk-react-utils';

import { remToPx } from './utils';

import type { BarComponentProps } from './types';

import STYLES from './BpkBarchartBar.module.scss';

const getClassName = cssModules(STYLES);
Expand All @@ -35,7 +36,7 @@ const KEYCODES = {
SPACEBAR: 32,
};

const handleKeyboardEvent = (callback) => (event) => {
const handleKeyboardEvent = (callback: (event: KeyboardEvent) => void) => (event: KeyboardEvent) => {
if (event.keyCode === KEYCODES.ENTER || event.keyCode === KEYCODES.SPACEBAR) {
event.preventDefault();
callback(event);
Expand All @@ -44,20 +45,9 @@ const handleKeyboardEvent = (callback) => (event) => {

const borderRadius = remToPx(borderRadiusXs);

type Props = {
height: number,
label: string,
width: number,
x: number,
y: number,
className: ?string,
onClick: ?(?any) => mixed,
onHover: ?() => mixed,
onFocus: ?() => mixed,
outlier: boolean,
padding: number,
selected: boolean,
};
interface Props extends BarComponentProps {
className?: string | null;
}

const BpkBarchartBar = (props: Props) => {
const {
Expand Down Expand Up @@ -91,11 +81,11 @@ const BpkBarchartBar = (props: Props) => {
);

const isAriaPressed = !!(onClick && selected);
const rectPadding = width * (padding / 2);
const rectWidth = width * (1 - padding);
const rectPadding = width * ((padding ?? 0) / 2);
const rectWidth = width * (1 - (padding ?? 0));

return (
<g className={classNames} transform={`translate(${x}, ${y})`}>
<g className={classNames} transform={`translate(${x ?? 0}, ${y})`}>
{/* $FlowFixMe[cannot-spread-inexact] - inexact rest. See 'decisions/flowfixme.md'. */}
<rect
className={rectClassNames}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* limitations under the License.
*/

/* @flow strict */


// TODO: remove this once we update the Chart implementation to accept values
// other than pixels
Expand Down
Loading
Loading