mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-23 14:39:58 +03:00
Update
This commit is contained in:
parent
6c85884d34
commit
d5b77157d6
|
@ -1,4 +1,5 @@
|
||||||
import d3, { D3ZoomEvent } from 'd3';
|
import * as d3 from 'd3';
|
||||||
|
import type { D3ZoomEvent, HierarchyPointLink, HierarchyPointNode } from 'd3';
|
||||||
import { isEmpty } from 'ramda';
|
import { isEmpty } from 'ramda';
|
||||||
import { map2tree } from 'map2tree';
|
import { map2tree } from 'map2tree';
|
||||||
import deepmerge from 'deepmerge';
|
import deepmerge from 'deepmerge';
|
||||||
|
@ -16,7 +17,7 @@ export interface InputOptions {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
state?: {} | null;
|
state?: {} | null;
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
tree?: NodeWithId | {};
|
tree?: Node | {};
|
||||||
|
|
||||||
rootKeyName: string;
|
rootKeyName: string;
|
||||||
pushMethod: 'push' | 'unshift';
|
pushMethod: 'push' | 'unshift';
|
||||||
|
@ -51,7 +52,7 @@ export interface InputOptions {
|
||||||
widthBetweenNodesCoeff: number;
|
widthBetweenNodesCoeff: number;
|
||||||
transitionDuration: number;
|
transitionDuration: number;
|
||||||
blinkDuration: number;
|
blinkDuration: number;
|
||||||
onClickText: (datum: NodeWithId) => void;
|
onClickText: (datum: Node) => void;
|
||||||
tooltipOptions: {
|
tooltipOptions: {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
left?: number | undefined;
|
left?: number | undefined;
|
||||||
|
@ -69,7 +70,7 @@ interface Options {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
state?: {} | null;
|
state?: {} | null;
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
tree?: NodeWithId | {};
|
tree?: Node | {};
|
||||||
|
|
||||||
rootKeyName: string;
|
rootKeyName: string;
|
||||||
pushMethod: 'push' | 'unshift';
|
pushMethod: 'push' | 'unshift';
|
||||||
|
@ -172,19 +173,37 @@ const defaultOptions: Options = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface NodeWithId {
|
export interface Node {
|
||||||
name: string;
|
name: string;
|
||||||
children?: NodeWithId[] | null;
|
children?: Node[] | null;
|
||||||
_children?: NodeWithId[] | null;
|
value?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InternalNode {
|
||||||
|
name: string;
|
||||||
|
children?: this[] | null;
|
||||||
value?: unknown;
|
value?: unknown;
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
parent?: NodeWithId;
|
|
||||||
depth?: number;
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HierarchyPointNodeWithPrivateChildren<Datum>
|
||||||
|
extends HierarchyPointNode<Datum> {
|
||||||
|
_children?: this[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export interface NodeWithId {
|
||||||
|
// name: string;
|
||||||
|
// children?: NodeWithId[] | null;
|
||||||
|
// _children?: NodeWithId[] | null;
|
||||||
|
// value?: unknown;
|
||||||
|
// id: string;
|
||||||
|
//
|
||||||
|
// parent?: NodeWithId;
|
||||||
|
// depth?: number;
|
||||||
|
// x?: number;
|
||||||
|
// y?: number;
|
||||||
|
// }
|
||||||
|
|
||||||
interface NodePosition {
|
interface NodePosition {
|
||||||
parentId: string | null | undefined;
|
parentId: string | null | undefined;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -263,17 +282,17 @@ export default function (
|
||||||
}) scale(${initialZoom})`
|
}) scale(${initialZoom})`
|
||||||
);
|
);
|
||||||
|
|
||||||
let layout = d3.layout.tree().size([width, height]);
|
// let layout = d3.tree().size([width, height]);
|
||||||
let data: NodeWithId;
|
let data: InternalNode;
|
||||||
|
|
||||||
if (isSorted) {
|
// if (isSorted) {
|
||||||
layout.sort((a, b) =>
|
// layout.sort((a, b) =>
|
||||||
(b as NodeWithId).name.toLowerCase() <
|
// (b as NodeWithId).name.toLowerCase() <
|
||||||
(a as NodeWithId).name.toLowerCase()
|
// (a as NodeWithId).name.toLowerCase()
|
||||||
? 1
|
// ? 1
|
||||||
: -1
|
// : -1
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
// previousNodePositionsById stores node x and y
|
// previousNodePositionsById stores node x and y
|
||||||
// as well as hierarchy (id / parentId);
|
// as well as hierarchy (id / parentId);
|
||||||
|
@ -309,18 +328,17 @@ export default function (
|
||||||
|
|
||||||
return function renderChart(nextState = tree || state) {
|
return function renderChart(nextState = tree || state) {
|
||||||
data = !tree
|
data = !tree
|
||||||
? // eslint-disable-next-line @typescript-eslint/ban-types
|
? (map2tree(nextState, {
|
||||||
(map2tree(nextState as {}, {
|
|
||||||
key: rootKeyName,
|
key: rootKeyName,
|
||||||
pushMethod,
|
pushMethod,
|
||||||
}) as NodeWithId)
|
}) as InternalNode)
|
||||||
: (nextState as NodeWithId);
|
: (nextState as InternalNode);
|
||||||
|
|
||||||
if (isEmpty(data) || !data.name) {
|
if (isEmpty(data) || !data.name) {
|
||||||
data = {
|
data = {
|
||||||
name: 'error',
|
name: 'error',
|
||||||
message: 'Please provide a state map or a tree structure',
|
message: 'Please provide a state map or a tree structure',
|
||||||
} as unknown as NodeWithId;
|
} as unknown as InternalNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nodeIndex = 0;
|
let nodeIndex = 0;
|
||||||
|
@ -354,22 +372,32 @@ export default function (
|
||||||
// set tree dimensions and spacing between branches and nodes
|
// set tree dimensions and spacing between branches and nodes
|
||||||
const maxNodeCountByLevel = Math.max(...getNodeGroupByDepthCount(data));
|
const maxNodeCountByLevel = Math.max(...getNodeGroupByDepthCount(data));
|
||||||
|
|
||||||
layout = layout.size([
|
const layout = d3
|
||||||
maxNodeCountByLevel * 25 * heightBetweenNodesCoeff,
|
.tree<InternalNode>()
|
||||||
width,
|
.size([maxNodeCountByLevel * 25 * heightBetweenNodesCoeff, width]);
|
||||||
]);
|
|
||||||
|
|
||||||
const nodes = layout.nodes(data as d3.layout.tree.Node) as NodeWithId[];
|
const rootNode = d3.hierarchy(data);
|
||||||
const links = layout.links(nodes as d3.layout.tree.Node[]);
|
if (isSorted) {
|
||||||
|
rootNode.sort((a, b) =>
|
||||||
|
b.data.name.toLowerCase() < a.data.name.toLowerCase() ? 1 : -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
nodes.forEach(
|
const rootPointNode = layout(
|
||||||
|
rootNode
|
||||||
|
) as HierarchyPointNodeWithPrivateChildren<InternalNode>;
|
||||||
|
const links = rootPointNode.links();
|
||||||
|
|
||||||
|
rootPointNode.each(
|
||||||
(node) =>
|
(node) =>
|
||||||
(node.y = node.depth! * (maxLabelLength * 7 * widthBetweenNodesCoeff))
|
(node.y = node.depth * (maxLabelLength * 7 * widthBetweenNodesCoeff))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const nodes = rootPointNode.descendants();
|
||||||
|
|
||||||
const nodePositions = nodes.map((n) => ({
|
const nodePositions = nodes.map((n) => ({
|
||||||
parentId: n.parent && n.parent.id,
|
parentId: n.parent && n.parent.data.id,
|
||||||
id: n.id,
|
id: n.data.id,
|
||||||
x: n.x,
|
x: n.x,
|
||||||
y: n.y,
|
y: n.y,
|
||||||
}));
|
}));
|
||||||
|
@ -378,9 +406,15 @@ export default function (
|
||||||
|
|
||||||
// process the node selection
|
// process the node selection
|
||||||
const node = vis
|
const node = vis
|
||||||
.selectAll('g.node')
|
.selectAll<
|
||||||
.property('__oldData__', (d: NodeWithId) => d)
|
SVGGElement,
|
||||||
.data(nodes, (d) => d.id || (d.id = ++nodeIndex as unknown as string));
|
HierarchyPointNodeWithPrivateChildren<InternalNode>
|
||||||
|
>('g.node')
|
||||||
|
.property('__oldData__', (d) => d)
|
||||||
|
.data(
|
||||||
|
nodes,
|
||||||
|
(d) => d.data.id || (d.data.id = ++nodeIndex as unknown as string)
|
||||||
|
);
|
||||||
const nodeEnter = node
|
const nodeEnter = node
|
||||||
.enter()
|
.enter()
|
||||||
.append('g')
|
.append('g')
|
||||||
|
@ -388,7 +422,7 @@ export default function (
|
||||||
.attr('transform', (d) => {
|
.attr('transform', (d) => {
|
||||||
const position = findParentNodePosition(
|
const position = findParentNodePosition(
|
||||||
nodePositionsById,
|
nodePositionsById,
|
||||||
d.id,
|
d.data.id,
|
||||||
(n) => !!previousNodePositionsById[n.id]
|
(n) => !!previousNodePositionsById[n.id]
|
||||||
);
|
);
|
||||||
const previousPosition =
|
const previousPosition =
|
||||||
|
@ -411,9 +445,9 @@ export default function (
|
||||||
|
|
||||||
if (!tooltipOptions.disabled) {
|
if (!tooltipOptions.disabled) {
|
||||||
nodeEnter.call(
|
nodeEnter.call(
|
||||||
tooltip<NodeWithId>(d3, 'tooltip', { ...tooltipOptions, root })
|
tooltip('tooltip', { ...tooltipOptions, root })
|
||||||
.text((d, i) => getTooltipString(d, i, tooltipOptions))
|
.text((d, i) => getTooltipString(d, i, tooltipOptions))
|
||||||
.style(tooltipOptions.style)
|
.styles(tooltipOptions.style)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,11 +471,11 @@ export default function (
|
||||||
.attr('transform', 'translate(0,0)')
|
.attr('transform', 'translate(0,0)')
|
||||||
.attr('dy', '.35em')
|
.attr('dy', '.35em')
|
||||||
.style('fill-opacity', 0)
|
.style('fill-opacity', 0)
|
||||||
.text((d) => d.name)
|
.text((d) => d.data.name)
|
||||||
.on('click', onClickText);
|
.on('click', onClickText);
|
||||||
|
|
||||||
// update the text to reflect whether node has children or not
|
// update the text to reflect whether node has children or not
|
||||||
node.select('text').text((d) => d.name);
|
node.select('text').text((d) => d.data.name);
|
||||||
|
|
||||||
// change the circle fill depending on whether it has children and is collapsed
|
// change the circle fill depending on whether it has children and is collapsed
|
||||||
node
|
node
|
||||||
|
@ -515,8 +549,8 @@ export default function (
|
||||||
|
|
||||||
// update the links
|
// update the links
|
||||||
const link = vis
|
const link = vis
|
||||||
.selectAll('path.link')
|
.selectAll<SVGGElement, HierarchyPointLink<InternalNode>>('path.link')
|
||||||
.data(links, (d) => (d.target as NodeWithId).id);
|
.data(links, (d) => d.target.data.id);
|
||||||
|
|
||||||
// enter any new links at the parent's previous position
|
// enter any new links at the parent's previous position
|
||||||
link
|
link
|
||||||
|
@ -526,7 +560,7 @@ export default function (
|
||||||
.attr('d', (d) => {
|
.attr('d', (d) => {
|
||||||
const position = findParentNodePosition(
|
const position = findParentNodePosition(
|
||||||
nodePositionsById,
|
nodePositionsById,
|
||||||
(d.target as NodeWithId).id,
|
d.target.data.id,
|
||||||
(n) => !!previousNodePositionsById[n.id]
|
(n) => !!previousNodePositionsById[n.id]
|
||||||
);
|
);
|
||||||
const previousPosition =
|
const previousPosition =
|
||||||
|
@ -535,9 +569,12 @@ export default function (
|
||||||
return diagonal({
|
return diagonal({
|
||||||
source: previousPosition,
|
source: previousPosition,
|
||||||
target: previousPosition,
|
target: previousPosition,
|
||||||
} as d3.svg.diagonal.Link<NodePosition>);
|
});
|
||||||
})
|
});
|
||||||
.style(linkStyles);
|
|
||||||
|
for (const [key, value] of Object.entries(linkStyles)) {
|
||||||
|
link.style(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
// transition links to their new position
|
// transition links to their new position
|
||||||
link.transition().duration(transitionDuration).attr('d', diagonal);
|
link.transition().duration(transitionDuration).attr('d', diagonal);
|
||||||
|
@ -550,7 +587,7 @@ export default function (
|
||||||
.attr('d', (d) => {
|
.attr('d', (d) => {
|
||||||
const position = findParentNodePosition(
|
const position = findParentNodePosition(
|
||||||
previousNodePositionsById,
|
previousNodePositionsById,
|
||||||
(d.target as NodeWithId).id,
|
d.target.data.id,
|
||||||
(n) => !!nodePositionsById[n.id]
|
(n) => !!nodePositionsById[n.id]
|
||||||
);
|
);
|
||||||
const futurePosition =
|
const futurePosition =
|
||||||
|
|
|
@ -1,38 +1,47 @@
|
||||||
import { is, join, pipe, replace } from 'ramda';
|
import { is, join, pipe, replace } from 'ramda';
|
||||||
import sortAndSerialize from './sortAndSerialize';
|
import sortAndSerialize from './sortAndSerialize';
|
||||||
import { NodeWithId } from './tree';
|
import type {
|
||||||
|
HierarchyPointNodeWithPrivateChildren,
|
||||||
|
InternalNode,
|
||||||
|
} from './tree';
|
||||||
|
|
||||||
export function collapseChildren(node: NodeWithId) {
|
export function collapseChildren(
|
||||||
|
node: HierarchyPointNodeWithPrivateChildren<InternalNode>
|
||||||
|
) {
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
node._children = node.children;
|
node._children = node.children;
|
||||||
node._children.forEach(collapseChildren);
|
node._children.forEach(collapseChildren);
|
||||||
node.children = null;
|
node.children = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function expandChildren(node: NodeWithId) {
|
export function expandChildren(
|
||||||
|
node: HierarchyPointNodeWithPrivateChildren<InternalNode>
|
||||||
|
) {
|
||||||
if (node._children) {
|
if (node._children) {
|
||||||
node.children = node._children;
|
node.children = node._children;
|
||||||
node.children.forEach(expandChildren);
|
node.children.forEach(expandChildren);
|
||||||
node._children = null;
|
node._children = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleChildren(node: NodeWithId) {
|
export function toggleChildren(
|
||||||
|
node: HierarchyPointNodeWithPrivateChildren<InternalNode>
|
||||||
|
) {
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
node._children = node.children;
|
node._children = node.children;
|
||||||
node.children = null;
|
node.children = undefined;
|
||||||
} else if (node._children) {
|
} else if (node._children) {
|
||||||
node.children = node._children;
|
node.children = node._children;
|
||||||
node._children = null;
|
node._children = undefined;
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function visit(
|
export function visit(
|
||||||
parent: NodeWithId,
|
parent: InternalNode,
|
||||||
visitFn: (parent: NodeWithId) => void,
|
visitFn: (parent: InternalNode) => void,
|
||||||
childrenFn: (parent: NodeWithId) => NodeWithId[] | null | undefined
|
childrenFn: (parent: InternalNode) => InternalNode[] | null | undefined
|
||||||
) {
|
) {
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
return;
|
return;
|
||||||
|
@ -50,10 +59,10 @@ export function visit(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNodeGroupByDepthCount(rootNode: NodeWithId) {
|
export function getNodeGroupByDepthCount(rootNode: InternalNode) {
|
||||||
const nodeGroupByDepthCount = [1];
|
const nodeGroupByDepthCount = [1];
|
||||||
|
|
||||||
const traverseFrom = function traverseFrom(node: NodeWithId, depth = 0) {
|
const traverseFrom = function traverseFrom(node: InternalNode, depth = 0) {
|
||||||
if (!node.children || node.children.length === 0) {
|
if (!node.children || node.children.length === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import d3Package, { BaseType, ContainerElement, Selection } from 'd3';
|
import * as d3 from 'd3';
|
||||||
|
import type { BaseType, ContainerElement, Selection } from 'd3';
|
||||||
import { is } from 'ramda';
|
import { is } from 'ramda';
|
||||||
import functor from './utils/functor';
|
import functor from './utils/functor';
|
||||||
|
|
||||||
|
@ -48,7 +49,6 @@ export function tooltip<
|
||||||
PElement extends BaseType,
|
PElement extends BaseType,
|
||||||
PDatum
|
PDatum
|
||||||
>(
|
>(
|
||||||
d3: typeof d3Package,
|
|
||||||
className = 'tooltip',
|
className = 'tooltip',
|
||||||
options: Partial<Options<GElement, Datum, PElement, PDatum>> = {}
|
options: Partial<Options<GElement, Datum, PElement, PDatum>> = {}
|
||||||
): Tip<GElement, Datum, PElement, PDatum> {
|
): Tip<GElement, Datum, PElement, PDatum> {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user