Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turbo: data aggregator #2441

Closed
wants to merge 52 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
6aadfc9
feature(turbo): created DataAggregator
Aug 16, 2022
701ca4d
Merge remote-tracking branch 'origin/main' into turbo/data-aggregator
Aug 22, 2022
00ce819
Merge remote-tracking branch 'origin/main' into turbo/data-aggregator
Aug 23, 2022
6195681
feature(turbo): moved lazy-data-aggregator to separate file
Aug 23, 2022
8ca79ea
feature(turbo): refactored data-aggregator into data-selector, and ad…
Aug 23, 2022
7dac7f6
feature(turbo): fixed indents
Aug 24, 2022
2ff1cb9
feature(turbo): extracted selectors to separate file
Aug 24, 2022
b043def
feature(turbo): added a glossary to take notes about certain terms
Aug 25, 2022
b3a6970
feature(turbo): added `scale` and `range` selectors
Aug 25, 2022
4d7b503
feature(turbo): folder organization
Aug 26, 2022
667ab40
feature(turbo): added VicParent, VicChild
Aug 26, 2022
d51da2a
feature(turbo): created "Nestable" component, which replaces the VicP…
Aug 26, 2022
e8a80d0
feature(turbo): added `defaultProps` and `propTypes` configs
Aug 26, 2022
8070a5d
feature(turbo): removed obsolete vic-child/vic-parent
Aug 29, 2022
08b5b99
feature(turbo): Added `createVictoryComponent` API design
Aug 29, 2022
e9eaa61
feature(turbo): use `props` when normalizing
Aug 29, 2022
559506f
feature(turbo): use object-notation syntax for normalized/aggregated …
Aug 29, 2022
8f5221a
feature(turbo): added example story
Aug 29, 2022
cc09280
feature(turbo): added strong types to makeNestable!
Aug 30, 2022
9ae7978
feature(turbo): added unit tests for createVictoryComponent
Aug 30, 2022
dedc553
feature(turbo): configure Jest to load TSX files
Aug 30, 2022
3efe494
feature(turbo): minor type improvements
Aug 30, 2022
7f5e197
feature(turbo): renamed to nestable-component.tsx
Aug 30, 2022
066cc61
feature(turbo): removed obsolete code
Aug 30, 2022
5b953f8
feature(turbo): removed obsolete code
Aug 30, 2022
8463755
feature(turbo): added aggregate data memoization
Aug 30, 2022
93518b9
feature(turbo): simplified memo implementation
Aug 30, 2022
35c874b
feature(turbo): improved types for aggregator functions
Aug 30, 2022
05c832a
feature(turbo): moved all victory-component into victory-core
Aug 30, 2022
9b692dc
feature(turbo): moved all victory-component into victory-core
Aug 30, 2022
f1f7ad0
feature(turbo): implemented AggregateProps and NormalizeProps common …
Aug 31, 2022
f8d4f39
feature(turbo): Implemented VictoryBar using new createVictoryComponent
Aug 31, 2022
37b4e94
feature(turbo): split out NestableParent and NestableComponent
Sep 1, 2022
6fa4031
feature(turbo): added tests for mixed-component aggregation
Sep 1, 2022
2ea9e42
feature(turbo): moved NestableParent to top of file
Sep 1, 2022
c9ec42a
feature(turbo): added `TurboContainer` logic
Sep 1, 2022
ec84540
feature(turbo): renamed files to match source
Sep 1, 2022
7d49372
feature(turbo): improved nestable test to allow React configs
Sep 1, 2022
e0a6403
feature(turbo): added some Turbo data types
Sep 2, 2022
19c3c97
feature(turbo): extracted example code to examples/vicLine
Sep 2, 2022
f98e19a
feature(turbo): added an example VicChart
Sep 2, 2022
49d019c
feature(turbo): added CommonProps defaults
Sep 2, 2022
baa4219
feature(turbo): build before starting Storybook to prevent thrashing
Sep 2, 2022
5cd9a80
feature(turbo): fixed circular AggregateProps type issue
Sep 2, 2022
d4dd0dc
feature(turbo): removed unused fill prop
Sep 2, 2022
9fa25e3
feature(turbo): copied vic-container
Sep 4, 2022
34ed234
feature(turbo): naïve port of vic-container to hooks
Sep 4, 2022
b96d19c
feature(turbo): added types to line helpers
Sep 5, 2022
3043ac2
feature(turbo): don't pass turbo container props into implementation
Sep 5, 2022
8f7a2d7
Merge remote-tracking branch 'origin/main' into turbo/data-aggregator
Sep 5, 2022
b9f4fe4
Merge remote-tracking branch 'origin/main' into turbo/data-aggregator
Nov 16, 2023
d0e93fd
feature(turbo): updated lockfile
Nov 16, 2023
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@
"types:create": "wireit",
"storybook:build": "wireit",
"build-storybook": "pnpm run build:lib:esm && pnpm exec build-storybook",
"storybook:server": "concurrently --raw \"pnpm:build:lib:esm --watch\" \"start-storybook -p 6006\"",
"storybook:server": "pnpm build:lib:esm && concurrently --raw \"pnpm:build:lib:esm --watch\" \"start-storybook -p 6006\"",
carbonrobot marked this conversation as resolved.
Show resolved Hide resolved
"storybook": "pnpm run storybook:server",
"chromatic": "wireit",
"chromatic:ci": "wireit",
"sync": "wireit",
Expand Down
30 changes: 30 additions & 0 deletions packages/victory-bar/src/v37/victory-bar-v3.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-disable react/no-multi-comp */
import * as React from "react";
import VictoryBar from "./victory-bar-v3";
import VictoryChart from "victory-chart/es/v37/victory-chart";

