P.S.
P.S.

Reputation: 16392

Can't create links between circles after appending new circles in d3.js

First need to say, that I'm totally new to d3.js (I'm using version 4). For now I have nested object of nodes and trying to draw child elements after clicking parent. And it works partly: I can draw child nodes, but not links from parent circle to child circles. I've got this errors:

Error: missing: mammal

and

TypeError: Cannot create property 'vx' on string 'mammal'

Looks like source created successfully, but target breaks at the first link and others:

enter image description here

I'm not sure where the issue is, so I have created a fiddle with all code that I have for now: https://jsfiddle.net/L6c6pxrv/4/

@KEKUATAN suggested me to assign new array to links, don't push each item. The result is: https://jsfiddle.net/L6c6pxrv/3/. There is no errors anymore, but looks like links still doesn't know coordinates of source and target.

Here is what I'm trying to achieve after circle click (here I'm using solid array of nodes and links are defined from the very beginning): https://jsfiddle.net/L6c6pxrv/2/

Thanks for any help!

Upvotes: 0

Views: 619

Answers (1)

KEKUATAN
KEKUATAN

Reputation: 958

just ask if you need explaination i will try if i can, but not now this time i must go home

like i said before each chart on d3 have structure to understand first before make it, uderstand this structure chart , if you see the data array on working findle you will see there is no children object and id or source betwen 2 array have same value. i assume your data dont have same data value betwen 2 data array, id and source, that is why you have

Error: missing: mammal

i add missing mamal to the children object

if you console the node and link, you will find this chart type generating vx and vy

TypeError: Cannot create property 'vx' on string 'mammal'

is a chain error cos there is no mammal so they cant make vx and vy on id = mammal

Bug:

i cant make "M" text on middle of node dancing like other.

Note:

d3.forceSimulation() and d3.forceLink() i think this is the core of creating chart like this and this is the reason they generated cildren or vx and vy

Tips:

make sure the links data and the nodes data have one same value like id and source so you will get the connection, try dont believe me this is just share what i thing i know, not what i know, try have better teacher to better understanding

