Skip to content

Commit

Permalink
feat: support labelField in MindMap, IndentedTree, Fishbone, FlowGraph (
Browse files Browse the repository at this point in the history
#2801)

* feat: support labelField in MindMap

* feat: support labelField in IndentedTree

* feat: support labelField in Fishbone

* feat: support labelField in FlowDirectionGraph and FlowGraph

* fix: typo
  • Loading branch information
yvonneyx authored Dec 12, 2024
1 parent f096e24 commit 26b7295
Show file tree
Hide file tree
Showing 20 changed files with 320 additions and 147 deletions.
13 changes: 9 additions & 4 deletions packages/graphs/src/components/fishbone/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,21 @@ import { mergeOptions } from '../../core/utils/options';
import { DEFAULT_OPTIONS, getFishboneOptions } from './options';
import type { FishboneOptions } from './types';

export const Fishbone: ForwardRefExoticComponent<PropsWithoutRef<PropsWithChildren<FishboneOptions>> & RefAttributes<Graph>> = forwardRef<Graph, PropsWithChildren<FishboneOptions>>(({ children, ...props }, ref) => {
const { type = 'cause', ...restProps } = props;
export const Fishbone: ForwardRefExoticComponent<
PropsWithoutRef<PropsWithChildren<FishboneOptions>> & RefAttributes<Graph>
> = forwardRef<Graph, PropsWithChildren<FishboneOptions>>(({ children, ...props }, ref) => {
const { type = 'cause', labelField, ...restProps } = props;

const options = useMemo(() => mergeOptions(COMMON_OPTIONS, DEFAULT_OPTIONS, getFishboneOptions({ type }), restProps), [props]);
const options = useMemo(
() => mergeOptions(COMMON_OPTIONS, DEFAULT_OPTIONS, getFishboneOptions({ type, labelField }), restProps),
[props],
);

return (
<BaseGraph {...options} ref={ref}>
{children}
</BaseGraph>
);
})
});

export type { FishboneOptions };
43 changes: 24 additions & 19 deletions packages/graphs/src/components/fishbone/options.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type { ID, NodeData, SingleLayoutOptions, Size } from '@antv/g6';
import type { FishboneOptions } from "./types";
import { get } from 'lodash';
import { formatLabel } from '../../core/utils/label';
import { measureTextSize } from '../../core/utils/measure-text';
import type { FishboneOptions } from './types';

export const DEFAULT_OPTIONS: FishboneOptions = {
node: {
style: {
size: 10,
labelText: d => d.id,
labelPlacement: 'center',
}
},
},
edge: {
type: 'polyline',
Expand All @@ -34,30 +35,34 @@ const getNodeFill = (node: NodeData): string => {
if (depth === 0) return '#EFF0F0';
if (depth === 1) return (node.style?.color as string) || '#EFF0F0';
return 'transparent';
}
};

export function getFishboneOptions({ type }: Pick<FishboneOptions, 'type'>): FishboneOptions {
export function getFishboneOptions({
type,
labelField,
}: Pick<FishboneOptions, 'type' | 'labelField'>): FishboneOptions {
const options: FishboneOptions = {
node: {
type: 'rect',
style: {
fill: d => getNodeFill(d),
labelFill: d => d.depth === 1 ? '#fff' : '#262626',
fill: (d) => getNodeFill(d),
labelFill: (d) => (d.depth === 1 ? '#fff' : '#262626'),
labelFillOpacity: 1,
labelFontSize: d => d.depth === 0 ? 24 : d.depth === 1 ? 18 : 16,
labelFontWeight: d => d.depth === 0 ? 600 : 400,
labelLineHeight: d => d.depth === 0 ? 26 : d.depth === 1 ? 20 : 18,
labelText: d => d.id,
labelFontSize: (d) => (d.depth === 0 ? 24 : d.depth === 1 ? 18 : 16),
labelFontWeight: (d) => (d.depth === 0 ? 600 : 400),
labelLineHeight: (d) => (d.depth === 0 ? 26 : d.depth === 1 ? 20 : 18),
labelText: (d) => formatLabel(d, labelField),
radius: 8,
size: d => getNodeSize(d.id, d.depth!),
}
size: (d) => getNodeSize(d.id, d.depth!),
},
},
edge: {
type: 'polyline',
style: {
lineWidth: 3,
stroke: function (data) {
return (this.getNodeData(data.target).style!.color as string) || '#99ADD1';
const target = this.getNodeData(data.target);
return get(target, 'style.color', '#99ADD1') as string;
},
},
},
Expand All @@ -71,18 +76,18 @@ export function getFishboneOptions({ type }: Pick<FishboneOptions, 'type'>): Fis
type: 'arrange-edge-z-index',
key: 'arrange-edge-z-index',
},
]
],
};

