Skip to content

Commit

Permalink
Migrate victory-histogram to TypeScript (#2719)
Browse files Browse the repository at this point in the history
  • Loading branch information
KenanYusuf committed Jan 17, 2024
1 parent d7675fe commit 0d54830
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 128 deletions.
5 changes: 5 additions & 0 deletions .changeset/six-donuts-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"victory-histogram": patch
---

Migrate victory-histogram to TypeScript
3 changes: 3 additions & 0 deletions packages/victory-histogram/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"victory-core": "^36.8.2",
"victory-vendor": "^36.8.2"
},
"devDependencies": {
"victory-histogram": "*"
},
"peerDependencies": {
"react": ">=16.6.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getBarPosition } from "victory-bar";
import isEqual from "react-fast-compare";
import * as d3Array from "victory-vendor/d3-array";
import * as d3Scale from "victory-vendor/d3-scale";
import { VictoryHistogramProps } from "./victory-histogram";

const cacheLastValue = (func) => {
let called = false;
Expand All @@ -25,9 +26,14 @@ const cacheLastValue = (func) => {
};
};

const dataOrBinsContainDates = ({ data, bins, x }) => {
const dataOrBinsContainDates = ({
data,
bins,
x,
}: Pick<VictoryHistogramProps, "data" | "x" | "bins">) => {
const xAccessor = Helpers.createAccessor(x || "x");
const dataIsDates = data.some((datum) => xAccessor(datum) instanceof Date);
const dataIsDates =
data?.some((datum) => xAccessor(datum) instanceof Date) || false;
const binsHasDates =
Array.isArray(bins) && bins.some((bin) => bin instanceof Date);

Expand All @@ -39,7 +45,9 @@ const getBinningFunc = ({ data, x, bins, dataOrBinsContainsDates }) => {
const bin = d3Array.bin().value(xAccessor);

const niceScale = (
dataOrBinsContainsDates ? d3Scale.scaleTime() : d3Scale.scaleLinear()
(dataOrBinsContainsDates
? d3Scale.scaleTime()
: d3Scale.scaleLinear()) as any
)
.domain(d3Array.extent(data, xAccessor))
.nice();
Expand All @@ -61,59 +69,68 @@ const getBinningFunc = ({ data, x, bins, dataOrBinsContainsDates }) => {
if (dataOrBinsContainsDates) {
bin.domain(niceScale.domain());
bin.thresholds(niceScale.ticks());

return bin;
return bin as unknown as d3Array.HistogramGeneratorDate<Date, Date>;
}

bin.domain(niceScale.domain());

return bin;
};

export const getFormattedData = cacheLastValue(({ data = [], x, bins }) => {
if ((!data || !data.length) && !Array.isArray(bins)) {
return [];
}
const dataOrBinsContainsDates = dataOrBinsContainDates({ data, bins, x });
const binFunc = getBinningFunc({ data, x, bins, dataOrBinsContainsDates });
const foo = binFunc(data);
const binnedData = foo.filter(({ x0, x1 }) => {
if (dataOrBinsContainsDates) {
return new Date(x0).getTime() !== new Date(x1).getTime();
export const getFormattedData = cacheLastValue(
({
data = [],
x,
bins,
}: Pick<VictoryHistogramProps, "data" | "x" | "bins">) => {
if ((!data || !data.length) && !Array.isArray(bins)) {
return [];
}

return x0 !== x1;
});

const formattedData = binnedData.map((bin) => {
const x0 = dataOrBinsContainsDates ? new Date(bin.x0) : bin.x0;
const x1 = dataOrBinsContainsDates ? new Date(bin.x1) : bin.x1;

return {
x0,
x1,
x: dataOrBinsContainsDates
? new Date((x0.getTime() + x1.getTime()) / 2)
: (x0 + x1) / 2,
y: bin.length,
binnedData: [...bin],
};
});

return formattedData;
});

export const getData = (props) => {
const dataOrBinsContainsDates = dataOrBinsContainDates({ data, bins, x });
const binFunc = getBinningFunc({ data, x, bins, dataOrBinsContainsDates });
const foo = binFunc(data);
const binnedData = [...foo].filter(({ x0, x1 }) => {
if (x0 instanceof Date && x1 instanceof Date) {
return new Date(x0).getTime() !== new Date(x1).getTime();
}

return x0 !== x1;
});

const formattedData = binnedData.map((bin) => {
const x0 = dataOrBinsContainsDates
? new Date(bin.x0 as Date)
: bin.x0 || 0;
const x1 = dataOrBinsContainsDates
? new Date(bin.x1 as Date)
: bin.x1 || 0;

return {
x0,
x1,
x: dataOrBinsContainsDates
? new Date(((x0 as Date).getTime() + (x1 as Date).getTime()) / 2)
: ((x0 as number) + (x1 as number)) / 2,
y: bin.length,
binnedData: [...bin],
};
});

return formattedData;
},
);

export const getData = (props: VictoryHistogramProps) => {
const { bins, data, x } = props;
const dataIsPreformatted = data.some(({ _y }) => !isNil(_y));
const dataIsPreformatted = data?.some(({ _y }) => !isNil(_y));

const formattedData = dataIsPreformatted
? data
: getFormattedData({ data, x, bins });
return Data.getData({ ...props, data: formattedData, x: "x" });
};

export const getDomain = (props, axis) => {
export const getDomain = (props: VictoryHistogramProps, axis: "x" | "y") => {
const data = getData(props);

if (!data.length) {
Expand All @@ -130,7 +147,7 @@ export const getDomain = (props, axis) => {
);
}

return props.data.length
return props.data?.length
? Domain.getDomainWithZero({ ...props, data }, "y")
: [0, 1];
};
Expand Down
46 changes: 0 additions & 46 deletions packages/victory-histogram/src/index.d.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/victory-histogram/src/index.js

This file was deleted.

1 change: 1 addition & 0 deletions packages/victory-histogram/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./victory-histogram";
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ describe("components/victory-histogram", () => {
it("renders an svg with the correct width and height", () => {
const { container } = render(<VictoryHistogram />);
const svg = container.querySelector("svg");
expect(svg.getAttribute("style")).toContain("width: 100%; height: 100%");
expect(svg?.getAttribute("style")).toContain("width: 100%; height: 100%");
});

it("renders an svg with the correct viewBox", () => {
const { container } = render(<VictoryHistogram />);
const svg = container.querySelector("svg");
const viewBoxValue = `0 0 ${450} ${300}`;
expect(svg.getAttribute("viewBox")).toEqual(viewBoxValue);
expect(svg?.getAttribute("viewBox")).toEqual(viewBoxValue);
});

it("renders 0 bars", () => {
Expand Down Expand Up @@ -94,6 +94,7 @@ describe("components/victory-histogram", () => {
render(
<VictoryHistogram
data={data}
// @ts-expect-error "'null' is not assignable to 'x'"
x={null}
y={null}
dataComponent={<DataComponent />}
Expand Down Expand Up @@ -139,7 +140,7 @@ describe("components/victory-histogram", () => {
/>,
);
const svg = container.querySelector("svg");
fireEvent.click(svg);
fireEvent.click(svg!);

expect(clickHandler).toHaveBeenCalled();
// the first argument is the standard event object
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import React from "react";
import PropTypes from "prop-types";
import { Bar } from "victory-bar";
import {
Helpers,
VictoryLabel,
VictoryContainer,
VictoryTheme,
CommonProps,
addEvents,
PropTypes as CustomPropTypes,
UserProps,
EventPropTypeInterface,
NumberOrCallback,
StringOrNumberOrCallback,
VictoryCommonProps,
VictoryDatableProps,
VictoryMultiLabelableProps,
VictoryStyleInterface,
EventsMixinClass,
} from "victory-core";
import {
getBaseProps,
Expand All @@ -18,15 +23,50 @@ import {
getFormattedData,
} from "./helper-methods";

const fallbackProps = {
export type VictoryHistogramTargetType = "data" | "labels" | "parent";

export interface VictoryHistogramProps
extends Omit<VictoryCommonProps, "polar">,
Omit<VictoryDatableProps, "y" | "y0">,
VictoryMultiLabelableProps {
binSpacing?: number;
bins?: number | number[] | Date[];
cornerRadius?:
| NumberOrCallback
| {
top?: NumberOrCallback;
topLeft?: NumberOrCallback;
topRight?: NumberOrCallback;
bottom?: NumberOrCallback;
bottomLeft?: NumberOrCallback;
bottomRight?: NumberOrCallback;
};
events?: EventPropTypeInterface<
VictoryHistogramTargetType,
number | string | number[] | string[]
>[];
eventKey?: StringOrNumberOrCallback;
horizontal?: boolean;
style?: VictoryStyleInterface;
}

const fallbackProps: Partial<VictoryHistogramProps> = {
width: 450,
height: 300,
padding: 50,
};

const defaultData = [];

export class VictoryHistogram extends React.Component {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface VictoryHistogramBase
extends EventsMixinClass<VictoryHistogramProps> {}

/**
* Draw SVG histogram charts with React. VictoryHistogram is a composable component, so it doesn't include axes
* Check out VictoryChart for complete histogram charts and more.
*/
class VictoryHistogramBase extends React.Component<VictoryHistogramProps> {
static animationWhitelist = [
"data",
"domain",
Expand Down Expand Up @@ -59,33 +99,7 @@ export class VictoryHistogram extends React.Component {

static getFormattedData = getFormattedData;

static propTypes = {
...CommonProps.baseProps,
...CommonProps.dataProps,
binSpacing: CustomPropTypes.nonNegative,
bins: PropTypes.oneOfType([
PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.number, PropTypes.instanceOf(Date)]),
),
CustomPropTypes.nonNegative,
]),
cornerRadius: PropTypes.oneOfType([
PropTypes.number,
PropTypes.func,
PropTypes.shape({
top: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
topLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
topRight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
bottom: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
bottomLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
bottomRight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
}),
]),
getPath: PropTypes.func,
horizontal: PropTypes.bool,
};

static defaultProps = {
static defaultProps: VictoryHistogramProps = {
containerComponent: <VictoryContainer />,
data: defaultData,
dataComponent: <Bar />,
Expand All @@ -99,8 +113,9 @@ export class VictoryHistogram extends React.Component {

static getDomain = getDomain;
static getData = getData;
static getBaseProps = (props) => getBaseProps(props, fallbackProps);
static expectedComponents = [
static getBaseProps = (props: VictoryHistogramProps) =>
getBaseProps(props, fallbackProps);
static expectedComponents: Partial<keyof VictoryHistogramProps>[] = [
"dataComponent",
"labelComponent",
"groupComponent",
Expand All @@ -112,8 +127,8 @@ export class VictoryHistogram extends React.Component {
return !!this.props.animate;
}

render() {
const { animationWhitelist, role } = VictoryHistogram;
render(): React.ReactElement {
const { animationWhitelist, role } = VictoryHistogramBase;
const props = Helpers.modifyProps(this.props, fallbackProps, role);

if (this.shouldAnimate()) {
Expand All @@ -130,4 +145,4 @@ export class VictoryHistogram extends React.Component {
}
}

export default addEvents(VictoryHistogram);
export const VictoryHistogram = addEvents(VictoryHistogramBase);

1 comment on commit 0d54830

@vercel
Copy link

@vercel vercel bot commented on 0d54830 Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.