block by nitaku 92eb6fa2412af3270053

Pie vs. pie III

Full Screen

-

index.js

/* polar layout
*/


(function() {
  var RADIUS, W, arc_generator, bl, bm, br, color, data, dist_scale, height, inner_radius_scale, max, max_dist, pie, pies, polar, polar_data, polar_layout, radius_scale, rand2_6, side, svg, ul, um, ur, width;

  polar = function() {
    var angle, self;

    angle = null;
    self = function(data) {
      angle = 2 * Math.PI / data.length;
      data.forEach(function(d, i) {
        return d.angle = data.length > 2 ? i * angle : (i - 0.25) * angle;
      });
      return data;
    };
    self.angle = function() {
      return angle;
    };
    return self;
  };

  /* ---
  */


  rand2_6 = function() {
    return 2 + Math.round(Math.random() * 6);
  };

  data = d3.range(rand2_6()).map(function(d) {
    return {
      category: "cat_" + d,
      value: Math.random()
    };
  });

  console.log(data);

  max = d3.max(data, function(d) {
    return d.value;
  });

  width = 960;

  height = 500;

  side = Math.min(width, height);

  RADIUS = side / 4 - 20;

  polar_layout = polar();

  polar_data = polar_layout(data);

  console.log(polar_data);

  svg = d3.select("body").append("svg").attr("width", width).attr("height", height).append('g').attr({
    transform: "translate(" + (width / 2) + ", " + (height / 2) + ")"
  });

  ul = svg.append("g").attr({
    transform: "translate(" + (-side / 2) + ", " + (-side / 4) + ")"
  });

  um = svg.append("g").attr({
    transform: "translate(0, " + (-side / 4) + ")"
  });

  ur = svg.append("g").attr({
    transform: "translate(" + (+side / 2) + ", " + (-side / 4) + ")"
  });

  bl = svg.append("g").attr({
    transform: "translate(" + (-side / 2) + ", " + (+side / 4) + ")"
  });

  bm = svg.append("g").attr({
    transform: "translate(0, " + (+side / 4) + ")"
  });

  br = svg.append("g").attr({
    transform: "translate(" + (+side / 2) + ", " + (+side / 4) + ")"
  });

  color = d3.scale.ordinal().range(["#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e", "#e6ab02", "#a6761d", "#666666"]);

  pie = d3.layout.pie().sort(null).value(function(d) {
    return d.value;
  });

  arc_generator = d3.svg.arc().innerRadius(0).outerRadius(RADIUS);

  ul.selectAll('.arc').data(pie(data)).enter().append('path').attr({
    "class": 'arc',
    d: arc_generator,
    fill: function(d, i) {
      return color(i);
    }
  });

  radius_scale = d3.scale.linear().domain([0, max]).range([0, RADIUS]);

  inner_radius_scale = d3.scale.linear().domain([0, max]).range([RADIUS * 0.4, RADIUS]);

  arc_generator = d3.svg.arc().innerRadius(function(d) {
    return inner_radius_scale(max - d.value);
  }).outerRadius(function(d) {
    return radius_scale(max);
  }).startAngle(function(d) {
    return d.angle - polar_layout.angle() / 2;
  }).endAngle(function(d) {
    return d.angle + polar_layout.angle() / 2;
  });

  pies = um.selectAll('.arc').data(polar_data);

  pies.enter().append('path').attr({
    "class": 'arc',
    d: arc_generator,
    fill: function(count, klass) {
      return color(klass);
    }
  });

  max_dist = RADIUS / (1 + Math.sin(polar_layout.angle() / 2));

  dist_scale = d3.scale.sqrt().domain([0, max]).range([0, max_dist]);

  bm.selectAll('.bubble').data(polar_data).enter().append('circle').attr({
    "class": 'bubble',
    cx: function(d) {
      return dist_scale(d.value) * Math.cos(d.angle - Math.PI / 2);
    },
    cy: function(d) {
      return dist_scale(d.value) * Math.sin(d.angle - Math.PI / 2);
    },
    r: function(d) {
      return dist_scale(d.value) * Math.sin(polar_layout.angle() / 2);
    },
    fill: function(d, i) {
      return color(i);
    }
  });

  radius_scale = d3.scale.sqrt().domain([0, max]).range([0, RADIUS]);

  arc_generator = d3.svg.arc().innerRadius(0).outerRadius(function(d) {
    return radius_scale(d.value);
  }).startAngle(function(d) {
    return d.angle - polar_layout.angle() / 2;
  }).endAngle(function(d) {
    return d.angle + polar_layout.angle() / 2;
  });

  bl.selectAll('.arc').data(polar_data).enter().append('path').attr({
    "class": 'arc',
    d: arc_generator,
    fill: function(d, i) {
      return color(i);
    }
  });

  max_dist = RADIUS / (1 + Math.sin(polar_layout.angle() / 2));

  dist_scale = d3.scale.sqrt().domain([0, max]).range([0, max_dist]);

  br.selectAll('.petal').data(polar_data).enter().append('path').attr({
    "class": 'petal',
    d: function(d) {
      var far_x, far_y, l, r, theta, theta_a, theta_b;

      r = dist_scale(d.value) * Math.sin(polar_layout.angle() / 2);
      l = dist_scale(d.value) * Math.cos(polar_layout.angle() / 2);
      theta = d.angle - Math.PI / 2;
      if (l === 0) {
        far_x = (dist_scale(d.value) + r) * Math.cos(theta);
        far_y = (dist_scale(d.value) + r) * Math.sin(theta);
        return "M0 0 A" + r + " " + r + " 0 1 1 " + far_x + " " + far_y + " A" + r + " " + r + " 0 1 1 0 0";
      }
      theta_a = theta - polar_layout.angle() / 2;
      theta_b = theta + polar_layout.angle() / 2;
      return "M0 0 L" + (l * Math.cos(theta_a)) + " " + (l * Math.sin(theta_a)) + " A" + r + " " + r + " 0 1 1 " + (l * Math.cos(theta_b)) + " " + (l * Math.sin(theta_b)) + " z";
    },
    fill: function(d, i) {
      return color(i);
    }
  });

  W = 8;

  dist_scale = d3.scale.linear().domain([0, max]).range([0, RADIUS]);

  ur.selectAll('.stick').data(polar_data).enter().append('path').attr({
    "class": 'stick',
    d: function(d) {
      var b_x, b_y, c_x, c_y, d_x, d_y, delta, e_x, e_y, fx, fy, nx, ny, theta;

      theta = d.angle;
      delta = W / 2 / Math.tan(polar_layout.angle() / 2);
      nx = delta * Math.sin(theta);
      ny = -delta * Math.cos(theta);
      b_x = nx - W / 2 * Math.cos(theta);
      b_y = ny - W / 2 * Math.sin(theta);
      e_x = nx + W / 2 * Math.cos(theta);
      e_y = ny + W / 2 * Math.sin(theta);
      fx = dist_scale(d.value) * Math.sin(theta);
      fy = -dist_scale(d.value) * Math.cos(theta);
      c_x = fx - W / 2 * Math.cos(theta);
      c_y = fy - W / 2 * Math.sin(theta);
      d_x = fx + W / 2 * Math.cos(theta);
      d_y = fy + W / 2 * Math.sin(theta);
      return "M0 0 L" + b_x + " " + b_y + " L" + c_x + " " + c_y + " L" + d_x + " " + d_y + " L" + e_x + " " + e_y + " z";
    },
    fill: function(d, i) {
      return color(i);
    }
  });

}).call(this);

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="description" content="Pie vs. pie III" />
  <title>Pie vs. pie III</title>
  <link rel="stylesheet" href="index.css">
  <script src="//d3js.org/d3.v3.min.js"></script>
