如何将图片添加到可折叠的力树中?

时间:2018-01-23 17:10:07

标签: javascript d3.js treemap force-layout

这里的交易:我对javascript和编码一般都很陌生。但我仍然需要使用D3.js库提交交互式图形。现在我和我的小说都失去了如何继续。到目前为止,我已经设法使用Json数据(给定代码)将图片作为节点和可折叠强制树制作树图。

This is the treemap with pictures

现在,我的想法是使用这个D3 V3 code并将其转换,以便我可以在新的V4版本中使用它。事实证明这对我来说太难了所以我实际上试图复制过程并编写一个新的工作代码。然而,我已经无法在我的节点中放置图片而不是简单的彩色圆圈。

我还需要做的是将我的照片链接到代码中,并让鼠标悬停在效果上,一旦我点击角色,它就会显示有关它们的更多信息。

所以,如果有人会这么善良并帮助我做到这一点。我会永远感激,或者至少告诉我如何添加图片......这将是我图表正确方向的一大步。

plnkr.co/edit/77iRi22Pl3HBIKQ92F5f?p=preview

这是我目前使用的代码......



<!DOCTYPE html>

<head>

  <meta charset="utf-8">
  <title>Alliances of the Marvel Characters in the MCU</title>
  <script type="text/javascript" src="lib/d3.js"></script>

  <style>

    /*evtl. raus damit*/
    body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }

    .node text { font: 16px sans-serif; }
    h1 { font-size: 36px; margin: 10px 0; text-transform: uppercase; font-weight: normal;}
    h2, h3 { font-size: 18px; margin: 5px 0 ; font-weight: normal;}
    header {padding: 20px; position: absolute; top: 0; left: 0;}

    .link {
      fill: none;
      stroke: #ccc;
      stroke-width: 3px;
    }

  </style>

</head>

<body>

  <header>
    <h1>Marvel Characters within the MCU</h1>
      <h2>Click to view their identity</h2>
      <h2>And to close the branches!</h2>
  </header>

  <div class="">
    <div id="myGraph"></div>
  </div>

  <script>
    d3.json('data.json', data => {
        networkChart = renderChartCollapsibleNetwork()
          .svgHeight(window.innerHeight - 30)
          .svgWidth(window.innerWidth - 30)
          .container('#myGraph')
          .data({ root: data })
          .debug(true)
          .run()
        })
  </script>

  <script>
/*

This code is based on following convention:

https://github.com/bumbeishvili/d3-coding-conventions

*/