export default {
title: "v37/VictoryBarV3",
component: VictoryBar,
};

export const Demo = (props) => {
return (
<svg>
<VictoryBar {...props} />{" "}
</svg>
);
};

Demo.args = {
barRatio: 0.5,
};

// NOT WORKING YET:
const WithChart = (props) => {
return (
<VictoryChart {...props}>
<VictoryBar />
</VictoryChart>
);
};
106 changes: 106 additions & 0 deletions packages/victory-bar/src/v37/victory-bar-v3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* eslint-disable react/no-multi-comp */
import React from "react";
import {
Datum,
NumberOrCallback,
VictoryCommonProps,
VictoryDatableProps,
} from "victory-core";
import {
createTurboComponent,
AggregateProps,
NormalizeProps,
TurboContainerProps,
} from "victory-core/es/v37/victory-component";

import Bar from "../bar";
import { BarProps } from "..";
import { getBarPosition } from "../helper-methods";

export type VictoryBarAlignmentType = "start" | "middle" | "end";
export interface VictoryBarProps
extends VictoryCommonProps,
VictoryDatableProps,
TurboContainerProps {
alignment: VictoryBarAlignmentType;
barRatio?: number;
barWidth?: NumberOrCallback;
cornerRadius?:
| NumberOrCallback
| {
top?: NumberOrCallback;
topLeft?: NumberOrCallback;
topRight?: NumberOrCallback;
bottom?: NumberOrCallback;
bottomLeft?: NumberOrCallback;
bottomRight?: NumberOrCallback;
};
data: Datum[];
dataComponent: React.ReactElement;
groupComponent: React.ReactElement;
horizontal: boolean;
}

export const VictoryBarV3 = createTurboComponent<VictoryBarProps>()(
{
displayName: "VictoryBar",
propTypes: {},
defaultProps: {
alignment: "middle",
data: [
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
{ x: 4, y: 4 },
],
dataComponent: <Bar />,
groupComponent: <g role="presentation" />,
horizontal: false,
sortOrder: "ascending",
},
normalizeProps: {
...NormalizeProps,
},
aggregateProps: {
...AggregateProps,
},
},
(props) => {
const {
data,
domain,
scale,
horizontal,
alignment,
barRatio,
barWidth,
cornerRadius,
dataComponent,
groupComponent,
} = props;

const barPositionProps = { domain, scale, horizontal };

const children = data.map((datum: Datum, i: number) => {
const { x, y, y0 } = getBarPosition(barPositionProps, datum);
const dataProps: BarProps & { key: string } = {
index: i,
key: `bar-${i}`,
alignment,
barRatio,
barWidth,
cornerRadius,
scale,
data,
x,
y,
y0,
datum,
};
return React.cloneElement(dataComponent, dataProps);
});
return React.cloneElement(groupComponent, {}, children);
},
);