</head>
<body>
  <script src="index.js"></script>
</body>
</html>

index.coffee

### polar layout ###
polar = () ->
  # defaults
  #scale = d3.scale.linear
  angle = null
  
  self = (data) ->
    angle = 2*Math.PI/data.length
    
    data.forEach (d, i) ->
      d.angle = if data.length > 2 then i * angle else (i-0.25) * angle
      
    return data
  
  self.angle = () ->
    return angle
  
  return self
    
### --- ###

rand2_6 = () -> 2 + Math.round(Math.random() * 6)

data = d3.range(rand2_6()).map (d) -> { category: "cat_#{d}", value: Math.random() }
console.log data

max = d3.max(data, (d) -> d.value)

width = 960
height = 500
side = Math.min(width,height)
RADIUS = side / 4 - 20


polar_layout = polar()
polar_data = polar_layout(data)
console.log polar_data

svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
  .append('g')
    .attr
      transform: "translate(#{width/2}, #{height/2})"
    
ul = svg.append("g")
    .attr
      transform: "translate(#{-side/2}, #{-side/4})"
    
um = svg.append("g")
    .attr
      transform: "translate(0, #{-side/4})"
    
ur = svg.append("g")
    .attr
      transform: "translate(#{+side/2}, #{-side/4})"
    