function renderChartCollapsibleNetwork(params) {

  // exposed variables
  var attrs = {
    id: 'id' + Math.floor(Math.random() * 1000000),
    svgWidth: 960,
    svgHeight: 600,
    marginTop: 0,
    marginBottom: 5,
    marginRight: 0,
    marginLeft: 30,
    nodeRadius: 18,
    container: 'body',
    distance: 150,
    hiddenChildLevel: 1,
    //hiddenChildLevel: 5,
    nodeStroke: '#41302D',
    nodeTextColor: '#ff0000',
    linkColor: '#303030',
    activeLinkColor: "blue",
    hoverOpacity: 0.2,
    maxTextDisplayZoomLevel: 1,
    textDisplayed: true,
    lineStrokeWidth: 1.5,
    data: null
  };



  /*############### IF EXISTS OVERWRITE ATTRIBUTES FROM PASSED PARAM  #######  */

  var attrKeys = Object.keys(attrs);
  attrKeys.forEach(function (key) {
    if (params && params[key]) {
      attrs[key] = params[key];
    }
  })

  //innerFunctions which will update visuals
  var updateData;
  var filter;

  //main chart object
  var main = function (selection) {
    selection.each(function scope() {

      //calculated properties
      var calc = {}
      calc.chartLeftMargin = attrs.marginLeft;
      calc.chartTopMargin = attrs.marginTop;
      calc.chartWidth = attrs.svgWidth - attrs.marginRight - calc.chartLeftMargin;
      calc.chartHeight = attrs.svgHeight - attrs.marginBottom - calc.chartTopMargin;

      //########################## HIERARCHY STUFF  #########################
      var hierarchy = {};
      hierarchy.root = d3.hierarchy(attrs.data.root);


      //###########################   BEHAVIORS #########################
      var behaviors = {};
      behaviors.zoom = d3.zoom().scaleExtent([0.75, 100, 8]).on('zoom', zoomed);
      behaviors.drag = d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended);

      //###########################   LAYOUTS #########################
      var layouts = {};

      // custom radial kayout
      layouts.radial = d3.radial();

      //###########################   FORCE STUFF #########################
      var force = {};
      force.link = d3.forceLink().id(d => d.id);
      force.charge = d3.forceManyBody()
      force.center = d3.forceCenter(calc.chartWidth / 2, calc.chartHeight / 2)

      // prevent collide
      force.collide = d3.forceCollide().radius(d => {

        // if parent has many children, reduce collide strength
        if (d.parent) {
          if (d.parent.children.length > 50) {

            // also slow down node movement
            slowDownNodes();
            return 7;
          }
        }

        // increase collide strength
        if (d.children && d.depth > 2) {
          return attrs.nodeRadius;
        }
        return attrs.nodeRadius * 2;
      });

      //manually set x positions (which is calculated using custom radial layout)
      force.x = d3.forceX()
        .strength(0.2)
        .x(function (d, i) {

          // if node does not have children and is channel (depth=2) , then position it on parent's coordinate
          if (!d.children && d.depth > 2) {
            if (d.parent) {
              d = d.parent
            }
          }

          // custom circle projection -  radius will be -  (d.depth - 1) * 150
          return projectCircle(d.proportion, (d.depth - 1) * 150)[0];
        });


      //manually set y positions (which is calculated using d3.cluster)
      force.y = d3.forceY()
        .strength(0.5)
        .y(function (d, i) {

          // if node does not have children and is channel (depth=2) , then position it on parent's coordinate
          if (!d.children && d.depth > 2) {
            if (d.parent) {
              d = d.parent
            }
          }

          // custom circle projection -  radius will be -  (d.depth - 1) * 150
          return projectCircle(d.proportion, (d.depth - 1) * 150)[1];
        })


      //---------------------------------  INITIALISE FORCE SIMULATION ----------------------------

      // get based on top parameter simulation
      force.simulation = d3.forceSimulation()
        .force('link', force.link)
        .force('charge', force.charge)
        .force('center', force.center)
        .force("collide", force.collide)
        .force('x', force.x)
        .force('y', force.y)

      //###########################   HIERARCHY STUFF #########################

      // flatten root
      var arr = flatten(hierarchy.root);

      // hide members based on their depth
      arr.forEach(d => {
        if (d.depth > attrs.hiddenChildLevel) {
          d._children = d.children;
          d.children = null;
        }
      })

      //####################################  DRAWINGS #######################

      //drawing containers
      var container = d3.select(this);

      //add svg
      var svg = container.patternify({ tag: 'svg', selector: 'svg-chart-container' })
        .attr('width', attrs.svgWidth)
        .attr('height', attrs.svgHeight)
        .call(behaviors.zoom)

      //add container g element
      var chart = svg.patternify({ tag: 'g', selector: 'chart' })
        .attr('transform', 'translate(' + (calc.chartLeftMargin) + ',' + calc.chartTopMargin + ')');


      //################################   Chart Content Drawing ##################################

      //link wrapper
      var linksWrapper = chart.patternify({ tag: 'g', selector: 'links-wrapper' })

      //node wrapper
      var nodesWrapper = chart.patternify({ tag: 'g', selector: 'nodes-wrapper' })
      var nodes, links, enteredNodes;

      // reusable function which updates visual based on data change
      update();

      //update visual based on data change
      function update(clickedNode) {

        //  set xy and proportion properties with custom radial layout
        layouts.radial(hierarchy.root);

        //nodes and links array
        var nodesArr = flatten(hierarchy.root, true)
          .orderBy(d => d.depth)
          .filter(d => !d.hidden);

        var linksArr = hierarchy.root.links()
          .filter(d => !d.source.hidden)
          .filter(d => !d.target.hidden)

        // make new nodes to appear near the parents
        nodesArr.forEach(function (d, i) {
          if (clickedNode && clickedNode.id == (d.parent && d.parent.id)) {
            d.x = d.parent.x;
            d.y = d.parent.y;
          }
        });

        //links
        links = linksWrapper.selectAll('.link').data(linksArr, d => d.target.id);
        links.exit().remove();
        links = links.enter()
          .append('line')
          .attr('class', 'link')
          .merge(links).attr('stroke', '#9ecae1');
        links.attr('stroke', attrs.linkColor).attr('stroke-width', attrs.lineStrokeWidth)

        //node groups
        nodes = nodesWrapper.selectAll('.node').data(nodesArr, d => d.id);
        var exited = nodes.exit().remove();
        var enteredNodes = nodes.enter()
          .append('g')
          .attr('class', 'node')

        //bind event handlers
        enteredNodes.on('click', nodeClick)
          .on('mouseenter', nodeMouseEnter)
          .on('mouseleave', nodeMouseLeave)
          .call(behaviors.drag)

        //node texts
        enteredNodes.append('text').attr('class', 'node-texts')
          .attr('x', 60).attr('fill', attrs.nodeTextColor)
          .text(d => d.data.name)
          .style('display', attrs.textDisplayed ? "initial" : "none")

        //channels grandchildren
        var channelsGrandchildren = enteredNodes
          .append("circle")
          .attr('r', 50)
          .attr('stroke-width', 2)
          .attr('stroke', attrs.nodeStroke)



        //merge  node groups and style it
        nodes = enteredNodes.merge(nodes);
        nodes
          .attr('fill', d => {
            return d._children ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
          })
          .style('cursor', 'pointer')



        //force simulation
        force.simulation.nodes(nodesArr)
          .on('tick', ticked);

        // links simulation
        force.simulation.force("link").links(links).id(d => d.id).distance(100).strength(d => 1);
      }

      //####################################### EVENT HANDLERS  ########################

      // zoom handler
      function zoomed() {

        //get transform event
        var transform = d3.event.transform;
        attrs.lastTransform = transform;

        // apply transform event props to the wrapper
        chart.attr('transform', transform)

        svg.selectAll('.node').attr("transform", function (d) { return `translate(${d.x},${d.y}) scale(${1 / (attrs.lastTransform ? attrs.lastTransform.k : 1)})`; });
        svg.selectAll('.link').attr("stroke-width", attrs.lineStrokeWidth / (attrs.lastTransform ? attrs.lastTransform.k : 1));

        // hide texts if zooming is less than certain level
        if (transform.k < attrs.maxTextDisplayZoomLevel) {
          svg.selectAll('.node-texts').style('display', 'none');
          attrs.textDisplayed = false;
        } else {
          svg.selectAll('.node-texts').style('display', 'initial');
          attrs.textDisplayed = true;
        }
      }


      //tick handler
      function ticked() {

        // set links position
        links.attr("x1", function (d) { return d.source.x; })
          .attr("y1", function (d) { return d.source.y; })
          .attr("x2", function (d) { return d.target.x; })
          .attr("y2", function (d) { return d.target.y; });

        //set nodes position
        svg.selectAll('.node').attr("transform", function (d) { return `translate(${d.x},${d.y}) scale(${1 / (attrs.lastTransform ? attrs.lastTransform.k : 1)})`; });
      }

      //handler drag start event
      function dragstarted(d) {

        //disable node fixing
        nodes.each(d => { d.fx = null; d.fy = null })
      }


      // handle dragging event
      function dragged(d) {

        // make dragged node fixed
        d.fx = d3.event.x;
        d.fy = d3.event.y;


      }



      //-------------------- handle drag end event ---------------
      function dragended(d) {
        // we are doing nothing, here , aren't we?
      }

      //-------------------------- node mouse hover handler ---------------
      function nodeMouseEnter(d) {

        //get hovered node
        var node = d3.select(this);

        //get links
        var links = hierarchy.root.links();

        //get hovered node connected links
        var connectedLinks = links.filter(l => l.source.id == d.id || l.target.id == d.id);

        //get hovered node linked nodes
        var linkedNodes = connectedLinks.map(s => s.source.id).concat(connectedLinks.map(d => d.target.id))

        //reduce all other nodes opacity
        nodesWrapper.selectAll('.node')
          .filter(n => linkedNodes.indexOf(n.id) == -1)
          .attr('opacity', attrs.hoverOpacity);

        //reduce all other links opacity
        linksWrapper.selectAll('.link').attr('opacity', attrs.hoverOpacity);


        //highlight hovered nodes connections
        linksWrapper.selectAll('.link')
          .filter(l => l.source.id == d.id || l.target.id == d.id)
          .attr('opacity', 1)
          .attr('stroke', attrs.activeLinkColor)



      }

      // --------------- handle mouseleave event ---------------
      function nodeMouseLeave(d) {

        // return things back to normal
        nodesWrapper.selectAll('.node').attr('opacity', 1);
        linksWrapper.selectAll('.link').attr('opacity', 1).attr('stroke', attrs.linkColor)
      }

      // --------------- handle node click event ---------------
      function nodeClick(d) {

        //free fixed nodes
        nodes.each(d => { d.fx = null; d.fy = null })

        // collapse or expand node
        if (d.children) {
          d._children = d.children;
          d.children = null;
          update();
          force.simulation.restart();
          force.simulation.alphaTarget(0.15);
        } else if (d._children) {
          d.children = d._children;
          d._children = null;
          update(d);
          force.simulation.restart();
          force.simulation.alphaTarget(0.15);
        } else {
          //nothing is to collapse or expand
        }
        freeNodes();
      }

      //#########################################  UTIL FUNCS ##################################
      updateData = function () {
        main.run();
      }

      function slowDownNodes() {
        force.simulation.alphaTarget(0.05);
      };

      function speedUpNodes() {
        force.simulation.alphaTarget(0.45);
      }

      function freeNodes() {
        d3.selectAll('.node').each(n => { n.fx = null; n.fy = null; })
      }

      function projectCircle(value, radius) {
        var r = radius || 0;
        var corner = value * 2 * Math.PI;
        return [Math.sin(corner) * r, -Math.cos(corner) * r]

      }



      //recursively loop on children and extract nodes as an array
      function flatten(root, clustered) {
        var nodesArray = [];
        var i = 0;
        function recurse(node, depth) {
          if (node.children)
            node.children.forEach(function (child) {
              recurse(child, depth + 1);
            });
          if (!node.id) node.id = ++i;
          else ++i;
          node.depth = depth;
          if (clustered) {
            if (!node.cluster) {
              // if cluster coordinates are not set, set it
              node.cluster = { x: node.x, y: node.y }
            }
          }
          nodesArray.push(node);
        }
        recurse(root, 1);
        return nodesArray;
      }

      function debug() {
        if (attrs.isDebug) {
          //stringify func
          var stringified = scope + "";

          // parse variable names
          var groupVariables = stringified
            //match var x-xx= {};
            .match(/var\s+([\w])+\s*=\s*{\s*}/gi)
            //match xxx
            .map(d => d.match(/\s+\w*/gi).filter(s => s.trim()))
            //get xxx
            .map(v => v[0].trim())

          //assign local variables to the scope
          groupVariables.forEach(v => {
            main['P_' + v] = eval(v)
          })
        }
      }
      debug();
    });
  };

  //----------- PROTOTYEPE FUNCTIONS  ----------------------
  d3.selection.prototype.patternify = function (params) {
    var container = this;
    var selector = params.selector;
    var elementTag = params.tag;
    var data = params.data || [selector];

    // pattern in action
    var selection = container.selectAll('.' + selector).data(data)
    selection.exit().remove();
    selection = selection.enter().append(elementTag).merge(selection)
    selection.attr('class', selector);
    return selection;
  }

  // custom radial layout
  d3.radial = function () {
    return function setProportions(root) {
      recurse(root, 0, 1);
      function recurse(node, min, max) {
        node.proportion = (max + min) / 2;
        if (!node.x) {

          // if node has parent, match entered node positions to it's parent
          if (node.parent) {
            node.x = node.parent.x;
          } else {
            node.x = 0;
          }
        }

        // if node had parent, match entered node positions to it's parent
        if (!node.y) {
          if (node.parent) {
            node.y = node.parent.y;
          } else {
            node.y = 0;
          }
        }

        //recursively do the same for children
        if (node.children) {
          var offset = (max - min) / node.children.length;
          node.children.forEach(function (child, i, arr) {
            var newMin = min + offset * i;
            var newMax = newMin + offset;
            recurse(child, newMin, newMax);
          });
        }
      }
    }
  }

  //https://github.com/bumbeishvili/d3js-boilerplates#orderby
  Array.prototype.orderBy = function (func) {
    this.sort((a, b) => {
      var a = func(a);
      var b = func(b);
      if (typeof a === 'string' || a instanceof String) {
        return a.localeCompare(b);
      }
      return a - b;
    });
    return this;
  }


  //##########################  BOILEPLATE STUFF ################

  //dinamic keys functions
  Object.keys(attrs).forEach(key => {
    // Attach variables to main function
    return main[key] = function (_) {
      var string = `attrs['${key}'] = _`;
      if (!arguments.length) { return eval(` attrs['${key}'];`); }
      eval(string);
      return main;
    };
  });

  //set attrs as property
  main.attrs = attrs;

  //debugging visuals
  main.debug = function (isDebug) {
    attrs.isDebug = isDebug;
    if (isDebug) {
      if (!window.charts) window.charts = [];
      window.charts.push(main);
    }
    return main;
  }

  //exposed update functions
  main.data = function (value) {
    if (!arguments.length) return attrs.data;
    attrs.data = value;
    if (typeof updateData === 'function') {
      updateData();
    }
    return main;
  }

  // run  visual
  main.run = function () {
    d3.selectAll(attrs.container).call(main);
    return main;
  }

  main.filter = function (filterParams) {
    if (!arguments.length) return attrs.filterParams;
    attrs.filterParams = filterParams;
    if (typeof filter === 'function') {
      filter();
    }
    return main;
  }

  return main;
}

  </script>
