react-d3-tree icon indicating copy to clipboard operation
react-d3-tree copied to clipboard

Search capabilities?

Open francisngo opened this issue 5 years ago • 2 comments

Is there a feature to search the tree and reposition the node searched into the center of container? Doesn't seem like it but I am wondering how I could start making one.

Search would be based off name or other key fields from nodeData

francisngo avatar May 14 '19 18:05 francisngo

I would love this 👍

SimpleVictor avatar May 26 '19 08:05 SimpleVictor

I have implemented search by searching all node labels for its .innerHTML and then center the container to that position retreived by .getBoundingClientRect()

Code:

class DeviceTopology extends React.Component<DeviceTopologyProps, DeviceTopologyState> {
    treeContainer: HTMLDivElement | null;
    constructor(props: DeviceTopologyProps) {
        super(props);
        this.state = {
            translate: { x: 0, y: 0 },
            data: {},
            nodeCount: 0,
            hoveredNode: {},
            searchFor: '',
            currentSearch: 0,
        };
    }

    handleSearchChange = (event: React.FormEvent<HTMLInputElement>) => {
        this.setState({
            ...this.state,
            searchFor: event.currentTarget.value
        });
    };

    handelSearchSubmit = () => {
        const searchResults = [];
        const allBaseNodes = document.getElementsByClassName("nodeBase");
        for (let i = 0; i < allBaseNodes.length; i = i + 1) {
            const currentNode = allBaseNodes[i].children[1].children[0].children[0];
            if (currentNode.textContent === this.state.searchFor) {
                searchResults.push(currentNode.getBoundingClientRect());
            }
        }
        const allLeafNodes = document.getElementsByClassName("leafNodeBase");
        for (let i = 0; i < allLeafNodes.length; i = i + 1) {
            const currentNode = allLeafNodes[i].children[1].children[0].children[0];
            if (currentNode.textContent === this.state.searchFor) {
                searchResults.push(currentNode.getBoundingClientRect());
            }
        }
        if (searchResults.length && this.treeContainer) {
            const { translate } = this.state
            let { currentSearch, } = this.state
            if (currentSearch >= searchResults.length) {
                currentSearch = 0;
            }
            const dimensions = this.treeContainer.getBoundingClientRect();
            const x = translate.x - searchResults[currentSearch].left + dimensions.width / 2;
            const y = translate.y - searchResults[currentSearch].top + dimensions.height / 2 + 100;
            this.setState({
                translate: {
                    x,
                    y
                },
                currentSearch: currentSearch + 1,
            });
        }

    };


    handleUpdate = (data: any) => {
        const { translate } = data;
        if (translate !== this.state.translate) this.setState({ translate });
    }

    render() {
        return (
            <div id="treeWrapper" style={{ width: '100%', height: '100%' }} ref={tc => (this.treeContainer = tc)}>
                <TopologieViewSearch
                    onChange={this.handleSearchChange}
                    onSearch={this.handelSearchSubmit}
                />
                <Tree
                    data={this.state.data}
                    translate={this.state.translate}
                    orientation='vertical'
                    allowForeignObjects={true}
                    onUpdate={this.handleUpdate}
                    nodeLabelComponent={{
                        render: <DeviceTopologyNodeLabel
                            nodeData={this.state.data}
                            hoveredNode={this.state.hoveredNode}
                            removeUnit={this.props.removeUnit}
                        />,
                        foreignObjectWrapper: {
                            y: -23,
                            width: 70,
                            height: 50,
                        }
                    }}
                    onMouseOver={(nodeData) => { this.setState({ hoveredNode: nodeData }) }}
                    styles={{
                        links: {
                            stroke: '#00376c',
                            strokeWidth: 0.3,
                        }
                    }}
                />
            </div>
        );
    }
}

TopologieViewSearch.tsx

import SearchInput from './SearchInput';
import * as React from 'react';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

export interface TopologieViewSearchProps {
    onChange: (event: React.FormEvent<HTMLInputElement>) => void;
    onSearch: () => void;
}

export interface TopologieViewSearchState { }

export default class TopologieViewSearch extends React.Component<
    TopologieViewSearchProps,
    TopologieViewSearchState
    > {
    render() {
        return (
            <div className="tree__search">
                <SearchInput
                    onChange={this.props.onChange}
                    name='tree-search'
                />
                <button
                    onClick={this.props.onSearch}>
                    <FontAwesomeIcon
                        icon={faSearch}
                        size="sm"
                        className="tree__controls__icon" />
                </button>
            </div >
        );
    }
}

SearchInput.tsx

import * as React from 'react';
import { Translate } from 'react-localize-redux';

export interface SearchInputProps {
    onChange: (event: React.FormEvent<HTMLInputElement>) => void;
    name: string;
    value?: string;
}

export interface SearchInputState { }

export default class SearchInput extends React.Component<
    SearchInputProps,
    SearchInputState
    > {
    render() {
        const { onChange, name, value } = this.props;

        return (
            <div className="search__item">
                <Translate>
                    {({ translate }) =>
                        <input
                            type="text"
                            placeholder={translate("search") as string}
                            className="filterbar__item__select"
                            onChange={onChange}
                            name={name}
                            value={value}
                        />}
                </Translate>
            </div>
        );
    }
}

TomlDev avatar Sep 02 '19 07:09 TomlDev