block by sxywu 4d221a4492f1377d32ce

visfest block visualization 3

Full Screen

Built with blockbuilder.org

forked from sxywu‘s block: visfest block visualization

forked from sxywu‘s block: visfest block visualization 2

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
  <style>
    body {      margin:0;position:absolute;top:0;right:0;bottom:0;left:0;
        overflow-y: scroll;}
    svg { width: 1024px; height: 1024px; }
    .brush .extent {
      fill: #555;
      fill-opacity: .1;
      stroke: #fff;
      shape-rendering: crispEdges;
    }
  </style>
</head>

<body>
  <script>
    var margin = {top: 20, right: 10, bottom: 20, left: 10};
    var width = 1024 - margin.left - margin.right;
    var height = 1024 - margin.top - margin.bottom;
    var duration = 200;
    var radius = 450;
    var apiSize = 60;
    var ignoreApi = ['d3.select', 'd3.selectAll'];
    var selected = {};
    var hovered = false;
    var brushing = false;
    var force = d3.layout.force()
      .size([width, height])
      .charge(function(d) {return d.type === 'api' ? 5 : -Math.pow(d.size, 2)})
      .linkStrength(function(d) {return d.strength})
      .on('end', updateGraph);
    var nodes = [],
        links = [];
  
    var circle, path, text, summary;
    var svg = d3.select("body").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 + ")");
    var div = d3.select('body').append('div')
    	.style({
        'width': 1024
      });
    svg.append("g")
      .attr("class", "brush")
      .call(d3.svg.brush()
        .x(d3.scale.identity().domain([0, width]))
        .y(d3.scale.identity().domain([0, height]))
        .on('brushstart', function() {brushing = true;})
        .on("brush", brush)
        .on('brushend', function() {brushing = false;}));
    
    function enterGraph() {
      path = svg.selectAll('line')
        .data(links).enter().insert('line', '.brush')
        .attr('stroke', '#ccc')
        .attr('stroke-linecap', 'round');
      circle = svg.selectAll('circle')
        .data(nodes).enter().append('circle')
        .attr('stroke-width', 2)
      	.style({'cursor': 'pointer'})
        .on('mouseover', hover)
        .on('mouseleave', unhover)
        .on('click', select);
      text = svg.selectAll('text')
        .data(_.filter(nodes, function(node) {return node.type === 'api';}))
        .enter().append('text')
        .attr('text-anchor', 'middle')
        .attr('dy', '.35em')
        .attr('fill', '#555')
        .style({
          'font-size': '12px',
          'font-family': 'Helvetica',
          'font-weight': 600,
        	'cursor': 'pointer'
        }).on('mouseover', hover)
        .on('mouseleave', unhover)
        .on('click', select);
    };
    function updateGraph() { 
      circle.attr('r', function(d) {return d.size})
        .attr('fill', function(d) {return d.fill || '#fff'})
        .attr('fill-opacity', function(d) {return d.type === 'api' ? .25 : 1})
        .attr('stroke', function(d) {return d.stroke || 'none'})
        .attr('cx', function(d) {return d.x})
        .attr('cy', function(d) {return d.y});
      text.attr('x', function(d) {return d.x})
        .attr('y', function(d) {return d.y + 10})
        .text(function(d) {return d.id});
      
      path.attr('stroke-width', function(d) {return d.size})
        .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});
    };
    
    function renderSummary() {
      var selectedData = _.filter(selected, function(d) {return d.type !== 'api'});
      summary = div.selectAll('span').data(selectedData);
      
      var enterSummary = summary.enter().append('span');
      enterSummary.append('img');
      enterSummary.append('a');
      
      summary.style({
        'margin': '10px 5px',
        'width': 300,
        'position': 'relative'
      });
      
      summary.select('img')
      	.attr('src', function(d) {return d.image})
      	.attr('width', 230)
      	.attr('height', 120)
      	.style({
        	'border': '1px solid #555',
        	'border-radius': '3px',
        	'margin': 5
        });
      
      summary.select('a')
      	.attr('href', function(d) {
        	return '//bl.ocks.org/' + d.user + '/' + d.id;
        }).attr('target', '_new')
        .style({
        	'font-family': 'Garamond',
        	'display': 'block',
        	'color': '#555',
        	'width': '100%',
        	'text-align': 'center'
        }).text(function(d) {return d.title + ' (' + d.user + ')';});
      
      summary.exit().remove();
    };
    
    function hover() {
      hovered = d3.select(this).datum();
      calculateHighlight(hovered, true, 'hovered');
      applyHighlight();
      renderSummary();
    }
    
    function unhover() {
      hovered = false;
      calculateHighlight(d3.select(this).datum(), false, 'hovered');
      applyHighlight();
      renderSummary();
    }
    
    function select() {
      var data = d3.select(this).datum();
      if (selected[data.id]) {
        delete selected[data.id];
        calculateHighlight(data, false, 'selected');
      } else {
        selected[data.id] = data;
        calculateHighlight(data, true, 'selected');
      }
      renderSummary();
    }
    
    function brush() {
      selected = {};
      var extent = d3.event.target.extent();
      circle.each(function(d) {
        if (extent[0][0] <= d.x && d.x < extent[1][0]
            && extent[0][1] <= d.y && d.y < extent[1][1]) {
        	selected[d.id] = d; 
        }
      });
      
      calculateHighlight(null, true, 'selected');
      applyHighlight();
      renderSummary();
    }
    
    function calculateHighlight(data, highlight, type) {
      var nodesToHighlight = {};

      path.each(function(d) {
        if ((data && d.source.id === data.id) || selected[d.source.id]) {
          nodesToHighlight[d.target.id] = selected[d.source.id] ? true : highlight;
          d[type] = selected[d.source.id] ? true : highlight;
        } else if ((data && d.target.id === data.id) || selected[d.target.id]) {
          nodesToHighlight[d.source.id] = selected[d.target.id] ? true : highlight;
          d[type] = selected[d.target.id] ? true : highlight;
        } else {
          d[type] = false;
        }
      });

      circle.each(function(d) {
        if ((data && d.id === data.id) || selected[d.id]) {
          d[type] = selected[d.id] ? true : highlight;
        } else if (nodesToHighlight[d.id]) {
          d[type] = nodesToHighlight[d.id];
        } else {
          d[type] = false;
        }
      });

      text.each(function(d) {
        if ((data && d.id === data.id) || selected[d.id]) {
          d[type] = selected[d.id] ? true : highlight;
        } else if (nodesToHighlight[d.id]) {
          d[type] = nodesToHighlight[d.id];
        } else {
          d[type] = false;
        }
      });
    }
    
    function applyHighlight() {
      path.transition().duration(brushing ? 0 : duration)
        .attr('stroke-opacity', function(d) {
        	return d.selected || d.hovered ||
            (_.isEmpty(selected) && !hovered) ? 1 : .15;
        });
      circle.transition().duration(brushing ? 0 : duration)
        .attr('fill-opacity', function(d) {
          if (d.selected || d.hovered) {
            return d.type === 'api' ? .75 : 1;
          } else if (_.isEmpty(selected) && !hovered) {
            return d.type === 'api' ? .25 : 1;
          } else {
            return .15;
          }
        }).attr('stroke-opacity', function(d) {
        	return d.selected || d.hovered ||
            (_.isEmpty(selected) && !hovered) ? 1 : .15;
        });
      text.transition().duration(brushing ? 0 : duration)
        .attr('fill-opacity', function(d) {
          return d.selected || d.hovered ||
            (_.isEmpty(selected) && !hovered) ? 1 : .15;
        });
    }

    d3.json('data.json', function(data) {
      var linkStrengths = [];
      var api = _.chain(data)
        .pluck('api')
        .map(function(api) {return _.pairs(api)})
        .flatten().compact()
        .filter(function(api) {return !_.contains(ignoreApi, api[0])})
        .reduce(function(memo, api) {
          linkStrengths.push(api[1]);
          if (!memo[api[0]]) {
            memo[api[0]] = 0;
          }
          memo[api[0]] += api[1];
          return memo;
        }, {})
        .value();
      var colors = d3.scale.category20();
      var sizeScale = d3.scale.linear()
        .domain([_.min(api), _.max(api)])
        .range([5, 40]);
      var widthScale = d3.scale.linear()
      	.domain([_.min(linkStrengths), _.max(linkStrengths)])
        .range([1, 5]);
      var strengthScale = d3.scale.linear()
        .domain([_.min(linkStrengths), _.max(linkStrengths)])
        .range([0, 1]);
      var apiNodes = _.chain(api)
        .map(function(count, name) {
          api[name] = {
            id: name,
            size: sizeScale(count),
            fill: colors(name),
            fixed: true,
            type: 'api'
          };
          return api[name];
        }).sortBy(function(node) {
          return -node.size;  
        }).map(function(node, i) {
          if (i > (apiSize - 1)) {
            delete api[node.id];
          	return; 
          }
          var radian = (2 * Math.PI) / apiSize * i - (Math.PI / 2);
          node.x = radius * Math.cos(radian) + (width / 2);
          node.y = radius * Math.sin(radian) + (height / 2);
          return node;
        }).compact().value();
      var blockNodes = _.map(data, function(block) {
        var node = {
          id: block.id,
          title: block.description,
          user: block.userId,
          image: block.thumbnail,
          size: 5,
          stroke: '#999'
        };
        _.each(block.api, function(count, apiName) {
          if (!api[apiName]) return;
          links.push({
            source: node,
            target: api[apiName],
            size: widthScale(count),
            strength: strengthScale(count)
          });
        });

        return node;
      });
      
      nodes = _.union(apiNodes, blockNodes);
      force.nodes(nodes).links(links);
      
      enterGraph();
      
      force.start();
      _.times(1000, function() {force.tick();});
      force.stop();
    });
  </script>
</body>