</body>
&#13;
&#13;
&#13;

这是链接的Json:

&#13;
&#13;
{
  "name": "MCU",
  "icon": "Logos/Marvel-Cinematic-Universe.png",
  "size": 40000,
  "children": [
    {
      "name": "Heroes",
      "children": [
        {
          "name": "Avengers",
          "img": "Logos/Avengers.jpg",
          "size": 40000,
          "children": [
            {
              "name": "Steven 'Steve' Grant Rogers",
              "hero": "Captain America",
              "img": "Characters/Avengers/Cap-America.jpg",
              "size": 40000
            },
            {
              "name": "Anthony 'Tony' Edward Stark",
              "hero": "Iron Man",
              "img": "Characters/Avengers/Iron Man.jpg",
              "size": 40000
            },
            {
              "name": "Thor Odinson",
              "hero": "THOR",
              "img": "Characters/Avengers/thor.jpg",
              "size": 40000
            },
            {
              "name": "Natalia 'Natasha' Alianova Romanoff",
              "hero": "Black Widow",
              "img": "Characters/Avengers/Black Widow.jpg",
              "size": 40000
            },
            {
              "name": "Clinton 'Clint' Francis Barton",
              "hero": "Hawkeye",
              "img": "Characters/Avengers/Hawkeye.jpg",
              "size": 40000
            },
            {
              "name": "Dr. Bruce Banner",
              "hero": "HULK",
              "img": "Characters/Avengers/HULK.jpg",
              "size": 40000
            },
            {
              "name": "Sam Wilson",
              "hero": "Falcon",
              "img": "Characters/Avengers/Falcon.jpg",
              "size": 40000
            },
            {
              "name": "Wanda Maximoff",
              "hero": "Scarlet Witch",
              "img": "Characters/Avengers/Scarlet Witch.jpg",
              "size": 40000
            },
            {
              "name": "Vision",
              "hero": "Vision",
              "img": "Characters/Avengers/Vision.jpg",
              "size": 40000
            },
            {
              "name": "Collonel James 'Rhodey' Rhodes",
              "hero": "Iron Patriot",
              "img": "Characters/Avengers/Iron Patriot.jpg",
              "size": 40000
            }
          ]
        },
        {
          "name": "Revengers",
          "img": "Logos/Revengers.png",
          "size": 40000,
          "children": [
            {
              "name": "Thor Odinson",
              "hero": "THOR, the God of Thunder",
              "img": "Characters/Revengers/thor ragnarok.jpg",
              "size": 40000
            },
            {
              "name": "Loki Laufeyson",
              "hero": "Loki, the God of Mischief",
              "img": "Characters/Revengers/Loki.jpg",
              "size": 40000
            },
            {
              "name": "Dr. Bruce Banner / HULK",
              "hero": "HULK",
              "img": "Characters/Avengers/HULK.jpg",
              "size": 40000
            },
            {
              "name": "Valkyrie",
              "hero": "Valkyrie",
              "img": "Characters/Revengers/Valkyrie.jpg",
              "size": 40000
            }
          ]
        },
        {
          "name": "Guardians of the Galaxy",
          "img": "Logos/Guardians.png",
          "size": 40000,
          "children": [
            {
              "name": "Peter Quill",
              "hero": "Star Lord",
              "img": "Characters/Guardians/Peter Quill.jpg",
              "size": 40000
            },
            {
              "name": "Drax",
              "hero": "Drax, the Destroyer",
              "img": "Characters/Guardians/Drax.jpg",
              "size": 40000
            },
            {
              "name": "Rocket",
              "hero": "Rocket Racoon",
              "img": "Characters/Guardians/Rocket Racoon.jpg",
              "size": 40000
            },
            {
              "name": "Gamora",
              "hero": "Gamora",
              "img": "Characters/Guardians/Gamora.jpg",
              "size": 40000
            },
            {
              "name": "Groot",
              "hero": "I am Groot!",
              "img": "Characters/Guardians/Groot.jpg",
              "size": 40000
            }
          ]
        },
        {
          "name": "Howling Commando",
          "img": "Logos/Howling-Commando.png",
          "size": 40000,
          "children": [
            {
              "name": "Steven 'Steve' Grant Rogers",
              "hero": "Captain America",
              "img": "Characters/Avengers/Cap-America.jpg",
              "size": 40000
            },
            {
              "name": "James 'Bucky' Buchanan Barnes",
              "hero": "Bucky",
              "img": "Characters/Howling-Commando/BuckyBarnes.jpg",
              "size": 40000
            }
          ]
        },
        {
          "name": "Peter Benjamin Parker",
          "hero": "Spiderman",
          "img": "Characters/Avengers/Spiderman.jpg",
          "size": 40000
        },
        {
          "name": "Dr. Stephen Strange",
          "hero": "Doctor Strange",
          "img": "Characters/Doctor Strange",
          "size": 40000
        },
        {
          "name": "Scott Lang",
          "hero": "Ant Man",
          "img": "Characters/Avengers/Ant Man.jpg",
          "size": 40000
        },
        {
          "name": "T'Challa",
          "hero": "Black Panther",
          "img": "Characters/Black Panther.jpg",
          "size": 40000
        }
      ]
    },
    {
      "name": "Aliances",
      "children": [
        {
          "name": "SHIELD",
          "img": "Logos/SHIELD.png",
          "size": 40000,
          "children": [
            {
              "name": "Director Nicholas 'Nick' Fury",
              "hero": "Nick Fury",
              "img": "Characters/SHIELD/Nick Fury.jpg",
              "size": 40000
            },
            {
              "name": "Agent Phil Coulson",
              "hero": "Coulson",
              "img": "Characters/SHIELD/Phil Coulson.jpg",
              "size": 40000
            }
          ]
        }
      ]
    }
  ]
}
&#13;
&#13;
&#13;

0 个答案:

没有答案