bl = svg.append("g")
    .attr
      transform: "translate(#{-side/2}, #{+side/4})"
    
bm = svg.append("g")
    .attr
      transform: "translate(0, #{+side/4})"
    
br = svg.append("g")
    .attr
      transform: "translate(#{+side/2}, #{+side/4})"

color = d3.scale.ordinal()
  .range(["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"])
  

# pie chart
pie = d3.layout.pie()
  .sort(null)
  .value((d) -> d.value )
  
arc_generator = d3.svg.arc()
  .innerRadius(0)
  .outerRadius(RADIUS)
  
ul.selectAll('.arc')
    .data(pie(data))
  .enter().append('path')
    .attr
      class: 'arc'
      d: arc_generator
      fill: (d, i) -> color(i)
      
# arc chart subplotting
radius_scale = d3.scale.linear()
  .domain([0, max])
  .range([0, RADIUS])
  
inner_radius_scale = d3.scale.linear()
  .domain([0, max])
  .range([RADIUS*0.4, RADIUS])
  
arc_generator = d3.svg.arc()
  .innerRadius((d) -> inner_radius_scale(max-d.value))
  .outerRadius((d) -> radius_scale(max))
  .startAngle((d) -> d.angle - polar_layout.angle()/2)
  .endAngle((d) -> d.angle + polar_layout.angle()/2)
  
pies = um.selectAll('.arc')
  .data(polar_data)
  
pies.enter().append('path')
  .attr
    class: 'arc'
    d: arc_generator
    fill: (count, klass) -> color(klass)
    
# radar chart
#outer_polygon_generator = d3.svg.line()
#  .x((d) -> RADIUS*Math.cos(d.angle-Math.PI/2))
#  .y((d) -> RADIUS*Math.sin(d.angle-Math.PI/2))
#
#ur.append("path")
#  .datum(polar_data)
#    .attr
#      class: 'outer_polygon'
#      d: (ds) -> outer_polygon_generator(ds) + 'z'
#      
#ur.selectAll(".radius")
#    .data(polar_data)
#  .enter().append("path")
#    .attr('class','radius')
#    .attr("d", (d) -> "M0 0 L#{RADIUS*Math.cos(d.angle-Math.PI/2)} #{RADIUS*Math.sin(d.angle-Math.PI/2)}")
#    
#polygon_generator = d3.svg.line()
#  .x((d) -> RADIUS/max*d.value*Math.cos(d.angle-Math.PI/2))
#  .y((d) -> RADIUS/max*d.value*Math.sin(d.angle-Math.PI/2))
#  
#ur.append('path')
#    .datum(polar_data)
#    .attr
#      class: 'polygon'
#      d: (ds) -> polygon_generator(ds) + 'z'
#      
#ur.selectAll(".dot")
#    .data(polar_data)
#  .enter().append("circle")
#    .attr('class','dot')
#    .attr
#      cx: (d) -> "#{RADIUS/max*d.value*Math.cos(d.angle-Math.PI/2)}"
#      cy: (d) -> "#{RADIUS/max*d.value*Math.sin(d.angle-Math.PI/2)}"
#      r: 4
#      fill: (d, i) -> color(i) 
      
# bubble chart
max_dist = RADIUS / (1+Math.sin(polar_layout.angle()/2))
dist_scale = d3.scale.sqrt()
  .domain([0, max])
  .range([0, max_dist])
  