var nodes = [
        {
            id: "mammal",
            label: "Mammals",
            fill: 'orange',
            color: '#333333',
            children: [
                        {
                    id: "mammal",
                    label: "Dogs",
                    fill: 'yellow',
                    color: 'orangered'
                },
                {
                    id: "dog",
                    label: "Dogs",
                    fill: 'yellow',
                    color: 'orangered'
                },
                {
                    id: "cat",
                    label: "Cats",
                    fill: 'white',
                    color: 'blue'
                },
                {
                    id: "fox",
                    label: "Foxes",
                    fill: 'green',
                    color: 'black'
                },
                {
                    id: "elk",
                    label: "Elk",
                    fill: 'black',
                    color: 'orangered'
                },
                {
                    id: "insect",
                    label: "Insects",
                    fill: '#333333',
                    color: 'lightblue',
                    children: [
                        {
                            id: "pike",
                            label: "Pikes",
                            fill: 'white',
                            color: 'forestgreen'
                        }
                    ]
                },
                {
                    id: "ant",
                    label: "Ants",
                    fill: 'violet',
                    color: 'white'
                },
                {
                    id: "bee",
                    label: "Bees",
                    fill: 'white',
                    color: 'purple'
                },
                {
                    id: "fish",
                    label: "Fish",
                    fill: 'darkblue',
                    color: 'white'
                },
                {
                    id: "carp",
                    label: "Carp",
                    fill: 'purple',
                    color: 'white'
                }
            ]
        }
    ];

    var links = [
        // { target: "mammal", source: "dog" },
        // { target: "mammal", source: "cat" },
        // { target: "mammal", source: "fox" },
        // { target: "mammal", source: "elk" },
        // { target: "mammal", source: "insect" },
        // { target: "mammal", source: "ant" },
        // { target: "mammal", source: "bee" },
        // { target: "mammal", source: "fish" },
        // { target: "mammal", source: "carp" }
        // { target: "insect", source: "pike" }
    ];



    var width = window.innerWidth;
    var height = window.innerHeight;

    var svg = d3.select('svg');

    // Append rect and make it draggable
    svg.append('rect').style('width', width * 2).style('height', height * 2).style('fill', 'transparent');
    var dragcontainer = d3.drag()
        .on("drag", function(d) {
            d3.select(this).attr("transform", "translate(" + (d.x = d3.event.x) + "," + (d.y = d3.event.y) + ")");
        });
    var g = d3.select('svg').select("g").datum({x: 0, y: 0}).call(dragcontainer);

    var groupWrapper = svg.append('g');



    // Zoom
    var zoom = d3.zoom()
        .scaleExtent([-Infinity, 100])
        .on('zoom', zoomFn);

    function zoomFn() {
        d3.select('svg').select('g')
            .attr('transform', 'translate(' + d3.event.transform.x + ',' + d3.event.transform.y + ') scale(' + d3.event.transform.k + ')');
    }

    d3.select('svg').call(zoom);



    // simulation setup with all forces
    var linkForce = d3
        .forceLink()
        .id(function (link) { return link.id })
        .strength(function (link) { return 0.3 });

    var simulation = d3
        .forceSimulation()
        .force('link', linkForce)
        .force('charge', d3.forceManyBody().strength(-5000))
        .force('center', d3.forceCenter(width / 2, height / 2));

    var dragDrop = d3.drag().on('start', function (node) {
        node.fx = node.x;
        node.fy = node.y;
    }).on('drag', function (node) {
        simulation.alphaTarget(0.7).restart();
        node.fx = d3.event.x;
        node.fy = d3.event.y;
    }).on('end', function (node) {
        if (!d3.event.active) {
            simulation.alphaTarget(0);
        }
        node.fx = null;
        node.fy = null;
    });



    var linkElements = groupWrapper.append("g")
        .attr("class", "links")
        /**
         * comment below
         */
        .selectAll("line")
        .data(links)
        .enter().append("line")
        .attr("stroke-width", 1)
        .attr("stroke", "rgba(50, 50, 50, 0.2)");

    var nodeElementsWrapper = groupWrapper.append("g").attr('class', '1');

    var nodeElementsGroup = nodeElementsWrapper
        .attr("class", "nodes")
        .selectAll("circle")
        .data(nodes)
        .enter().append("g")
        .attr('class', 'item')
        .on("click", function(d) {
            console.log("on click", d);
            draw();
        });

    var circleElements = nodeElementsGroup
        .append("circle")
        .attr("r", 100)
        .attr("fill", function(item) {
            return item.fill;
        })
        .attr("stroke", function(item) {
            return item.color;
        })
        .attr("stroke-width", function(item) {

        })
        .call(dragDrop);

    var textElements = d3.selectAll('g.item')
        .each(function(item, i) {
            d3.select(this)
                .append('text')
                .attr("font-size", 50)
                .attr("text-anchor", "middle")
                .attr("alignment-baseline", "central")
                .attr("fill", function () { return item.color })
                .text(function () { return item.id[0].toUpperCase() })

        });



    // Draw children
    function draw() {
        var newLinks = [
            { target: "mammal", source: "dog" },
            { target: "mammal", source: "cat" },
            { target: "mammal", source: "fox" },
            { target: "mammal", source: "elk" },
            { target: "mammal", source: "insect" },
            { target: "mammal", source: "ant" },
            { target: "mammal", source: "bee" },
            { target: "mammal", source: "fish" },
            { target: "mammal", source: "carp" }
        ];

        newLinks.forEach(function(item) {
            links.push(item);
        });

        console.log('LINKS: ', links);

        nodeElementsGroup = nodeElementsWrapper
            .attr("class", "nodes")
            .selectAll("circle")
            .data(nodes[0].children)
            .enter().append("g")
            .attr('class', 'item')
            .on("click", function(d) {
                console.log("on click", d);
            });

        circleElements = nodeElementsGroup
            .data(nodes[0].children)
            .append("circle")
            .attr("r", 100)
            .attr("fill", function(item) {
                return item.fill;
            })
            .attr("stroke", function(item) {
                return item.color;
            })
            .attr("stroke-width", function(item) {

            })
            .call(dragDrop);

        textElements = d3.selectAll('g.item')
            .each(function(item, i) {
            var t = d3.select(this).text()
            if (t=='M'){

            }else{
                d3.select(this)

                    .append('text')
                    .attr("font-size", 50)
                    .attr("text-anchor", "middle")
                    .attr("alignment-baseline", "central")
                    .attr("fill", function () { return item.color })
                    .text(function () { 
                    var s =  item.id[0].toUpperCase()       
                    if (s!=='M'){
                    return s
                                        }
                    }).call(dragDrop)
          }
            })          


        linkElements = d3.select('g.links')
            .attr("class", "links")
            .selectAll("line")
            .data(links)
            .enter().append("line")
            .attr("class", "link")
            .attr("stroke-width", 1)
            .attr("stroke", "rgba(50, 50, 50, 0.2)");

        // simulation
        simulation.nodes(nodes[0].children).on('tick', function() {
            d3.selectAll('circle')
                .attr('cx', function (node) { return node.x })
                .attr('cy', function (node) { return node.y });
            d3.selectAll('text')
                .attr('x', function (node) { return node.x })
                .attr('y', function (node) { return node.y });
            d3.selectAll('line.link')
                .attr('x1', function (link) { return link.source.x })
                .attr('y1', function (link) { return link.source.y })
                .attr('x2', function (link) { return link.target.x })
                .attr('y2', function (link) { return link.target.y })
        });

     //   simulation.force("link").links(d3.selectAll('line.link')); // "links" instead of d3.selectAll('line.link')
    }



    // Simulation
    simulation.nodes(nodes).on('tick', function() {
        circleElements
            .attr('cx', function (node) { return node.x })
            .attr('cy', function (node) { return node.y });
        d3.selectAll('text')
            .attr('x', function (node) { return node.x })
            .attr('y', function (node) { return node.y });
        linkElements
            .attr('x1', function (link) { return link.source.x })
            .attr('y1', function (link) { return link.source.y })
            .attr('x2', function (link) { return link.target.x })
            .attr('y2', function (link) { return link.target.y })
    });

    simulation.force("link").links(links);
html {
  background-color: gray;
}

body {
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

svg {
  position: fixed;
  top: 0;
  width: auto;
  height: auto;
  min-width: 100%;
  min-height: 100%;
  background-color: gray;
}

circle {
  cursor: pointer;
}

text {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;
  user-select: none;
  cursor: pointer;
}

line {
  stroke: #fff;
  stroke-width: 1.5;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
  
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<svg></svg>
</body>
</html>

Upvotes: 1

Related Questions