This commit is contained in:
Nathan Bierema 2023-01-02 10:22:16 -05:00
parent dcd562e123
commit e65d9ec409
5 changed files with 37 additions and 65 deletions

View File

@ -1,3 +1,3 @@
export type { StyleValue } from 'd3tooltip'; export type { StyleValue } from 'd3tooltip';
export { default as tree } from './tree/tree'; export { default as tree } from './tree/tree';
export type { InputOptions, Node } from './tree/tree'; export type { Node, Options } from './tree/tree';

View File

@ -2,6 +2,7 @@ import * as d3 from 'd3';
import type { D3ZoomEvent, HierarchyPointLink, HierarchyPointNode } 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 type { Node } from 'map2tree';
import deepmerge from 'deepmerge'; import deepmerge from 'deepmerge';
import { import {
getTooltipString, getTooltipString,
@ -12,7 +13,7 @@ import {
import { tooltip } from 'd3tooltip'; import { tooltip } from 'd3tooltip';
import type { StyleValue } from 'd3tooltip'; import type { StyleValue } from 'd3tooltip';
interface Options { export 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
@ -51,16 +52,16 @@ interface Options {
widthBetweenNodesCoeff: number; widthBetweenNodesCoeff: number;
transitionDuration: number; transitionDuration: number;
blinkDuration: number; blinkDuration: number;
onClickText: (datum: HierarchyPointNode<Node>) => void; onClickText: (datum: Node) => void;
tooltipOptions: { tooltipOptions: {
disabled: boolean; disabled?: boolean;
left: number | undefined; left?: number | undefined;
top: number | undefined; top?: number | undefined;
offset: { offset?: {
left: number; left: number;
top: number; top: number;
}; };
style: { [key: string]: StyleValue } | undefined; styles?: { [key: string]: StyleValue } | undefined;
indentationSize?: number; indentationSize?: number;
}; };
} }
@ -115,28 +116,13 @@ const defaultOptions: Options = {
left: 0, left: 0,
top: 0, top: 0,
}, },
style: undefined, styles: undefined,
}, },
} satisfies Options; } satisfies Options;
export interface Node { export interface InternalNode extends Node {
name: string;
children?: this[] | null;
object?: unknown;
value?: unknown;
}
export interface InternalNode {
name: string;
children?: this[] | null;
object?: unknown;
value?: unknown;
id: string | number;
}
export interface HierarchyPointNodeWithPrivateChildren<Datum>
extends HierarchyPointNode<Datum> {
_children?: this[] | undefined; _children?: this[] | undefined;
id: string | number;
} }
interface NodePosition { interface NodePosition {
@ -310,9 +296,7 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
); );
} }
const rootPointNode = layout( const rootPointNode = layout(rootNode);
rootNode
) as HierarchyPointNodeWithPrivateChildren<InternalNode>;
const links = rootPointNode.links(); const links = rootPointNode.links();
rootPointNode.each( rootPointNode.each(
@ -333,10 +317,7 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
// process the node selection // process the node selection
let node = vis let node = vis
.selectAll< .selectAll<SVGGElement, HierarchyPointNode<InternalNode>>('g.node')
SVGGElement,
HierarchyPointNodeWithPrivateChildren<InternalNode>
>('g.node')
.property('__oldData__', (d) => d) .property('__oldData__', (d) => d)
.data(nodes, (d) => d.data.id || (d.data.id = ++nodeIndex)); .data(nodes, (d) => d.data.id || (d.data.id = ++nodeIndex));
const nodeEnter = node const nodeEnter = node
@ -367,7 +348,7 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
nodeEnter.call( nodeEnter.call(
tooltip< tooltip<
SVGGElement, SVGGElement,
HierarchyPointNodeWithPrivateChildren<InternalNode>, HierarchyPointNode<InternalNode>,
SVGGElement, SVGGElement,
unknown, unknown,
HTMLElement, HTMLElement,
@ -375,8 +356,8 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
null, null,
undefined undefined
>('tooltip', { ...tooltipOptions, root }) >('tooltip', { ...tooltipOptions, root })
.text((d, i) => getTooltipString(d, i, tooltipOptions)) .text((d, i) => getTooltipString(d.data, i, tooltipOptions))
.styles(tooltipOptions.style) .styles(tooltipOptions.styles)
); );
} }
@ -388,8 +369,8 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
.attr('class', 'nodeCircle') .attr('class', 'nodeCircle')
.attr('r', 0) .attr('r', 0)
.on('click', (clickedNode) => { .on('click', (clickedNode) => {
if ((d3.event as Event).defaultPrevented) return; if (d3.event.defaultPrevented) return;
toggleChildren(clickedNode); toggleChildren(clickedNode.data);
update(); update();
}); });
@ -401,7 +382,7 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
.attr('dy', '.35em') .attr('dy', '.35em')
.style('fill-opacity', 0) .style('fill-opacity', 0)
.text((d) => d.data.name) .text((d) => d.data.name)
.on('click', onClickText); .on('click', (d) => onClickText(d.data));
node = nodeEnter.merge(node); node = nodeEnter.merge(node);
@ -414,9 +395,9 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
.style('stroke', 'black') .style('stroke', 'black')
.style('stroke-width', '1.5px') .style('stroke-width', '1.5px')
.style('fill', (d) => .style('fill', (d) =>
d._children d.data._children
? nodeStyleOptions.colors.collapsed ? nodeStyleOptions.colors.collapsed
: d.children : d.data.children
? nodeStyleOptions.colors.parent ? nodeStyleOptions.colors.parent
: nodeStyleOptions.colors.default : nodeStyleOptions.colors.default
); );
@ -436,7 +417,7 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
.style('fill-opacity', 1) .style('fill-opacity', 1)
.attr('transform', function transform(d) { .attr('transform', function transform(d) {
const x = const x =
(d.children || d._children ? -1 : 1) * (d.data.children || d.data._children ? -1 : 1) *
(this.getBBox().width / 2 + nodeStyleOptions.radius + 5); (this.getBBox().width / 2 + nodeStyleOptions.radius + 5);
return `translate(${x},0)`; return `translate(${x},0)`;
}); });
@ -445,7 +426,7 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
node node
.filter(function flick( .filter(function flick(
this: SVGGElement & { this: SVGGElement & {
__oldData__?: HierarchyPointNodeWithPrivateChildren<InternalNode>; __oldData__?: HierarchyPointNode<InternalNode>;
}, },
d d
) { ) {
@ -465,7 +446,7 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
// transition exiting nodes to the parent's new position // transition exiting nodes to the parent's new position
const nodeExit = node const nodeExit = node
.exit<HierarchyPointNodeWithPrivateChildren<InternalNode>>() .exit<HierarchyPointNode<InternalNode>>()
.transition() .transition()
.duration(transitionDuration) .duration(transitionDuration)
.attr('transform', (d) => { .attr('transform', (d) => {
@ -550,3 +531,5 @@ export default function (DOMNode: HTMLElement, options: Partial<Options> = {}) {
} }
}; };
} }
export type { Node };

View File

@ -1,13 +1,8 @@
import { is, join, pipe, replace } from 'ramda'; import { is, join, pipe, replace } from 'ramda';
import sortAndSerialize from './sortAndSerialize'; import sortAndSerialize from './sortAndSerialize';
import type { import type { InternalNode } from './tree';
HierarchyPointNodeWithPrivateChildren,
InternalNode,
} from './tree';
export function collapseChildren( export function collapseChildren(node: InternalNode) {
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);
@ -15,9 +10,7 @@ export function collapseChildren(
} }
} }
export function expandChildren( export function expandChildren(node: InternalNode) {
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);
@ -25,9 +18,7 @@ export function expandChildren(
} }
} }
export function toggleChildren( export function toggleChildren(node: InternalNode) {
node: HierarchyPointNodeWithPrivateChildren<InternalNode>
) {
if (node.children) { if (node.children) {
node._children = node.children; node._children = node.children;
node.children = undefined; node.children = undefined;
@ -83,11 +74,11 @@ export function getNodeGroupByDepthCount(rootNode: InternalNode) {
} }
export function getTooltipString( export function getTooltipString(
node: HierarchyPointNodeWithPrivateChildren<InternalNode>, node: InternalNode,
i: number | undefined, i: number | undefined,
{ indentationSize = 4 } { indentationSize = 4 }
) { ) {
if (!is(Object, node.data)) return ''; if (!is(Object, node)) return '';
const spacer = join('&nbsp;&nbsp;'); const spacer = join('&nbsp;&nbsp;');
const cr2br = replace(/\n/g, '<br/>'); const cr2br = replace(/\n/g, '<br/>');
@ -96,9 +87,8 @@ export function getTooltipString(
const children = node.children || node._children; const children = node.children || node._children;
if (typeof node.data.value !== 'undefined') return json2html(node.data.value); if (typeof node.value !== 'undefined') return json2html(node.value);
if (typeof node.data.object !== 'undefined') if (typeof node.object !== 'undefined') return json2html(node.object);
return json2html(node.data.object);
if (children && children.length) return `childrenCount: ${children.length}`; if (children && children.length) return `childrenCount: ${children.length}`;
return 'empty'; return 'empty';
} }

View File

@ -1,3 +1,3 @@
export type { StyleValue } from 'd3tooltip'; export type { StyleValue } from 'd3tooltip';
export { tree } from './charts'; export { tree } from './charts';
export type { InputOptions, Node } from './charts'; export type { Node, Options } from './charts';

View File

@ -4,7 +4,7 @@ import mapValues from 'lodash/mapValues';
export interface Node { export interface Node {
name: string; name: string;
children?: Node[] | null; children?: this[];
object?: unknown; object?: unknown;
value?: unknown; value?: unknown;
} }
@ -44,7 +44,6 @@ function getNode(tree: Node, key: string): Node | null {
} }
export function map2tree( export function map2tree(
// eslint-disable-next-line @typescript-eslint/ban-types
root: unknown, root: unknown,
options: { key?: string; pushMethod?: 'push' | 'unshift' } = {}, options: { key?: string; pushMethod?: 'push' | 'unshift' } = {},
tree: Node = { name: options.key || 'state', children: [] } tree: Node = { name: options.key || 'state', children: [] }