options.layout ||= {} as SingleLayoutOptions;
if (type === 'decision') {
// @ts-ignore
options.node.style.labelPlacement = d => d.depth === 0 || d.depth === 1 ? 'center' : 'right';
Object.assign(options.layout!, { direction: 'LR' })
options.node.style.labelPlacement = (d) => (d.depth === 0 || d.depth === 1 ? 'center' : 'right');
Object.assign(options.layout!, { direction: 'LR' });
} else if (type === 'cause') {
// @ts-ignore
options.node.style.labelPlacement = d => d.depth === 0 || d.depth === 1 ? 'center' : 'left';
Object.assign(options.layout!, { direction: 'RL' })
options.node.style.labelPlacement = (d) => (d.depth === 0 || d.depth === 1 ? 'center' : 'left');
Object.assign(options.layout!, { direction: 'RL' });
}

return options;
Expand Down
9 changes: 8 additions & 1 deletion packages/graphs/src/components/fishbone/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GraphOptions } from "../../types";
import type { NodeData } from '@antv/g6';
import type { GraphOptions } from '../../types';

export interface FishboneOptions extends GraphOptions {
/**
Expand All @@ -9,4 +9,11 @@ export interface FishboneOptions extends GraphOptions {
* @default 'cause'
*/
type?: 'decision' | 'cause';
/**
* Selects a field from the data to use as the label for the node.
* If a string is provided, it will select the field as `data[labelField]`.
* If a function is provided, it will call the function with the data and use the returned value.
* @default (data) => data.id
*/
labelField?: string | ((node: NodeData) => string);
}
26 changes: 21 additions & 5 deletions packages/graphs/src/components/flow-direction-graph/options.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import React from 'react';
import { RCNode } from '../../core/base';
import { formatLabel } from '../../core/utils/label';
import type { GraphOptions } from '../../types';
import type { FlowDirectionGraphOptions } from './types';

const { TextNode } = RCNode;