export default VictoryBarV3;
1 change: 1 addition & 0 deletions packages/victory-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"license": "MIT",
"dependencies": {
"lodash": "^4.17.21",
"memoize-weak": "^1.0.2",
"prop-types": "^15.8.1",
"react-fast-compare": "^3.2.0",
"victory-vendor": "^36.6.12"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Victory Turbo API

This is a design doc, for formulating + illustrating a working approach for "Victory Turbo" components.
This is a WIP.

# `createTurboComponent`

Here's how we'd define a component:

```tsx
import { createTurboComponent } from "./create-turbo-component";

export const VictoryLine = createTurboComponent(
{
// Standard React types:
displayName: "VictoryLine",
defaultProps: {
curve: "linear",
data: [ 1, 2, 3, 4 ],
},
propTypes: {
curve: PropTypes.oneOfType([ PropTypes.oneOf("linear", "smooth") ]),
},

// Map and normalize prop names:
normalizeProps: {
data: NormalizeProps.data,
curve: (props: { curve: "linear" | "smooth" | CurveFn }) => {
return typeof props.curve === "function"
? props.curve
: curveMethods[props.curve] || curveMethods.linear;
},
},

// Aggregate props, derived from all nested components:
aggregateProps: {
domain: AggregateProps.domain,
scale: AggregateProps.scale,
range: AggregateProps.range,
polar: (allComponents, props, memo) =>
props.polar || allComponents.some((c) => c.props.polar),
},
},
(props) => {
// This is where we put the actual component rendering

// All these props are now normalized:
const { curve, data, domain, scale, range, polar } = props;
// Implementation details here...
return <line {...props} />;
},
);
```

- The standard React config gets copied to the component (eg. `displayName`, `defaultProps`, and `propTypes`)
- The `normalizeProps` will all get computed BEFORE rendering the component, and passed into the component
- The `aggregateProps` will be computed as follows:
- During render, all children will traversed, and their props will be normalized and "collected"
- All collected + normalized props will be used to calculate the `aggregateProps`
- The `aggregateProps` will be passed into the component
- A caching/memoizing mechanism will be used to ensure multiple components can "share" aggregated results. Eg. we only calculate `domain` once for all components.
- The component's implementation will receive all props from the raw props, normalized props, and aggregate props, combined.


Example use-cases (stays the same as current API):
```tsx
<>
<VictoryLine />
<VictoryLine curve="smooth" />
<VictoryLine polar />
<Victorychart>
<VictoryLine />
<VictoryLine />
</Victorychart>
</>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from "react";
import { createTurboComponent } from "./create-turbo-component";
import { TurboContainerProps } from "./with-turbo-container";
import { VictoryContainer } from "../../../victory-container/victory-container";

export default {
title: "v37/createTurboComponent",
};

type ExampleProps = React.PropsWithChildren<
{
title: string;
optionalProp?: boolean;
defaultedProp: boolean;
} & TurboContainerProps
>;

const ExampleComponent = createTurboComponent<ExampleProps>()(
{
displayName: "ExampleComponent",
propTypes: {},
defaultProps: {
title: "Default Title",
defaultedProp: true,
containerComponent: <VictoryContainer />,
},
normalizeProps: {
TITLE: (props) => props.title.toUpperCase(),
},
aggregateProps: {
totalCount: (myProps, allProps) => allProps.length,
titles: (myProps, allProps) =>
allProps.map((props) => (props as ExampleProps).title).join(", "),
TITLES: (myProps, allProps) =>
allProps.map((props) => (props as { TITLE: string }).TITLE).join(", "),
},
},
({ children, ...props }) => {
const {
title,
TITLE,
optionalProp,
defaultedProp,
totalCount,
titles,
TITLES,
// @ts-expect-error INVALID
INVALID,
} = props;

return (
<g>
<text>{props.title}</text>
<text>{JSON.stringify(props, null, 2)}</text>
<>{children}</>
</g>
);
},
);

export const Example = () => {
return (
<ExampleComponent title="Parent" optionalProp>
<ExampleComponent title="Child">
<ExampleComponent title="Grandchild" />
</ExampleComponent>
<ExampleComponent title="Child" />
</ExampleComponent>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from "react";
import { render } from "@testing-library/react";
import { createTurboComponent } from "./create-turbo-component";
import { TurboContainerProps } from "./with-turbo-container";
import { VictoryContainer, LineHelpers, Path } from "../../../index";
import { TurboDataProps } from "../utils/props";
import { Clone } from "../../clone";
import { AggregateProps, NormalizeProps } from "../utils/aggregate-props";

describe("createTurboComponent", () => {
interface VicExampleProps<TDatum = any>
extends TurboContainerProps,
TurboDataProps<TDatum> {
title: string;
fill: string;
}
const VicExample = createTurboComponent<VicExampleProps>()(
{
displayName: "VicExample",
propTypes: {},
defaultProps: {
title: "?",
fill: "?",
containerComponent: <VictoryContainer />,
dataComponent: <Path />,
data: [
{ x: 0, y: 0 },
{ x: 1, y: 1 },
{ x: 2, y: 2 },
{ x: 3, y: 3 },
],
},
normalizeProps: {
...NormalizeProps,
},
aggregateProps: {
...AggregateProps,
},
},
(props) => {
const lineFunction = LineHelpers.getLineFunction(props);
const d = lineFunction(props.data as any);
return (
<g>
<text>{props.title}</text>
<Clone element={props.dataComponent} d={d} fill={props.fill} />
</g>
);
},
);

it("should render inside an SVG", () => {
const result = render(
<VicExample data-testid="test" title="test-title" fill="test-fill" />,
);
const svg = result.getByTestId("test");
expect(svg).toMatchInlineSnapshot(`
<svg
aria-labelledby="victory-container-1-title"
data-testid="test"
role="img"
style="pointer-events: all; width: 100%; height: 100%;"
viewBox="0 0 undefined undefined"
>
<title
id="victory-container-1-title"
>
test-title
</title>
<g>
<text>
test-title
</text>
<path
d="M-225,450L0,300L225,150L450,0"
fill="test-fill"
/>
</g>
</svg>
`);
});
});
Loading
Loading