bm.selectAll('.bubble')
    .data(polar_data)
  .enter().append('circle')
    .attr
      class: 'bubble'
      cx: (d) -> dist_scale(d.value)*Math.cos(d.angle-Math.PI/2)
      cy: (d) -> dist_scale(d.value)*Math.sin(d.angle-Math.PI/2)
      r: (d) -> dist_scale(d.value)*Math.sin(polar_layout.angle()/2)
      fill: (d, i) -> color(i)
      
#bm.append('circle')
#  .attr
#    r: RADIUS
#    fill: 'none'
#    stroke: 'black'
#    'stroke-dasharray': '1 3'
#    
#bm.append('circle')
#  .attr
#    r: max_dist
#    fill: 'none'
#    stroke: 'black'
#    'stroke-dasharray': '1 3'
      
# polar area chart
radius_scale = d3.scale.sqrt()
  .domain([0, max])
  .range([0, RADIUS])
      
arc_generator = d3.svg.arc()
  .innerRadius(0)
  .outerRadius((d) -> radius_scale(d.value))
  .startAngle((d) -> d.angle - polar_layout.angle()/2)
  .endAngle((d) -> d.angle + polar_layout.angle()/2)
  
bl.selectAll('.arc')
    .data(polar_data)
  .enter().append('path')
    .attr
      class: 'arc'
      d: arc_generator
      fill: (d, i) -> color(i)
      
# flower chart
max_dist = RADIUS / (1+Math.sin(polar_layout.angle()/2))
dist_scale = d3.scale.sqrt()
  .domain([0, max])
  .range([0, max_dist])
  
br.selectAll('.petal')
    .data(polar_data)
  .enter().append('path')
    .attr
      class: 'petal'
      d: (d) ->
        r = dist_scale(d.value) * Math.sin(polar_layout.angle()/2)
        l = dist_scale(d.value) * Math.cos(polar_layout.angle()/2)
        theta = d.angle-Math.PI/2
        
        if l is 0
          # degenerate case (circles are tangent to each other)
          far_x = (dist_scale(d.value)+r) * Math.cos(theta)
          far_y = (dist_scale(d.value)+r) * Math.sin(theta)
          return "M0 0 A#{r} #{r} 0 1 1 #{far_x} #{far_y} A#{r} #{r} 0 1 1 0 0"
        
        
        theta_a = theta - polar_layout.angle()/2
        theta_b = theta + polar_layout.angle()/2
        
        return "M0 0 L#{l*Math.cos(theta_a)} #{l*Math.sin(theta_a)} A#{r} #{r} 0 1 1 #{l*Math.cos(theta_b)} #{l*Math.sin(theta_b)} z"
      fill: (d, i) -> color(i)
      
# stick chart
W = 8
dist_scale = d3.scale.linear()
  .domain([0, max])
  .range([0, RADIUS])
  
ur.selectAll('.stick')
    .data(polar_data)
  .enter().append('path')
    .attr
      class: 'stick'
      d: (d) ->
        theta = d.angle
        delta = W/2 / Math.tan(polar_layout.angle()/2)
        nx = delta*Math.sin(theta)
        ny = -delta*Math.cos(theta)
        b_x = nx - W/2*Math.cos(theta)
        b_y = ny - W/2*Math.sin(theta)
        e_x = nx + W/2*Math.cos(theta)
        e_y = ny + W/2*Math.sin(theta)
        fx = dist_scale(d.value)*Math.sin(theta)
        fy = -dist_scale(d.value)*Math.cos(theta)
        c_x = fx - W/2*Math.cos(theta)
        c_y = fy - W/2*Math.sin(theta)
        d_x = fx + W/2*Math.cos(theta)
        d_y = fy + W/2*Math.sin(theta)
        
        return "M0 0 L#{b_x} #{b_y} L#{c_x} #{c_y} L#{d_x} #{d_y} L#{e_x} #{e_y} z"
      fill: (d, i) -> color(i)
      

index.css

svg {
  background-color: white;
}
.arc, .bubble, .petal, .stick {
  stroke-width: 1;
  stroke: white;
  stroke-linejoin: round;
}
.radius {
  stroke: gray;
  stroke-dasharray: 3 3;
}
.polygon {
  fill: #DDD;
  fill-opacity: 0.5;
  stroke: gray;
}
.outer_polygon {
  fill: none;
  stroke: gray;
  stroke-dasharray: 3 3;
}
.dot {
  stroke: white;
}