Effect of target

๐Ÿš€ Online instance

The source address

If urgent, CV can first go code modification, where did not understand and then come back to see where.

Github.com/any86/any-t…

Introduce the D3. Js

D3 is a large and comprehensive graphics library that integrates SVG element manipulation with common chart (graph) data structures.

This paper is written based on D3 of V5 version. The functions of D3 are divided into independent packages. Here, we only need to introduce d3-hierarch and D3-Shape to generate topological data structures.

Hierarchy is used to generate d3 tree objects and mount methods such as getting child elements, descendants, and descendants
// tree is used to assign x/y coordinates to nodes
LinkHorizontal is used to generate horizontal connections
import {hierarchy, tree} from 'd3-hierarchy';
import {linkHorizontal} from 'd3-shape';
Copy the code

The code architecture

  1. Use D3 to generate topological data structures (essentially trees, where each node generates x/ Y coordinates).
  2. Render SVG DOM structures using VUE (canvas is also available).
  3. Use any-touch to add “drag” and “click to close/expand child nodes”.
  4. Simply encapsulate an animation function to close/expand the animation, really simple, just for icing on the cake, if you don’t need animation here can be skipped.

Code interpretation ๐Ÿ‘จ๐Ÿซ

The following code looks long, but it’s mostly comments.

D3 generates topological data structures

// Test data
// Common tree
const dataset = {
    name: 'Level 1'.children: [{name: 'Level 2'.children: [{name: 'Grade 3 A'.children: [{name: 'Class 4 A'}, {name: 'Class 4 B',},],},],},},};Copy the code

The code below looks long, but there are only four methods and the rest are comments.

{
    /** * Change the normal tree to the tree required by D3 */
    genTreeData(data) {
        const width = 1000;
        const height = 1000;
        Hierarchy transforms the hierarchy into a D3 tree.
        // Tree has a d3 method, which can get information like tree.descendants/parent/node count, etc
        const root = hierarchy(data);
        // descendants,descendants,descendants
        // But it also contains the current node itself.
        // Add hidden field to the node to control the current node to show/hide.
        root.descendants().forEach((node) = > {
            node.hidden = false;
        });
        // d3.tree returns a function,
        // You can set some nodeSize/separation information for a graph through functions
        // We pass the d3.tree structure data that we just entered into the return function, such as root,
        // Then all the data needed for the topology is complete.
        return (
            tree()
                .separation(function(a, b) {
                    // Adjust the gap ratio for the same elements
                    // Generally use 2:1
                    return (a.parent == b.parent ? 2 : 1) / a.depth;
                })
                // Node size
                .nodeSize([110, width / (root.height + 1)])(root)
        );
    },

    /** * Generate an array of nodes => [Node, Node] * to render elements to the template */
    updateNodes() {
        this.nodes = this.tree.descendants();
    },

    /**
     * ็”Ÿๆˆ็บฟ
     */
    updateLinks() {
        // tree.links generates connection data based on node data
        this.linkPaths = this.tree.links().map((link) = > {
            // d. inkHorizontal is the same as d3.tree,
            // can be used as a constructor,
            // It returns a function
            // Can be specified using the x/y method on the function
            // Since the default generated tree data is the topological data of the upper and lower structure,
            // So the X/Y data needs to be reversed in order to generate left-to-right lines
            {source:{},target:{}}
            if(! link.target.hidden) {return linkHorizontal()
                    .x((d) = > d.y)
                    .y((d) = >d.x)(link); }}); },/** * Generate the required data */
    renderTree() {
        this.tree = this.genTreeData(dataset);
        this.updateLinks();
        this.updateNodes(); }},Copy the code

Generate the DOM structure of SVG using VUE

The DOM structure may seem too long, but it does only two things:

  1. Loop to generate nodes, with<foreignObject>Element implementation SVG can nest normal HTML elements internally.
  2. Loop to generate wires between nodes.
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" style="width:100%">
    <g transform="translate(100, 100)">
        <template v-for="(linkPath, index) in linkPaths">
            <path v-if="linkPath" :key="index" :d="linkPath" class="line" />
        </template>
    </g>

    <g transform="translate(100, 100)">
        <foreignObject
            v-for="(node,index) in nodes"
            v-show=! "" node.hidden"
            :class="{[`at-${action}`]:activeNode===node}"
            :key="'foreignObject'+index"
            :width="itemWidth"
            :height="itemHeight"
            :x="node.y - itemWidth/2"
            :y="node.x - itemHeight/2"
            @panstart="onPanstart(index,$event)"
            @panmove="onPanmove(index,$event)"
            @panend="onPanend"
            @pancancel="onPanend"
            @tap="onTap(index)"
        >
            <body xmlns="http://www.w3.org/1999/xhtml">
                <div class="text">
                    <p>{{node.depth}}</p>
                    <p>Node order: {{index}}</p>
                </div>
            </body>
        </foreignObject>
    </g>
