d3.run_sankey = function(){
	console.log("f**k yeah");
	const f1 = "sankeygreenhouse.json";
	const f2 = "test.json";
	const jsonFile = "../json/" + f1;

	var units = "Widgets";///why is this the unit of the formatNumber result?
	 
	var margin = {top: 10, right: 10, bottom: 10, left: 10},
	    width = 800 - margin.left - margin.right,
	    height = 500 - margin.top - margin.bottom;
	 
	var formatNumber = d3.format(",.0f"),    // zero decimal places
	    format = function(d) { return formatNumber(d) + " " + units; },
	    color = d3.scale.category20();
	 
	// append the svg canvas to the page
	var svg = d3.select("#chart").append("svg")
	    .attr("width", width + margin.left + margin.right)
	    .attr("height", height + margin.top + margin.bottom)
	  .append("g")
	    .attr("transform", 
	          "translate(" + margin.left + "," + margin.top + ")");
	 
	// Set the sankey diagram properties
	var sankey = d3.sankey()
	    .nodeWidth(36)
	    .nodePadding(10)
	    .size([width, height]);
	 
	var path = sankey.link();
	 
	// load the data
	d3.json(jsonFile, function(error, graph) {
	 
	    var nodeMap = {};
	    graph.nodes.forEach(function(x) { nodeMap[x.name] = x; });
	    graph.links = graph.links.map(function(x) {
	      return {
	        source: nodeMap[x.source],
	        target: nodeMap[x.target],
	        value: x.value
	      };
	    });
	
	  sankey
	      .nodes(graph.nodes)
	      .links(graph.links)
	      .layout(32);
	 
	// add in the links

	  var link = svg.append("g").selectAll(".link")
	      .data(graph.links)
	      .enter().append("path")
	      .attr("class", "link")
	      .attr("d", path)
	      .style("stroke-width", function(d) { 
	      	return Math.max(1, d.dy); 
	      })
	      .sort(function(a, b) { return b.dy - a.dy; });
	 
	// add the link titles
	  link.append("title")
	        .text(function(d) {
	      	return d.source.name + " → " + 
	                d.target.name + "\n" + format(d.value); });
	 
	// add in the nodes
	  var node = svg.append("g").selectAll(".node")
	      .data(graph.nodes)
	    .enter().append("g")
	      .attr("class", "node")
	      .attr("transform", function(d) { 
			  return "translate(" + d.x + "," + d.y + ")"; })
	    .call(d3.behavior.drag()
	      .origin(function(d) { return d; })
	      .on("dragstart", function() { 
			  this.parentNode.appendChild(this); })
	      .on("drag", dragmove));
	 
	// add the rectangles for the nodes
	  node.append("rect")
	      .attr("height", function(d) { return d.dy; })
	      .attr("width", sankey.nodeWidth())
	      .style("fill", function(d) { 
			  return d.color = color(d.name.replace(/ .*/, "")); 
			})
	      .style("stroke", function(d) { 
			  return d3.rgb(d.color).darker(2); })
	    .append("title")
	      .text(function(d) { 
			  return d.name + "\n" + format(d.value); });
	 
	// add in the title for the nodes
	  node.append("text")
	      .attr("x", -6)
	      .attr("y", function(d) { return d.dy / 2; })
	      .attr("dy", ".35em")
	      .attr("text-anchor", "end")
	      .attr("transform", null)
	      .text(function(d) { return d.name; })
	    .filter(function(d) { return d.x < width / 2; })
	      .attr("x", 6 + sankey.nodeWidth())
	      .attr("text-anchor", "start");
	 
	// the function for moving the nodes
	  function dragmove(d) {
	    d3.select(this).attr("transform", 
	        "translate(" + (
	        	   d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))
	        	) + "," + (
	                   d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
	            ) + ")");
	    sankey.relayout();
	    link.attr("d", path);
	  }
	});
}
Пример #2
0
function sankeyVis(slice, payload) {
  const div = d3.select(slice.selector);
  const margin = {
    top: 5,
    right: 5,
    bottom: 5,
    left: 5,
  };
  const width = slice.width() - margin.left - margin.right;
  const height = slice.height() - margin.top - margin.bottom;

  const formatNumber = d3.format(',.2f');

  div.selectAll('*').remove();
  const svg = div.append('svg')
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
    .append('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

  const tooltip = div.append('div')
    .attr('class', 'sankey-tooltip')
    .style('opacity', 0);

  const sankey = d3.sankey()
    .nodeWidth(15)
    .nodePadding(10)
    .size([width, height]);

  const path = sankey.link();

  let nodes = {};
  // Compute the distinct nodes from the links.
  const links = payload.data.map(function (row) {
    const link = Object.assign({}, row);
    link.source = nodes[link.source] || (nodes[link.source] = { name: link.source });
    link.target = nodes[link.target] || (nodes[link.target] = { name: link.target });
    link.value = Number(link.value);
    return link;
  });
  nodes = d3.values(nodes);

  sankey
    .nodes(nodes)
    .links(links)
    .layout(32);

  function getTooltipHtml(d) {
    let html;

    if (d.sourceLinks) { // is node
      html = d.name + " Value: <span class='emph'>" + formatNumber(d.value) + '</span>';
    } else {
      const val = formatNumber(d.value);
      const sourcePercent = d3.round((d.value / d.source.value) * 100, 1);
      const targetPercent = d3.round((d.value / d.target.value) * 100, 1);

      html = [
        "<div class=''>Path Value: <span class='emph'>", val, '</span></div>',
        "<div class='percents'>",
        "<span class='emph'>",
        (isFinite(sourcePercent) ? sourcePercent : '100'),
        '%</span> of ', d.source.name, '<br/>',
        "<span class='emph'>" +
        (isFinite(targetPercent) ? targetPercent : '--') +
        '%</span> of ', d.target.name, 'target',
        '</div>',
      ].join('');
    }
    return html;
  }

  function onmouseover(d) {
    tooltip
      .html(function () { return getTooltipHtml(d); })
     .transition()
      .duration(200)
      .style('left', (d3.event.offsetX + 10) + 'px')
      .style('top', (d3.event.offsetY + 10) + 'px')
      .style('opacity', 0.95);
  }

  function onmouseout() {
    tooltip.transition()
      .duration(100)
      .style('opacity', 0);
  }

  const link = svg.append('g').selectAll('.link')
    .data(links)
    .enter()
    .append('path')
    .attr('class', 'link')
    .attr('d', path)
    .style('stroke-width', d => Math.max(1, d.dy))
    .sort((a, b) => b.dy - a.dy)
    .on('mouseover', onmouseover)
    .on('mouseout', onmouseout);

  function dragmove(d) {
    d3.select(this)
      .attr(
        'transform',
        `translate(${d.x},${(d.y = Math.max(0, Math.min(height - d.dy, d3.event.y)))})`,
      );
    sankey.relayout();
    link.attr('d', path);
  }

  const node = svg.append('g').selectAll('.node')
    .data(nodes)
    .enter()
    .append('g')
    .attr('class', 'node')
    .attr('transform', function (d) {
      return 'translate(' + d.x + ',' + d.y + ')';
    })
    .call(d3.behavior.drag()
      .origin(function (d) {
        return d;
      })
      .on('dragstart', function () {
        this.parentNode.appendChild(this);
      })
      .on('drag', dragmove),
    );
  const minRectHeight = 5;
  node.append('rect')
    .attr('height', d => d.dy > minRectHeight ? d.dy : minRectHeight)
    .attr('width', sankey.nodeWidth())
    .style('fill', function (d) {
      const name = d.name || 'N/A';
      d.color = getColorFromScheme(name.replace(/ .*/, ''), slice.formData.color_scheme);
      return d.color;
    })
    .style('stroke', function (d) {
      return d3.rgb(d.color).darker(2);
    })
    .on('mouseover', onmouseover)
    .on('mouseout', onmouseout);

  node.append('text')
    .attr('x', -6)
    .attr('y', function (d) {
      return d.dy / 2;
    })
    .attr('dy', '.35em')
    .attr('text-anchor', 'end')
    .attr('transform', null)
    .text(function (d) {
      return d.name;
    })
    .filter(function (d) {
      return d.x < width / 2;
    })
    .attr('x', 6 + sankey.nodeWidth())
    .attr('text-anchor', 'start');
}
Пример #3
0
      .then((data) => {
        size.height = data.cells.length * unit;
        svg.attr('height', size.height + margin.top + margin.bottom);
        svg.attr('width', size.width);

        var graph = {
          nodes: [],
          links: []
        };
        var objs = {};

        var targetScale = d3.scale.ordinal().range(['#ddd', '#ccc', '#eee', '#bbb']);

        var currency = data.currency[params.aggregates];
        var valueFormat = that.getValueFormatter(currency);

        _.each(data.cells, (cell) => {
          var source = _.find(cell.dimensions, {keyField: sourceKey});
          var target = _.find(cell.dimensions, {keyField: targetKey});
          var measure = _.find(cell.measures, {key: params.aggregates});

          var sourceId = source.keyValue;
          var targetId = target.keyValue;

          var link = {
            value: measure.value,
            number: valueFormat(measure.value),
            isLink: true
          };

          if (link.value == 0 || !sourceId || !targetId) {
            return;
          }
          sourceId = 'source-' + sourceKey + sourceId;
          targetId = 'target-' + targetKey + targetId;

          if (!objs[sourceId]) {
            graph.nodes.push({
              key: source.keyValue,
              name: source.nameValue,
              color: colorScale(sourceId),
              isSource: true
            });
            objs[sourceId] = {idx: graph.nodes.length - 1};
          }
          link.source = objs[sourceId].idx;

          if (!objs[targetId]) {
            graph.nodes.push({
              key: target.keyValue,
              name: target.nameValue,
              color: targetScale(targetId),
              isTarget: true
            });
            objs[targetId] = {
              idx: graph.nodes.length - 1
            };
          }
          link.target = objs[targetId].idx;
          graph.links.push(link);
        });

        that.sankey = d3.sankey()
          .nodeWidth(unit)
          .nodePadding(unit * 0.6)
          .size([size.width, size.height]);
        var sankey = that.sankey;

        var path = sankey.link();

        sankey
          .nodes(graph.nodes)
          .links(graph.links)
          .layout(32);

        group.selectAll('g').remove();

        var link = group.append('g').selectAll('.link')
          .data(graph.links)
          .enter().append('path')
          .attr('class', 'link')
          .attr('d', path)
          .style('stroke-width', function(d) {
            return Math.max(1, d.dy);
          })
          .style('stroke', function(d) {
            return d.source.color;
          })
          .sort(function(a, b) {
            return b.dy - a.dy;
          })
          .on('click', (d) => {
            that.emit('click', that, d);
          });

        link.append('title')
          .text(function(d) {
            return d.source.name + ' → ' + d.target.name + '\n' + d.number;
          });

        var node = group.append('g').selectAll('.node')
          .data(graph.nodes)
          .enter().append('g')
          .attr('class', 'node')
          .attr('transform', function(d) {
            return 'translate(' + d.x + ',' + d.y + ')';
          })
          .on('click', (d) => {
            that.emit('click', that, d);
          });

        node.append('rect')
          .attr('height', function(d) {
            return d.dy;
          })
          .attr('width', sankey.nodeWidth())
          .style('fill', function(d) {
            return d.color;
          })
          .style('stroke', function(d) {
            return d.color;
          })
          .append('title')
          .text(function(d) {
            return d.name + '\n' + valueFormat(d.value);
          });

        // Add values inside nodes
        node.filter(function(d) {
            // Ignore small nodes
            var estimatedTextWidth = (valueFormat(d.value).length * that.CHAR_WIDTH);
            return d.dy > estimatedTextWidth;
          })
          .append('text')
            .attr('class', 'nodeValue')
	          .text(function (d) { return valueFormat(d.value); })
	          .attr('text-anchor', 'middle')
            .attr('transform', function (d) {
              return 'rotate(-90) translate(' +
                (-d.dy / 2) +
                ', ' +
                (sankey.nodeWidth() / 2 + 5) +
                ')';
            });

        // Add labels inside links
        node.append('text')
          .attr('x', -6)
          .attr('y', function(d) {
            return d.dy / 2;
          })
          .attr('dy', '.35em')
          .attr('text-anchor', 'end')
          .attr('transform', null)
          .text(function(d) {
            return d.name;
          })
          .filter(function(d) {
            return d.x < size.width / 2;
          })
          .attr('x', 6 + sankey.nodeWidth())
          .attr('text-anchor', 'start');


        that.emit('loaded', that, data);
        that.emit('ready', that, data, null);
      })
Пример #4
0
  render() {
    // ========================================================================
    // Set units, margin, sizes
    // ========================================================================
    var margin = { top: 10, right: 0, bottom: 10, left: 0 };
    var width = 690 - margin.left - margin.right;
    var height = 400 - margin.top - margin.bottom;

    var format = (d) => formatNumber(d);
    var formatNumber = d3.format(",.0f"); // zero decimal places

    // ========================================================================
    // Set the sankey diagram properties
    // ========================================================================
    var sankey = d3.sankey()
      .size([width, height])
      .nodeWidth(15)
      .nodePadding(10);

    var path = sankey.link();

    var graph = {
      nodes: _.cloneDeep(this.state.nodes),
      links: _.cloneDeep(this.state.links)
    };

    sankey.nodes(graph.nodes)
      .links(graph.links)
      .layout(32);

    // ========================================================================
    // Initialize and append the svg canvas to faux-DOM
    // ========================================================================
    var svgNode = ReactFauxDOM.createElement('div');
    
    var svg = d3.select(svgNode).append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // ========================================================================
    // Add links
    // ========================================================================
    var link = svg.append("g").selectAll(".link")
      .data(graph.links)
      .enter().append("path")
      .attr("class", "link")
      .on('click', this.props.openModal) // register eventListener
      .attr("d", path)
      .style("stroke-width", (d) => Math.max(1, d.dy))

    // add link titles
    link.append("title")
      .text((d) => d.source.name + " → " + d.target.name + "\n Weight: " + format(d.value));

    // ========================================================================
    // Add nodes
    // ========================================================================
    var node = svg.append("g").selectAll(".node")
      .data(graph.nodes)
      .enter().append("g")
      .attr("class", "node")
      .on('click', this.props.openModal) // register eventListener
      .attr("transform", (d) => "translate(" + d.x + "," + d.y + ")")

    // add nodes rect
    node.append("rect")
      .attr("height", (d) => d.dy)
      .attr("width", sankey.nodeWidth())
      .append("title")
      .text((d) => d.name + "\n" + format(d.value));

    // add nodes text
    node.append("text")
      .attr("x", -6)
      .attr("y", (d) => d.dy / 2)
      .attr("dy", ".35em")
      .attr("text-anchor", "end")
      .text((d) => d.name)
      .filter((d) => d.x < width / 2)
      .attr("x", 6 + sankey.nodeWidth())
      .attr("text-anchor", "start");

    // Above D3 manipaluation equal to following jsx if didn't rely on faux-dom 
    // ------------------------------------------------------------------------
    // var links = graph.links.map((link, i) => {
    //   return (
    //     <g>
    //       <path key={i} className="link" onClick={()=>{this.props.openModal(link)}} d={path(link)} style={{strokeWidth: Math.max(1, link.dy)}}>
    //         <title>{link.source.name + " → " + link.target.name + "\n Weight: " + format(link.value)}</title>
    //       </path>
    //     </g>
    //   );
    // });

    // var nodes = graph.nodes.map((node, i) => {
    //   return (
    //     <g key={i} className="node" onClick={()=>{this.props.openModal(node)}} transform={"translate(" + node.x + "," + node.y + ")"}>
    //       <rect height={node.dy} width={sankey.nodeWidth()}>
    //         <title>{node.name + "\n" + format(node.value)}</title>
    //       </rect>
    //       { (node.x >= width / 2) ? 
    //         <text x={-6} y={node.dy / 2} dy={".35em"} textAnchor={"end"} >{node.name}</text> :
    //         <text x={6 + sankey.nodeWidth()} y={node.dy / 2} dy={".35em"} textAnchor={"start"} >{node.name}</text>
    //       }
    //     </g>
    //   );
    // });

    // ========================================================================
    // Render the faux-DOM to React elements
    // ========================================================================
    return svgNode.toReact();

    // JSX rendering return if didn't rely on faux-dom
    // ------------------------------------------------------------------------
    // return (
    //   <svg width={width + margin.left + margin.right} height={height + margin.top + margin.bottom}>
    //     <g transform={"translate(" + margin.left + "," + margin.top + ")"}>
    //       {links}
    //       {nodes}
    //     </g>
    //   </svg>
    // );
  }
Пример #5
0
function drawSankey(data){
  d3.select('#sankey svg').remove();
  var parentSize = d3.select('#sankey').node().getBoundingClientRect();
  var height = 540;
  var width = parentSize.width;
  var margin = {top:30,left:0,bottom:40,right:0};
  var chartHeight = height - (margin.top + margin.bottom);
  var chartWidth = width - (margin.left + margin.right);
  var nodePadding = 20;
  var sankey = d3.sankey()
    .size([chartWidth, chartHeight])
    .nodeWidth(nodeWidth)
    .nodePadding(nodePadding)
    .nodes(data.nodes)
    .links(data.links)
    .layout(32);

  var path = sankey.link();

  var svg = d3.select('#sankey').append('svg')
    .attr({
      width:width,
      height:height,
      class: 'sankey'
    });

  var gradients = svg.append('defs').selectAll('linearGradient').data(data.links)
    .enter()
      .append('linearGradient')
        .attr({
          id:gradientName,
          x1:'0%',
          x2:'100%',
          y1:'0%',
          y2:'0%'
        });

  gradients.append('stop')
    .attr({
      offset:'0%'
    })
    .style('stop-color',function(d){
      return partyColour(d.source.name);
    })
    .style('stop-opacity',1);

  gradients.append('stop')
    .attr({
      offset:'100%'
    })
    .style('stop-color',function(d){
      return partyColour(d.target.name);
    })
    .style('stop-opacity',1);

  svg.selectAll('text.axis-label').data(['Current','Predicted'])
    .enter()
      .append('text')
        .attr({
          'class':'axis-label',
          'text-anchor':function(d,i){
            if(i===0) return 'start';
            return 'end';
          },
          'transform':function(d,i){
            var xPos = (width-margin.right)*i;
            var yPos = margin.top - 8;
            return 'translate('+xPos+','+yPos+')';
          }
        }).text(function(d){ return d; });

  svg = svg.append('g')
      .attr('transform','translate('+margin.left+','+margin.top+')');

  var link = svg.append('g').selectAll('.link')
      .data(data.links)
    .enter()
      .append('g')
        .attr('class','link-container');

  link.append('path')
      .attr({
        'class':function(d){ return 'link ' + linkClass(d); },
        'data-from':function(d){ return toClass(d.source.name); },
        'data-to':function(d){ return toClass(d.target.name); },
        'd':path
      })
    .style('stroke-width', function(d) { return Math.max(1, d.dy); });

  //start value of each link
  link.append('text')
    .attr({
      'x':nodeWidth +6,
      'y':function(d) {
        return d.source.y + d.sy + (d.dy/2);
      },
      'data-from':function(d){ return toClass(d.source.name); },
      'data-to':function(d){ return toClass(d.target.name); },
      'class':'source link-label inactive',
      'dy':'.35em'
    })
    .text(function(d){
      return partyShortName(d.source.name) +': '+ d.value;
    });

  //end value of each link
  link.append('text')
    .attr({
      'x':chartWidth - (nodeWidth +6),
      'y':function(d) {
        return d.target.y + d.ty + (d.dy/2);
      },
      'data-from':function(d){ return toClass(d.source.name); },
      'data-to':function(d){ return toClass(d.target.name); },
      'dy':'.35em',
      'class':'target link-label inactive',
      'text-anchor':'end'
    })
    .text(function(d){
      return partyShortName(d.target.name) +': '+ d.value;
    });


  var node = svg.append('g').selectAll('.node')
      .data(data.nodes)
    .enter()
      .append('g')
      .attr('class', 'node')
      .attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });

  node.append('rect')
    .attr('height', function(d) { return Math.max(1, d.dy); })
    .attr('width', sankey.nodeWidth())
    .attr('class',function(d){
      return 'node ' + toClass(d.name);
    })
      .append('title')
      .text(function(d) { return d.name + " " + d.value; });

  node.append('rect')
    .attr('height', function(d) { return d.dy + nodePadding; })
    .attr('y', function(d) {
        return -nodePadding/2;
    })
    .attr('x', function(d){
      if(d.x>chartWidth/2) return -chartWidth/2 + sankey.nodeWidth();
      return 0;
    })
    .attr('width', chartWidth/2)
    .attr('class','selection-rect')
    .append('title')
    .text(function(d) { return d.name + " " + d.value; });

  node.append('text')
    .attr({
      'x':-6,
      'y':function(d) { return d.dy / 2; },
      'dy':'.35em',
      'text-anchor':'end',
      'transform':null,
      'class':'target node-label',
      'data-party':function(d){ return toClass(d.name); }
    })
    .text(function(d) {
      return partyShortName(d.name) + ': '+d.value ; }) //TODO
    .filter(function(d) { return d.x < width / 2; })
    .attr({
      'x':6 + sankey.nodeWidth(),
      'class':'source node-label',
      'text-anchor':'start'
    });

  d3.selectAll('.node')
    .on('click',function(d){
      clearSelections();
      var selectionList = buildSelectionList(d);
      selectLink( selectionList.list.join(', ') );
      activateLabels(selectionList.direction, selectionList.party);
    })
    .on('mouseover',function(d){
      clearHint();
      pathHint( buildSelectionList(d).list.join(', ') );
    })
    .on('mouseout', function(){
      clearHint();
    });

  d3.selectAll('path')
    .on('click',function(d){
      clearSelections();
      selectLink( '.'+linkClass(d) );
      activateLabels('both', d.source.name, d.target.name);
    });

  var logoGroup = svg.append('g');
  logoGroup.call(logo);
  logoGroup.attr('transform','translate(' + (width-32) + ',' + (height - 52) + ')');

  var sourceGroup = svg.append('g');
  sourceGroup.attr('transform','translate(0,' + (height-34) + ')');
  sourceGroup.append('text').attr({
    'id':'svg-source',
  }).text('Source: electionforecast.co.uk, ' + updateString);
}
    var _buildVis = function (data) {
      var energy = data.slices;
      div = d3.select(svgRoot);
      if (!energy.nodes.length) return;

      console.log($scope.vis);
      svg = div.append('svg')
        .attr('width', width)
        .attr('height', height + margin)
        .append('g')
        .attr('transform', 'translate(0, 0)');

      var sankey = d3.sankey()
        .nodeWidth(15)
        .nodePadding(10)
        .size([width, height]);
      var path = sankey.link();
      sankey
        .nodes(energy.nodes)
        .links(energy.links)
        .layout(32);

      var link = svg.append('g').selectAll('.link')
        .data(energy.links)
        .enter().append('path')
        .attr('class', 'link')
        .attr('d', path)
        .style('stroke-width', function (d) { return Math.max(1, d.dy);  })
        .sort(function (a, b) { return b.dy - a.dy;  });

      link.append('title')
        .text(function (d) { return d.source.name + ' → ' + d.target.name + '\n' + format(d.value);  });

      var node = svg.append('g').selectAll('.node')
        .data(energy.nodes)
        .enter().append('g')
        .attr('class', 'node')
        .attr('transform', function (d) { return 'translate(' + d.x + ',' + d.y + ')';  })
        .call(d3.behavior.drag()
        .origin(function (d) { return d;  })
        .on('dragstart', function () { this.parentNode.appendChild(this);  })
        .on('drag', dragmove));

      node.append('rect')
        .attr('height', function (d) { return d.dy;  })
        .attr('width', sankey.nodeWidth())
        .style('fill', function (d) { return d.color = color(d.name);  })
        .style('stroke', function (d) { return d3.rgb(d.color).darker(2);  })
        .append('title')
        .text(function (d) { return d.name + '\n' + format(d.value);  });

      node.append('text')
        .attr('x', -6)
        .attr('y', function (d) { return d.dy / 2;  })
        .attr('dy', '.35em')
        .attr('text-anchor', 'end')
        .attr('transform', null)
        .text(function (d) { return d.name;  })
        .filter(function (d) { return d.x < width / 2;  })
        .attr('x', 6 + sankey.nodeWidth())
        .attr('text-anchor', 'start');

      function dragmove(d) {
        d3.select(svgRoot).attr('transform', 'translate(' + d.x + ',' + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ')');
        sankey.relayout();
        link.attr('d', path);
      }
    };