export const DEFAULT_OPTIONS: GraphOptions = {
node: {
type: 'react',
style: {
component: (data) => <TextNode type="filled" text={data.id} />,
size: [100, 40],
ports: [{ placement: 'left' }, { placement: 'right' }],
},
state: {
active: {
halo: false,
Expand Down Expand Up @@ -39,3 +36,22 @@ export const DEFAULT_OPTIONS: GraphOptions = {
},
transforms: ['translate-react-node-origin'],
};

export const getFlowDirectionGraphOptions = ({
labelField,
}: Pick<FlowDirectionGraphOptions, 'labelField'>): FlowDirectionGraphOptions => {
const options: FlowDirectionGraphOptions = {
node: {
style: {
component: (data) => {
const label = formatLabel(data, labelField);
return <TextNode type="filled" text={label} />;
},
size: [100, 40],
ports: [{ placement: 'left' }, { placement: 'right' }],
},
},
};

return options;
};
13 changes: 11 additions & 2 deletions packages/graphs/src/components/flow-direction-graph/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import { GraphOptions } from '../../types';
import type { NodeData } from '@antv/g6';
import type { GraphOptions } from '../../types';

export interface FlowDirectionGraphOptions extends GraphOptions {}
export interface FlowDirectionGraphOptions extends GraphOptions {
/**
* Selects a field from the data to use as the label for the node.
* If a string is provided, it will select the field as `data[labelField]`.
* If a function is provided, it will call the function with the data and use the returned value.
* @default (data) => data.id
*/
labelField?: string | ((node: NodeData) => string);
}
4 changes: 2 additions & 2 deletions packages/graphs/src/components/flow-graph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export const FlowGraph: ForwardRefExoticComponent<
PropsWithoutRef<PropsWithChildren<FlowGraphOptions>> & RefAttributes<Graph>
> = forwardRef<Graph, PropsWithChildren<FlowGraphOptions>>(({ children, ...props }, ref) => {
const options = useMemo(() => {
const { direction = 'horizontal', ...restProps } = props;
return mergeOptions(COMMON_OPTIONS, DEFAULT_OPTIONS, getFlowGraphOptions({ direction }), restProps);
const { direction = 'horizontal', labelField, ...restProps } = props;
return mergeOptions(COMMON_OPTIONS, DEFAULT_OPTIONS, getFlowGraphOptions({ direction, labelField }), restProps);
}, [props]);

return (
Expand Down
42 changes: 22 additions & 20 deletions packages/graphs/src/components/flow-graph/options.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import React from 'react';
import { RCNode } from '../../core/base';
import { formatLabel } from '../../core/utils/label';
import type { FlowGraphOptions } from './types';

const { TextNode } = RCNode;

export const DEFAULT_OPTIONS: FlowGraphOptions = {
node: {
type: 'react',
style: {
component: (data) => <TextNode type="filled" text={data.id} />,
size: [100, 40],
ports: [{ placement: 'left' }, { placement: 'right' }],
},
state: {
active: {
halo: false,
Expand All @@ -32,28 +28,34 @@ export const DEFAULT_OPTIONS: FlowGraphOptions = {
},
layout: {
type: 'dagre',
rankdir: 'LR',
animation: false,
},
transforms: ['translate-react-node-origin'],
};

export const getFlowGraphOptions = ({ direction }: Pick<FlowGraphOptions, 'direction'>): FlowGraphOptions => {
let options: FlowGraphOptions = {};

if (direction === 'vertical') {
options = {
node: {
style: {
ports: [{ placement: 'top' }, { placement: 'bottom' }],
export const getFlowGraphOptions = ({
direction,
labelField,
}: Pick<FlowGraphOptions, 'direction' | 'labelField'>): FlowGraphOptions => {
const options: FlowGraphOptions = {
node: {
style: {
component: (data) => {
const label = formatLabel(data, labelField);
return <TextNode type="filled" text={label} />;
},
size: [100, 40],
ports:
direction === 'vertical'
? [{ placement: 'top' }, { placement: 'bottom' }]
: [{ placement: 'left' }, { placement: 'right' }],
},
layout: {
type: 'dagre',
rankdir: 'TB',
},
};
}
},
layout: {
type: 'dagre',
rankdir: direction === 'vertical' ? 'TB' : 'LR',
},
};

return options;
};
8 changes: 8 additions & 0 deletions packages/graphs/src/components/flow-graph/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { NodeData } from '@antv/g6';
import type { GraphOptions } from '../../types';

export interface FlowGraphOptions extends GraphOptions {
Expand All @@ -6,4 +7,11 @@ export interface FlowGraphOptions extends GraphOptions {
* @default 'horizontal'
*/
direction?: 'horizontal' | 'vertical';
/**
* Selects a field from the data to use as the label for the node.
* If a string is provided, it will select the field as `data[labelField]`.
* If a function is provided, it will call the function with the data and use the returned value.
* @default (data) => data.id
*/
labelField?: string | ((node: NodeData) => string);
}
18 changes: 11 additions & 7 deletions packages/graphs/src/components/indented-tree/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ import type { IndentedTreeOptions } from './types';
export const IndentedTree: ForwardRefExoticComponent<
PropsWithoutRef<PropsWithChildren<IndentedTreeOptions>> & RefAttributes<Graph>
> = forwardRef<Graph, PropsWithChildren<IndentedTreeOptions>>(({ children, ...props }, ref) => {
const { type = 'default', nodeMinWidth, nodeMaxWidth, direction = 'right', ...restProps } = props;
const { type = 'default', nodeMinWidth, nodeMaxWidth, direction = 'right', labelField, ...restProps } = props;

const options = useMemo(() => mergeOptions(
COMMON_OPTIONS,
DEFAULT_OPTIONS,
getIndentedTreeOptions({ type, nodeMinWidth, nodeMaxWidth, direction }),
restProps,
), [props]);
const options = useMemo(
() =>
mergeOptions(
COMMON_OPTIONS,
DEFAULT_OPTIONS,
getIndentedTreeOptions({ type, nodeMinWidth, nodeMaxWidth, direction, labelField }),
restProps,
),
[props],
);

return (
<BaseGraph {...options} ref={ref}>
Expand Down
Loading

0 comments on commit 26b7295

Please sign in to comment.