</svg>
Copy the code

Use any-touch to add drag and click

{
    /** * Drag starts and records the current node */
    onPanstart(index, e) {
        const [item] = this.nodes.splice(index, 1);
        this.nodes.push(item);
        this.activeNode = item;
    },

    /** * In drag * change node coordinates * regenerate line data */
    onPanmove(index, e) {
        this.action = e.type;
        const { deltaX, deltaY } = e;
        const { length } = this.nodes;
        this.activeNode.x += deltaY;
        this.activeNode.y += deltaX;
        this.updateLinks();
    },

    /** * Deactivates the current node */
    onPanend() {
        this.activeNode = null;
    },

    /** * collapses/expands the child node */
    onTap(index) {
            this.activeNode = this.nodes[index];
            // Whether the current node record is folded/expanded
            if (void 0= = =this.activeNode.collapse) {
                this.$set(this.activeNode, 'collapse'.true);
            } else {
                this.activeNode.collapse = !this.activeNode.collapse;
            }
            const { x, y, collapse } = this.activeNode;
            // Descendants return child nodes that contain themselves, so exclude themselves
            const [a, ...childNodes] = this.activeNode.descendants();
            // Expand/collapse the child node display according to the node collapse state
            childNodes.forEach((node) = > {
                if (collapse) {
                    const x1 = node.x;
                    const y1 = node.y;
                    // Store the location of the expansion,
                    // next time to restore the position
                    node._x = x1;
                    node._y = y1;
                    animate(1.0.200, (value, isDone) => {
                        node.x = x - (x - x1) * value;
                        node.y = y - (y - y1) * value;
                        if (isDone) {
                            node.hidden = true;
                        }
                        this.updateLinks();
                    });
                } else {
                    node.hidden = false;
                    // Let the value change from 0-1 for 200ms
                    // So that the node position changes to expand the shrink animation
                    animate(0.1.200, (value) => {
                        node.x = x + (node._x - x) * value;
                        node.y = y + (node._y - y) * value;
                        this.updateLinks(); }); }}); }}Copy the code

Animate (animate)

Source: github.com/any86/any-t…

EaseInOut is a function with a time of x axis and a value of y axis. After all, you can use the animate function. Try writing one yourself, for example with math.sin.

Animation in this case is the icing on the cake, the logic is very simple not to explain, if you are interested in comment.

/** * t time * b start value * C target value * d required time ** /
function easeInOut(t, b, c, d) {
    if ((t /= d / 2) < 1) return c / 2 * t * t + b;
    return -c / 2 * ((--t) * (t - 2) - 1) + b;
}

/** * easeInOut ** / with requestAnimationFrame
export function animate(from = 0, to = 0, duration = 1000, callback = () = >void 0) {
    const startTime = window.performance.now();
    function run() {
        const timeDiff = window.performance.now() - startTime;
        const value = easeInOut(timeDiff, from, to - from, duration);
        if (timeDiff <= duration) {
            callback(value);
            requestAnimationFrame(run);
        } else {
            // Fix out of bounds
            callback(to, true);
        }
    }
    run();
};
Copy the code

Full code: github.com/any86/any-t…

In the future

Plan to package into VUE components and open source, all depends on your feedback, if you support this plan, please leave a comment below โ˜Ž๏ธ.

๐Ÿ”ฅtypescript

The basic tutorial starts here

Lesson one: Play with typescript

Lesson two, basic types and introductory advanced types

Lesson three: Generics

Lesson four: Reading advanced types

Lesson 5: What is a namespace

Special, learn typescript in vue3๐Ÿ”ฅ source ๐Ÿฆ• – “is”

Lesson 6. What is a declare? ๐Ÿฆ• – Global declaration

๐Ÿ”ฅtypescript – Full screen browser (59 lines)

๐Ÿ”ฅ previous popular articles

๐Ÿ”ฅ Common regulars daquan 2020

๐Ÿš† novice front end don’t panic! Here you โœŠ10 straws ๐Ÿƒ

True.1px border, ๐Ÿš€ supports any number of edges and rounded corners, 1 catall method

๐Ÿš€ Reveal 5 “wheels not made by author” in vue/ React component library ๐Ÿคš

The Vue/React UI library uses several DOM apis ๐Ÿš€

weibo

Just play micro blog, we can follow each other, hey hey

WeChat group

Thank you for your reading. If you have any questions, you can add me to the wechat group, and I will pull you into the wechat group (Because Tencent limits the number of wechat groups to 100, when the number exceeds 100, you must be joined by group members).