Beispiel #1
0
  draw: function (options, width) {
    var data = options.data;
    var clickHandler = options.clickHandler;
    var mouseOverHandler = options.mouseOverHandler;
    var mouseLeaveHandler = options.mouseLeaveHandler;
    var height;
    var color;

    if (width > AppConstants.SUNBURST_MAX_WIDTH) {
      width = AppConstants.SUNBURST_MAX_WIDTH;
    }
    height = width;

    if (!data) {
      return;
    }

    radius = Math.min(width, height) / 2.2;
    color = d3.scale.category20c();

    x = d3.scale.linear().range([0, 2 * Math.PI]);
    y = d3.scale.sqrt().range([0, radius]);
    partition = d3.layout.partition().value(function (d) {
      return d.size;
    });

    arc = d3.svg.arc()
        .startAngle(function (d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
        .endAngle(function (d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
        .innerRadius(function (d) { return Math.max(0, y(d.y)); })
        .outerRadius(function (d) { return Math.max(0, y(d.y + d.dy)); });

    d3.select('#sunburst-chart svg').remove();
    svg = d3.select('#sunburst-chart')
        .append('svg')
          .attr('width', width)
          .attr('height', height)
        .append('g')
          .attr('id', 'sunburstd3-chart-container')
          .attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')');

    // fill data into svg
    svg.selectAll('path')
        .data(partition.nodes(data))
        .enter().append('path')
        .attr('d', arc)
        .style('fill', function (d) {
          if (!AppConstants.SUNBURST_ARC_COLORS[d.name]) {
            AppConstants.SUNBURST_ARC_COLORS[d.name] = color(sum(d));
          }
          return AppConstants.SUNBURST_ARC_COLORS[d.name];
        })
        .on('click', clickHandler)
        .on('touchstart', clickHandler)
        .on('mouseover', mouseOverHandler);

    d3.select('#sunburstd3-chart-container')
        .on('mouseleave', mouseLeaveHandler);
  },
Beispiel #2
0
/**
 * Represents CPU flame chart.
 * @constructor
 * @param {Object} parent - Parent element for flame chart.
 * @param {Object} data - Data for flame chart rendering.
 */
function FlameChart(parent, data) {
  this.PAD_SIZE = 10;
  this.HEIGHT = parent.node().scrollHeight - this.PAD_SIZE;
  this.WIDTH = parent.node().scrollWidth - this.PAD_SIZE;
  this.TEXT_OFFSET_X = 5;
  this.TEXT_OFFSET_Y= 14;
  this.TEXT_CUTOFF = 0.075 * this.WIDTH;
  this.LEGEND_X = this.WIDTH - 400;
  this.LEGEND_Y = 100;

  this.data_ = data;
  this.parent_ = parent;
  this.xScale_ = d3.scale.linear().domain([0, 1]).range([0, this.WIDTH]);
  this.yScale_ = d3.scale.linear().range([0, this.HEIGHT]);
  this.color_ = d3.scale.category10();
  this.flameChart_ = d3.layout.partition()
    .sort(null)
    .value(function(d) { return d.cumTime; });
}
Beispiel #3
0
// Modified from http://bl.ocks.org/kerryrodden/7090426
function sunburstVis(slice, payload) {
  const container = d3.select(slice.selector);

  // vars with shared scope within this function
  const margin = { top: 10, right: 5, bottom: 10, left: 5 };
  const containerWidth = slice.width();
  const containerHeight = slice.height();
  const breadcrumbHeight = containerHeight * 0.085;
  const visWidth = containerWidth - margin.left - margin.right;
  const visHeight = containerHeight - margin.top - margin.bottom - breadcrumbHeight;
  const radius = Math.min(visWidth, visHeight) / 2;

  let colorByCategory = true; // color by category if primary/secondary metrics match
  let maxBreadcrumbs;
  let breadcrumbDims; // set based on data
  let totalSize; // total size of all segments; set after loading the data.
  let colorScale;
  let breadcrumbs;
  let vis;
  let arcs;
  let gMiddleText; // dom handles

  // Helper + path gen functions
  const partition = d3.layout.partition()
    .size([2 * Math.PI, radius * radius])
    .value(function (d) { return d.m1; });

  const arc = d3.svg.arc()
    .startAngle(d => d.x)
    .endAngle(d => d.x + d.dx)
    .innerRadius(function (d) {
      return Math.sqrt(d.y);
    })
    .outerRadius(function (d) {
      return Math.sqrt(d.y + d.dy);
    });

  const formatNum = d3.format('.3s');
  const formatPerc = d3.format('.3p');

  container.select('svg').remove();

  const svg = container.append('svg:svg')
    .attr('width', containerWidth)
    .attr('height', containerHeight);

  function createBreadcrumbs(rawData) {
    const firstRowData = rawData.data[0];
    // -2 bc row contains 2x metrics, +extra for %label and buffer
    maxBreadcrumbs = (firstRowData.length - 2) + 1;
    breadcrumbDims = {
      width: visWidth / maxBreadcrumbs,
      height: breadcrumbHeight * 0.8, // more margin
      spacing: 3,
      tipTailWidth: 10,
    };

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

    breadcrumbs.append('svg:text')
      .attr('class', 'end-label');
  }

  // Given a node in a partition layout, return an array of all of its ancestor
  // nodes, highest first, but excluding the root.
  function getAncestors(node) {
    const path = [];
    let current = node;
    while (current.parent) {
      path.unshift(current);
      current = current.parent;
    }
    return path;
  }

  // Generate a string that describes the points of a breadcrumb polygon.
  function breadcrumbPoints(d, i) {
    const points = [];
    points.push('0,0');
    points.push(breadcrumbDims.width + ',0');
    points.push(
      breadcrumbDims.width + breadcrumbDims.tipTailWidth + ',' + (breadcrumbDims.height / 2));
    points.push(breadcrumbDims.width + ',' + breadcrumbDims.height);
    points.push('0,' + breadcrumbDims.height);
    if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
      points.push(breadcrumbDims.tipTailWidth + ',' + (breadcrumbDims.height / 2));
    }
    return points.join(' ');
  }

  function updateBreadcrumbs(sequenceArray, percentageString) {
    const g = breadcrumbs.selectAll('g')
      .data(sequenceArray, function (d) {
        return d.name + d.depth;
      });

    // Add breadcrumb and label for entering nodes.
    const entering = g.enter().append('svg:g');

    entering.append('svg:polygon')
        .attr('points', breadcrumbPoints)
        .style('fill', function (d) {
          return colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1);
        });

    entering.append('svg:text')
        .attr('x', (breadcrumbDims.width + breadcrumbDims.tipTailWidth) / 2)
        .attr('y', breadcrumbDims.height / 4)
        .attr('dy', '0.35em')
        .style('fill', function (d) {
          // Make text white or black based on the lightness of the background
          const col = d3.hsl(colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1));
          return col.l < 0.5 ? 'white' : 'black';
        })
        .attr('class', 'step-label')
        .text(function (d) { return d.name.replace(/_/g, ' '); })
        .call(wrapSvgText, breadcrumbDims.width, breadcrumbDims.height / 2);

    // Set position for entering and updating nodes.
    g.attr('transform', function (d, i) {
      return 'translate(' + i * (breadcrumbDims.width + breadcrumbDims.spacing) + ', 0)';
    });

    // Remove exiting nodes.
    g.exit().remove();

    // Now move and update the percentage at the end.
    breadcrumbs.select('.end-label')
        .attr('x', (sequenceArray.length + 0.5) * (breadcrumbDims.width + breadcrumbDims.spacing))
        .attr('y', breadcrumbDims.height / 2)
        .attr('dy', '0.35em')
        .text(percentageString);

    // Make the breadcrumb trail visible, if it's hidden.
    breadcrumbs.style('visibility', null);
  }

  // Fade all but the current sequence, and show it in the breadcrumb trail.
  function mouseenter(d) {
    const sequenceArray = getAncestors(d);
    const parentOfD = sequenceArray[sequenceArray.length - 2] || null;

    const absolutePercentage = (d.m1 / totalSize).toPrecision(3);
    const conditionalPercentage = parentOfD ? (d.m1 / parentOfD.m1).toPrecision(3) : null;

    const absolutePercString = formatPerc(absolutePercentage);
    const conditionalPercString = parentOfD ? formatPerc(conditionalPercentage) : '';

    // 3 levels of text if inner-most level, 4 otherwise
    const yOffsets = ['-25', '7', '35', '60'];
    let offsetIndex = 0;

    // If metrics match, assume we are coloring by category
    const metricsMatch = Math.abs(d.m1 - d.m2) < 0.00001;

    gMiddleText.selectAll('*').remove();

    gMiddleText.append('text')
      .attr('class', 'path-abs-percent')
      .attr('y', yOffsets[offsetIndex++])
      .text(absolutePercString + ' of total');

    if (conditionalPercString) {
      gMiddleText.append('text')
        .attr('class', 'path-cond-percent')
        .attr('y', yOffsets[offsetIndex++])
        .text(conditionalPercString + ' of parent');
    }

    gMiddleText.append('text')
      .attr('class', 'path-metrics')
      .attr('y', yOffsets[offsetIndex++])
      .text('m1: ' + formatNum(d.m1) + (metricsMatch ? '' : ', m2: ' + formatNum(d.m2)));

    gMiddleText.append('text')
      .attr('class', 'path-ratio')
      .attr('y', yOffsets[offsetIndex++])
      .text((metricsMatch ? '' : ('m2/m1: ' + formatPerc(d.m2 / d.m1))));

    // Reset and fade all the segments.
    arcs.selectAll('path')
      .style('stroke-width', null)
      .style('stroke', null)
      .style('opacity', 0.7);

    // Then highlight only those that are an ancestor of the current segment.
    arcs.selectAll('path')
      .filter(function (node) {
        return (sequenceArray.indexOf(node) >= 0);
      })
      .style('opacity', 1)
      .style('stroke-width', '2px')
      .style('stroke', '#000');

    updateBreadcrumbs(sequenceArray, absolutePercString);
  }

  // Restore everything to full opacity when moving off the visualization.
  function mouseleave() {
    // Hide the breadcrumb trail
    breadcrumbs.style('visibility', 'hidden');

    gMiddleText.selectAll('*').remove();

    // Deactivate all segments during transition.
    arcs.selectAll('path').on('mouseenter', null);

    // Transition each segment to full opacity and then reactivate it.
    arcs.selectAll('path')
      .transition()
      .duration(200)
      .style('opacity', 1)
      .style('stroke', null)
      .style('stroke-width', null)
      .each('end', function () {
        d3.select(this).on('mouseenter', mouseenter);
      });
  }


  function buildHierarchy(rows) {
    const root = {
      name: 'root',
      children: [],
    };

    // each record [groupby1val, groupby2val, (<string> or 0)n, m1, m2]
    for (let i = 0; i < rows.length; i++) {
      const row = rows[i];
      const m1 = Number(row[row.length - 2]);
      const m2 = Number(row[row.length - 1]);
      const levels = row.slice(0, row.length - 2);
      if (isNaN(m1)) { // e.g. if this is a header row
        continue;
      }
      let currentNode = root;
      for (let level = 0; level < levels.length; level++) {
        const children = currentNode.children || [];
        const nodeName = levels[level];
        // If the next node has the name '0', it will
        const isLeafNode = (level >= levels.length - 1) || levels[level + 1] === 0;
        let childNode;
        let currChild;

        if (!isLeafNode) {
          // Not yet at the end of the sequence; move down the tree.
          let foundChild = false;
          for (let k = 0; k < children.length; k++) {
            currChild = children[k];
            if (currChild.name === nodeName &&
                currChild.level === level) {
// must match name AND level

              childNode = currChild;
              foundChild = true;
              break;
            }
          }
          // If we don't already have a child node for this branch, create it.
          if (!foundChild) {
            childNode = {
              name: nodeName,
              children: [],
              level,
            };
            children.push(childNode);
          }
          currentNode = childNode;
        } else if (nodeName !== 0) {
          // Reached the end of the sequence; create a leaf node.
          childNode = {
            name: nodeName,
            m1,
            m2,
          };
          children.push(childNode);
        }
      }
    }

    function recurse(node) {
      if (node.children) {
        let sums;
        let m1 = 0;
        let m2 = 0;
        for (let i = 0; i < node.children.length; i++) {
          sums = recurse(node.children[i]);
          m1 += sums[0];
          m2 += sums[1];
        }
        node.m1 = m1;
        node.m2 = m2;
      }
      return [node.m1, node.m2];
    }

    recurse(root);
    return root;
  }

  // Main function to draw and set up the visualization, once we have the data.
  function createVisualization(rawData) {
    const tree = buildHierarchy(rawData.data);

    vis = svg.append('svg:g')
      .attr('class', 'sunburst-vis')
      .attr('transform', (
        'translate(' +
          `${(margin.left + (visWidth / 2))},` +
          `${(margin.top + breadcrumbHeight + (visHeight / 2))}` +
        ')'
      ))
      .on('mouseleave', mouseleave);

    arcs = vis.append('svg:g')
      .attr('id', 'arcs');

    gMiddleText = vis.append('svg:g')
      .attr('class', 'center-label');

    // Bounding circle underneath the sunburst, to make it easier to detect
    // when the mouse leaves the parent g.
    arcs.append('svg:circle')
      .attr('r', radius)
      .style('opacity', 0);

    // For efficiency, filter nodes to keep only those large enough to see.
    const nodes = partition.nodes(tree)
      .filter(function (d) {
        return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
      });

    let ext;
    const fd = slice.formData;

    if (fd.metric !== fd.secondary_metric) {
      colorByCategory = false;
      ext = d3.extent(nodes, d => d.m2 / d.m1);
      colorScale = d3.scale.linear()
        .domain([ext[0], ext[0] + ((ext[1] - ext[0]) / 2), ext[1]])
        .range(['#00D1C1', 'white', '#FFB400']);
    }

    const path = arcs.data([tree]).selectAll('path')
      .data(nodes)
      .enter()
      .append('svg:path')
      .attr('display', function (d) {
        return d.depth ? null : 'none';
      })
      .attr('d', arc)
      .attr('fill-rule', 'evenodd')
      .style('fill', d => colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1))
      .style('opacity', 1)
      .on('mouseenter', mouseenter);

    // Get total size of the tree = value of root node from partition.
    totalSize = path.node().__data__.value;
  }
  createBreadcrumbs(payload);
  createVisualization(payload);
}
Beispiel #4
0
  PieChart.prototype.addPath = function (width, height, svg, slices) {
    var self = this;
    var marginFactor = 0.95;
    var isDonut = self._attr.isDonut;
    var radius = (Math.min(width, height) / 2) * marginFactor;
    var color = self.handler.data.getPieColorFunc();
    var tooltip = self.tooltip;
    var isTooltip = self._attr.addTooltip;

    var partition = d3.layout.partition()
    .sort(null)
    .value(function (d) {
      return d.percentOfParent * 100;
    });
    var x = d3.scale.linear()
    .range([0, 2 * Math.PI]);
    var y = d3.scale.sqrt()
    .range([0, radius]);
    var arc = d3.svg.arc()
    .startAngle(function (d) {
      return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
    })
    .endAngle(function (d) {
      return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
    })
    .innerRadius(function (d) {
      // option for a single layer, i.e pie chart
      if (d.depth === 1 && !isDonut) {
        // return no inner radius
        return 0;
      }

      return Math.max(0, y(d.y));
    })
    .outerRadius(function (d) {
      return Math.max(0, y(d.y + d.dy));
    });

    var path = svg
    .datum(slices)
    .selectAll('path')
    .data(partition.nodes)
    .enter()
      .append('path')
      .attr('d', arc)
      .attr('class', function (d) {
        if (d.depth === 0) { return; }
        return 'slice';
      })
      .call(self._addIdentifier, 'name')
      .style('stroke', '#fff')
      .style('fill', function (d) {
        if (d.depth === 0) { return 'none'; }
        return color(d.name);
      });

    if (isTooltip) {
      path.call(tooltip.render());
    }

    return path;
  };
Beispiel #5
0
domready(function() {
  var root = window.disc
    , width = window.innerWidth
    , height = Math.max(window.innerHeight - 100, 100)
    , radius = Math.min(width, height) * 0.45
    , deg = 120

  var svg = d3.select('.chart').append('svg')
      .attr('width', width)
      .attr('height', height)
    .append('g')
      .attr('transform', 'translate(' + width / 2 + ',' + height * .52 + ')')

  var paletteDiv = d3.select('.palette-wrap')
    .style('top', String(window.innerHeight - (schemes.length - 1) * 56 - 16) + 'px')
    .selectAll('.palette')
    .data(schemes)
    .enter()
    .append('div')
    .classed('scheme-icon', true)

  paletteDiv.append('span')
    .classed('scheme-text', true)
    .text(function(d) { return d.name })

  var palettes = paletteDiv
    .append('svg')
    .style('display', 'inline-block')
    .classed('palette', true)
    .on('click', function(d, i) {
      useScheme(i, path.transition()
        .duration(600)
        .ease(bounce_high, 1000)
        .delay(function(d, i) {
          return d.x * 100 + d.y / maxdepth * 0.06125
        })
      )
    })

  palettes.append('rect')
    .attr('width', 23)
    .attr('height', 48)
    .style('fill', function(d) {
      return d.background
    })

  palettes.selectAll('.color')
    .data(function(d) { return d.all })
    .enter()
    .append('rect')
    .style('fill', function(d) { return d })
    .attr('x', 25)
    .attr('y', function(d, i, j) {
      return 48 * i / schemes[j].all.length - 1
    })
    .attr('width', 22)
    .attr('height', function(d, i, j) {
      return 48 / schemes[j].all.length - 1
    })

  var partition = d3.layout.partition()
      .sort(null)
      .size([2 * Math.PI, radius * radius])
      .value(modeFns[modeInitial])

  //
  // Creates the title text in
  // the center of the rings.
  //
  var title = svg.append('text')
    .text(root.name)
    .attr('x', 0)
    .attr('y', -5)
    .style('font-size', '12px')
    .style('fill', 'white')
    .style('font-weight', 500)
    .style('alignment-baseline', 'middle')
    .style('text-anchor', 'middle')

  //
  // Likewise, this is the file
  // size stat below the title
  //
  var size = svg.append('text')
    .text(pretty(root.size))
    .attr('x', 0)
    .attr('y', 15)
    .style('fill', 'white')
    .style('font-size', '10px')
    .style('alignment-baseline', 'middle')
    .style('text-anchor', 'middle')

  //
  // Each arc is wrapped in a group element,
  // to apply rotation transforms while
  // changing size and shape.
  //
  var groups = svg.datum(root).selectAll('g')
      .data(partition.nodes)
    .enter()
      .append('g')
      .attr('transform', 'rotate(' + deg + ')')

  var maxdepth = groups[0].reduce(function(max, el) {
    return Math.max(max, el.__data__.depth)
  }, 0)

  //
  // Actually create the arcs for each
  // file.
  //
  var path = groups.append('path')
    .attr('d', initArc)
    .attr('display', function(d) {
      return d.depth ? null : 'none'
    })
    .style('stroke', '#2B2B2B')
    .style('stroke-width', '0')
    .style('fill-rule', 'evenodd')
    .each(function(d) {
      d.x0 = d.x
      d.dx0 = d.dx
      d.el = this
    })

  //
  // Colour scheme functionality.
  //
  // Triggered immediately with the default
  // scheme, must be passed a d3 selection.
  //
  var background
    , scheme = 0
    , specials
    , color

  useScheme(scheme, path)
  function useScheme(n, path) {
    background = schemes[n].background
    specials = schemes[n].specials

    palettes.each(function(d, i) {
      d3.select(this.parentNode)
        .classed('selected', function() {
          return i === n
        })
    })

    palettes
      .transition()
      .ease('bounce')
      .duration(500)
      .attr('height', function(d, i) {
        return i === n ? 0 : 48
      })

    ;[d3.select('body')
    , d3.select('html')].forEach(function(el) {
      el.transition()
        .ease('sin-in-out')
        .duration(600)
        .style('background', background)
    })

    var colors = schemes[n].main
    Object.keys(specials).forEach(function(key) {
      var idx = colors.indexOf(specials[key].toLowerCase())
      if (idx === -1) return
      colors.splice(idx, 1)
    })

    color = d3.scale
      .ordinal()
      .range(colors)

    path.style('fill', function(d) {
      var name = d.children ? d.name : d.parent.name
      d.c = schemes[n].modifier.call(d
        , specials[name] || color(name)
        , root
      )
      return d.c
    })
  }

  path.transition()
    .duration(1000)
    .ease('elastic', 2, 1)
    .delay(function(d, i) {
      return d.x * 100 + (i % 4) * 250 + d.y / maxdepth * 0.25
    })
    .attr('d', arc)

  //
  // Rotates the newly created
  // arcs back towards their original
  // position.
  //
  groups.transition()
    .duration(3250)
    .delay(function(d, i) {
      return d.x * 100 + (i % 4) * 250 + d.y / maxdepth * 0.25 + 250
    })
    .attrTween('transform', rotateTween(deg))

  groups.on('mouseover', function(d) {
    highlight(d)
    title.text(d.name)
    size.text(pretty(d.size))
  }).on('mouseout', function(d) {
    unhighlight(d)
    title.text(root.name)
    size.text(pretty(root.size))
  })

  highlight.tween = hoverTween(1)
  function highlight(d) {
    if (d.el) d3.select(d.el)
      .transition()
      .delay(function(d) { return (d.depth - 1) * 300 / maxdepth })
      .ease('back-out', 10)
      .duration(500)
      .attrTween('d', highlight.tween)
      .style('fill', function(d) { return d.c })

    if (d.children) {
      var i = d.children.length
      while (i--) highlight(d.children[i])
    }
  }

  unhighlight.tween = hoverTween(0)
  function unhighlight(d) {
    if (d.el) d3.select(d.el)
      .transition()
      .delay(function(d) { return (d.depth - 1) * 300 / maxdepth })
      .ease('back-out', 4)
      .duration(500)
      .attrTween('d', unhighlight.tween)
      .style('fill', function(d) { return d.c })

    if (d.children) {
      var i = d.children.length
      while (i--) unhighlight(d.children[i])
    }
  }

  var modes = d3.selectAll('[data-mode]')

  updateMode(modeInitial)
  modes.on('click', function() {
    updateMode(
      this.getAttribute('data-mode')
    , true)
  })

  function updateMode(mode, update) {
    value = modeFns[mode] || value

    modes.style('opacity', function(d) {
      return mode === (
        this.mode = this.mode || this.getAttribute('data-mode')
      ) ? 1 : null
    })

    if (!update) return

    groups
        .data(partition.value(value).nodes)
        .select('path')
      .transition()
        .duration(1500)
        .attrTween('d', arcTween)
  }
})
Beispiel #6
0
  "account": "#6ab975",
  "other": "#a173d1",
  "end": "#bbbbbb"
};

// Total size of all segments; we set this later, after loading the data.
var totalSize = 0;

var vis = d3.select("#chart").append("svg:svg")
    .attr("width", width)
    .attr("height", height)
    .append("svg:g")
    .attr("id", "container")
    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

var partition = d3.layout.partition()
    .size([2 * Math.PI, radius * radius])
    .value(function(d) { return d.size; });

var arc = d3.svg.arc()
    .startAngle(function(d) { return d.x; })
    .endAngle(function(d) { return d.x + d.dx; })
    .innerRadius(function(d) { return Math.sqrt(d.y); })
    .outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });

// Use d3.text and d3.csv.parseRows so that we do not need to have a header
// row, and can receive the csv as an array of arrays.
d3.text("visit-sequences.csv", function(text) {
  var csv = d3.csv.parseRows(text);
  var json = buildHierarchy(csv);
  createVisualization(json);
Beispiel #7
0
	constructor(selection) {
    super(selection);
    var _Chart = this;

    // load configs
    configs.forEach(function (item) {
      _Chart.configs.set(item.name, item);
    });

    // accessors
    this.accessor('name', function (d) { return d[d.length-2]; });

    // formatters
    var percent = d3.format('%');
    var overUnder = d3.format('+%');

    // scales
    this.x = d3.scale.linear();
    this.y = d3.scale.linear();
    this.color = d3.scale.ordinal();

    // layouts
    this.partition = d3.layout.partition()
      .value(function(d) { return d[d.length - 1]; });

    // setup main group
    this._group = this.base.append('g');
    
    // Set up tool tip;
    this._tooltip = new KotoTooltip(this.base.append('g'));

    this._tooltip.config({
      opacity: 1,
      format: 'textRectText'
    });

    this._tooltipValue = this._tooltip._appends.append('text')
      .style('font-family', 'Open Sans');
    this._tooltipRect = this._tooltip._appends.append('rect');
    this._tooltipLabel = this._tooltip._appends.append('text')
      .style('font-family', 'Open Sans');
    
    this._tooltip.trigger('draw');
    this._tooltip.trigger('remove');

    this.on('external:click', function (d) {
      click(d);
    });
    
    // Setup Layers
    var icicle = this.layer('icicle', this._group, {
      dataBind: function (data) {
        var root = _Chart.partition(data[0]);
        if (_Chart._targetData) {
          var target = _Chart.partition(_Chart._targetData[0]);
          for (var i=0; i < root.length; i++) {
            root[i].target = target[i].value;
          }
        }
        return this.selectAll('g').data(root);
      },
      insert: function () {
        return this.append('g')
          .on('click', click)
          .on('mouseover', function (d) {
            _Chart._group.selectAll('rect')
              .filter(function (datum) {
                var thingName = d.name ? d.name : d[d.length - 2];
                return !isRelated(d, datum);
              })
              .style('fill', '#e3e3e3');

              _Chart._tooltipValue
                .text(d.name ? d.name : _Chart.accessor('name')(d));

              _Chart._tooltipRect.style('fill', '#000');

              _Chart._tooltipLabel
                .text(percent(d.value));

              if (_Chart._targetData) {
                _Chart._tooltipRect2.style('fill', '#000');
                _Chart._tooltipLabel2
                  .text(overUnder(d.value - d.target));
              }

              _Chart._tooltip.trigger('draw');
            })
            .on('mousemove', function () {
              var coordinates = d3.mouse(_Chart.base.node());
              _Chart._tooltip.trigger('moveTo', {x: coordinates[0], y: coordinates[1]}, null, _Chart.config('width'), _Chart.config('height'));
            })
            .on('mouseout', function (d) {
              _Chart._group.selectAll('rect')
                .style('fill', function(d) { return _Chart.color(d.base); });

              _Chart._tooltip.trigger('remove');
            });
      }
    });
  
    // layer life-cycle events
    icicle.on('enter', function () {
      this.append('rect')
        .style('stroke', '#fff');
      
      if (_Chart._targetData) {
        this.append('path');
      }

      this.append('text')
        .style('pointer-events', 'none');

      return this;
    })
    .on('merge', function () {
      // boxes
      this.select('rect')
        .attr('x', function(d) { return _Chart.x(d.x); })
        .attr('y', function(d) { return _Chart.y(d.y); })
        .attr('width', function(d) { return _Chart.x(d.dx); })
        .attr('height', 0)
        .style('fill', function(d) { d.base = getParent(d, _Chart.rootName); return _Chart.color(d.base); });

      // over-under indicator
      if (_Chart._targetData) {
        this.select('path')
          .style('fill', '#555')
          .style('opacity', 0)
          .attr({
            d: function (d) {
              var symbol = d3.svg.symbol();
              var gen = d.value > d.target ? symbol.type('triangle-up') : d.value < d.target ? symbol.type('triangle-down') : symbol.type('triangle-down');
              return gen();
            },
            transform: function (d) {
              var left = _Chart.x(d.x) + 15;
              var top = _Chart.y(d.y) + 15;
              
              return `translate(${left},${top})`;
            }
          });
      }

      // labels
      this.select('text')
        .text(function (d) { return d.name ? d.name : d[d.length - 2]; })
        .attr('x', function(d) { return _Chart.x(d.x); })
        .attr('dx', _Chart._targetData ? 25 : 5)
        .attr('y', function(d) { return _Chart.y(d.y); })
        .attr('dy', 16)
        .style('alignment-baseline', 'middle')
        .style('fill', '#555')
        .style('font-family', 'Open Sans')
        .style('opacity', 0);
    })
    .on('merge:transition', function () {
      var totalDuration = _Chart.config('introDuration');
      var duration = totalDuration/(_Chart.max-1);
      
      // rects
      this
        .duration(duration)
        .ease('linear')
        .delay(function (d) { return d.depth * duration; })
        .select('rect')
        .attr('height', function(d) { return _Chart.y(d.dy); });
      
      // paths
      if (_Chart._targetData) {
        this
          .delay(totalDuration)
          .selectAll('path')
          .style('opacity', function (d) {
            var shouldShow = d.value > d.target || d.value < d.target;
            var canFit = _Chart.x(d.dx) > 25;
            return shouldShow && canFit ? 1 : 0;
          });
      }

      // linear
      this
        .duration(duration)
        .ease('linear')
        .delay(function (d) { return d.depth * duration; })
        .select('text')
        .style('opacity', function (d) {
          d.textLength = this.getComputedTextLength();
          return (d.textLength + 30) < _Chart.x(d.dx) ? 1 : 0;
        });
      return this;
    });

    // click event handler
    function click(d) {
      _Chart.x.domain([d.x, d.x + d.dx]);
      _Chart.y.domain([d.y, 1]).range([d.y ? 20 : 0, _Chart.config('height')]);

      // transition Rects
      _Chart._group.selectAll('rect')
        .transition()
        .duration(_Chart.config('transitionDuration'))
        .attr('x', function(d) { return _Chart.x(d.x); })
        .attr('y', function(d) { return _Chart.y(d.y); })
        .attr('width', function(d) { return _Chart.x(d.x + d.dx) - _Chart.x(d.x); })
        .attr('height', function(d) { return _Chart.y(d.y + d.dy) - _Chart.y(d.y); });

      // transition paths
      if (_Chart._targetData) {
        _Chart._group.selectAll('path')
          .transition()
          .duration(_Chart.config('transitionDuration'))
          .attr('transform', function (d) {
            var left = _Chart.x(d.x) + 15;
            var top = _Chart.y(d.y) + 15;
            return `translate(${left},${top})`;
          });
      }

      // transition text
      _Chart._group.selectAll('text')
        .transition()
        .duration(_Chart.config('transitionDuration'))
        .attr('x', function(d) { return _Chart.x(d.x); })
        .attr('y', function(d) { return _Chart.y(d.y); })
        .style('opacity', function (d) {
          return (d.textLength + 12) < (_Chart.x(d.x + d.dx) - _Chart.x(d.x)) ? 1 : 0;
        });
    }

    // check to see if nodes are related
    function isRelated(thing, relative) {
      return (isChild(thing, relative) || isParent(thing, relative));
    }

    // check to see if node is child
    function isChild(thing, relative) {
      if (thing === relative) {
        return true;
      }
      if (!thing.parent) {
        return false;
      }
      return isChild(thing.parent, relative);
    }

    // check to see if node is parent
    function isParent (thing, relative) {
      var i;
      var temp;
      if (thing === relative) {
        return true;
      }
      if (!thing.children) {
        return false;
      }
      for (i=0; i < thing.children.length; i++) {
        if (isParent(thing.children[i], relative)) {
          return true;
        }
      }
      return false;
    }

    // get parents name - for color
    function getParent (d, rootName) {
      if (!d.parent) {
        return d.name;
      }
      if (d.parent.name === rootName) {
        return d.name;
      }
      return getParent(d.parent, rootName);
    }
  }
Beispiel #8
0
        , render: function () {
            document.querySelector('#viz-style').href = '/who.css'
            document.querySelector('main').innerHTML = ''
            var
            width = 300,
                height = 300,
                radius = 50 * Math.max(width, height) / 100,
                x = d3.scale.linear().range([0, 2 * Math.PI]),
                y = d3.scale.pow().exponent(1.3).domain([0, 1]).range([0, radius]),
                padding = 5,
                duration = 1500;

            var color = d3.scale.ordinal().domain([0, 15000000]).range(["#64BCBB", "#49A14C", "#10716F", "#A3D3D2", "#49A14C", "#72AE69", "#B5D0AB", "#D8E4D1", "#67576A"]);

            var div = d3.select("#vis");

            //format currency
            var format = d3.format(",f");

            var format1 = d3.format("%");

            var content = d3.select("#col2");

            //var content1 = d3.select("#annotation");
            div.select("img").remove();

            var svg = d3.select("body").append("svg").attr("width", width + padding * 2).attr("height", height + padding * 2).append("g").attr("transform", "translate(" + [radius + padding, radius + padding] + ")");

            //div.append("p")
            // .attr("id", "intro")
            //.text("Click to zoom!");
            var partition = d3.layout.partition().value(function (d) {
                return d.amount;
            });

            var arc = d3.svg.arc().startAngle(function (d) {
                return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
            })

            .endAngle(function (d) {
                return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
            })

            .innerRadius(function (d) {
                return Math.max(0, y(d.y));
            })

            .outerRadius(function (d) {
                return Math.max(0, y(d.y + d.dy));
            });

            d3.json("who.json", function (error, root) {
                var path = svg.selectAll("path").data(partition.nodes(root)).enter().append("path").attr("id", function (d, i) {
                    return "path-" + i;
                })

                .attr("d", arc).attr("fill-rule", "evenodd").style("fill", function (d) {
                    return color((d.children ? d : d.parent).name);
                })

                .on("click", click).on("mouseover", mouseover).on("mouseout", mouseout);

                //add text
                var text = svg.selectAll("text").data(partition.nodes(root));

                var textEnter = text.enter().append("text")
                //initial opacity
                //hides all those but the inner ring
                .style("fill-opacity", function (d) {
                    //if the depth is 1, innermost, then it's seen
                    if (d.depth === 1) {
                        return 1;
                    }
                    //else the depth is not one, then it's hidden
                    else {
                        return 0;
                    }
                })
                //color fill
                //#000000 is black
                .style("fill", "#000000").attr("text-anchor", function (d) {
                    return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
                })

                .attr("dy", ".2em")
                //checks for multiline names
                .attr("transform", function (d) {
                    var multiline = (d.name || "").split(" ").length > 1.5,
                        angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
                        rotate = angle + (multiline ? -.5 : 0);

                    return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
                })

                .on("click", click)
                //added mouseover and mouseout for the text as well.
                .on("mouseover", mouseover).on("mouseout", mouseout);

                //1st row of text
                textEnter.append("tspan").attr("x", 0).text(function (d) {
                    return d.depth ? d.name.split(" ")[0] : "";
                });

                //2nd row of text
                textEnter.append("tspan").attr("x", 0).attr("dy", ".9em").text(function (d) {
                    return d.depth ? d.name.split(" ")[1] || "" : "";
                });

                //3rd row
                textEnter.append("tspan").attr("x", 0).attr("dy", ".9em").text(function (d) {
                    return d.depth ? d.name.split(" ")[2] || "" : "";
                });

                //fourth row (if necessary)
                textEnter.append("tspan").attr("x", 0).attr("dy", ".9em").text(function (d) {
                    return d.depth ? d.name.split(" ")[3] || "" : "";
                });

                //click function


                function click(d) {
                    path.transition()
                    //duration is predefined above at 1500 (1.75 seconds)
                    .duration(duration).attrTween("d", arcTween(d));

                    // Somewhat of a hack as it relies on arcTween updating the scales.
                    text.style("visibility", function (e) {

                        return isParentOf(d, e) ? null : d3.select(this).style("visibility");
                    })

                    .transition().duration(duration).attrTween("text-anchor", function (d) {
                        return function ()

                        {
                            return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
                        };
                    })

                    .attrTween("transform", function (d) {
                        var multiline = (d.name || "").split(" ").length > 1.5;

                        return function () {
                            var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
                                rotate = angle + (multiline ? -.5 : 0);
                            return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
                        };
                    })


                    .each("end", function (e) {
                        d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden");
                    });

                }

                //mouseover function which will send the values to the legend


                function mouseover(d) {
                    content.append("p").attr("id", "current").text(d.name) // + " - 2013 amounts: " + d.amount + " - Which was a " + d.percentChange13 + "% change of the previous year.")
                    content.append("p")
                    //.attr("id", "name")
                    .text("Money exchanged: $" + format(d.amount))

                }

                //mouseout function which removes the values and replaces them with a blank space


                function mouseout(d) {

                    content.html(' ');

                }

            });

            d3.selectAll("input").on("change", function change() {
                var value = this.value === "show" ? 1 : 0;

                d3.selectAll("text").style("fill-opacity", function (d) {
                    if (value === 1) {
                        return 1;
                    }
                    else {
                        //if the depth is 1, innermost, then it's seen
                        if (d.depth === 1) {
                            return 1;
                        }
                        //else the depth is not one, then it's hidden
                        else {
                            return 0;
                        }
                    }
                });
            });

            function isParentOf(p, c) {
                if (p === c) return true;
                if (p.children) {
                    return p.children.some(function (d) {
                        return isParentOf(d, c);
                    });
                }
                return false;
            }

            // Interpolate the scales!


            function arcTween(d) {
                var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
                    yd = d3.interpolate(y.domain(), [d.y, 1]),
                    yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
                return function (d, i) {
                    return i ?
                    function (t) {
                        return arc(d);
                    } : function (t) {
                        x.domain(xd(t));
                        y.domain(yd(t)).range(yr(t));
                        return arc(d);
                    };
                };
            }

            function maxY(d) {
                return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
            }

            // http://www.w3.org/WAI/ER/WD-AERT/#color-contrast


            function brightness(rgb) {
                return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
            }

        }
Beispiel #9
0
    function link (scope, element, attrs) {
      var width,
        height,
        radius,
        color_scale,
        $breadcrumbs,
        svg,
        arc,
        partition,
        textContainer

      scope.max_depth = 0
      width = attrs.width ? parseInt(attrs.width, 10) : 2000
      height = attrs.height ? parseInt(attrs.height, 10) : width + 50   /* breadcrumbs */
      radius = Math.min(width, height) / 2
      color_scale = d3.scale.category10()

      scope.breadcrumbsNodes = []
      $breadcrumbs = $compile('<breadcrumbs-d3 nodes="breadcrumbsNodes" max-depth="maxDepth"></breadcrumbs-d3>')(scope)
      element.append($breadcrumbs)
      textContainer = d3
        .select(element[0])
        .append('div')
        .attr('style', 'position:absolute;top:50%;margin-top:-15px;height:50%;left:0;right:0;  ')
        .append('div')
        .attr('class', 'text-value')
        .style('font-size', '30px')
        .style('text-align', 'center')

      // took responsive svg trick from http://demosthenes.info/blog/744/Make-SVG-Responsive
      svg = d3.select(element[0])
        .append('div')
        .attr('style', 'position: relative;width: 90%;padding-bottom: 100%;margin-left:5%; vertical-align: middle;overflow: hidden;')
        .append('svg')
        .attr('style', 'display: block;position: absolute; top: 0;left: 0;')
        .attr('width', '100%')
        .attr('height', '100%')
        .attr('preserveAspectRatio', 'xMinYMin meet')
        .attr('viewBox', '0 0 2000 2000')
        .append('g')
        .attr('transform', 'translate(' + width / 2 + ',' + height * 0.52 + ')')

      arc = d3.svg.arc()
        .startAngle((d) => d.x)
        .endAngle((d) => d.x + d.dx)
        .innerRadius((d) => Math.sqrt(d.y))
        .outerRadius((d) => Math.sqrt(d.y + d.dy))

      scope.$watch('selected', function (newVal) {
        if (newVal && newVal.id) {
          highlight(newVal.id)
        }
      })

      partition = d3.layout.partition()
        .sort(null)
        .size([2 * Math.PI, radius * radius])
        .value((d) => scope.log ? Math.log(d.size) : d.size)

      scope.$watch(() => scope.chartData, function (newVal) {
        var g, chart_data
        if (!scope.chartData || !scope.chartData.children || !scope.chartData.children.length) {
          svg
            .selectAll('path')
            .remove()
          scope.breadcrumbsNodes = []
          return
        }
        // update breadcrumbs and stuff
        highlight()

        // d3 add a lot of data to nodes. It triggers watch without rest
        chart_data = clone(scope.chartData)

        g = svg
          .datum(chart_data)
          .selectAll('path')
          .data(partition.nodes)

        g
          .enter()
          .append('path')
          .each(function (d) { this._current = { x: 0, dx: 0, y: 0, dy: 0 } }) // store the initial angles

        var existing_and_current = g
          .attr('display', (d) => d.depth ? null : 'none') // hide inner ring
          .attr('d', arc)
          .style('stroke', '#fff')
          .style('stroke-width', width / 200)
          .style('fill', color)
          .style('fill-rule', 'evenodd')
          .on('mouseover', mouseover)
          .on('click', click)

        // avoid locking the loop for animation
        setTimeout(function () {
          g
            .transition()
            .duration(750)
            .attrTween('d', arcTween)
            .each('end', function (node) {
              this._current = {
                x: node.x,
                dx: node.dx,
                y: node.y,
                dy: node.dy
              }
            })
        }, 0)

        existing_and_current
          .each(function (d) {
            if (d.depth > scope.max_depth) {
              scope.max_depth = d.depth
            }
          })
          .filter((d, i) => !!scope.click && !d.virtual)
          .style('cursor', 'pointer')

        g.exit().remove()
      }, true)

      function color (d) {
        if (d.color) {
          return d.color
        }
        return color_scale((d.children ? d : d.parent).name)
      }

      function arcTween (node) {
        if (!this._current) {
          this._current = { x: node.x, dx: 0, y: node.y, dy: 0 }
        }
        var i = d3.interpolate(this._current, node)
        return function (t) {
          return arc(i(t))
        }
      }
      function mouseover (d) {
        highlight(d.id)
        if (scope.over) {
          scope.over.apply(null, [{ d: d }])
        }
      }

      function click (d) {
        if (scope.click) {
          scope.click.apply(null, [{ d: d }])
        }
      }

      function highlight (id) {
        const sequenceArray = getAncestors(id)

        if (sequenceArray && sequenceArray.length) {
          textContainer.text(sequenceArray[sequenceArray.length - 1].textSize)
        }
        updateBreadcrumbs(sequenceArray)

        // Fade all the segments.
        svg.selectAll('path')
          .style('opacity', 0.6)

        // Then highlight only those that are an ancestor of the current segment.
        svg.selectAll('path')
          .filter(function (node) {
            return (sequenceArray.indexOf(node) >= 0)
          })
          .style('opacity', 1)
      }

      // Update the breadcrumb trail to show the current sequence .
      function updateBreadcrumbs (nodeArray) {
        const toBreadCrumbs = map(nodeArray, function (node) {
          return {name: node.name, fill: color(node)}
        })
        $timeout(function () {
          scope.breadcrumbsNodes = toBreadCrumbs
        }, 0)
      }

      function getAncestors (id) {
        var path = []
        svg.selectAll('path')
          .each(function (node) {
            if (node.id === id) {
              var current = node
              while (current.parent) {
                path.unshift(current)
                current = current.parent
              }
            }
          })
        return path
      }
    }
Beispiel #10
0
        update: function (element, valueAccessor, viewModel, bindingContext) {
            var height = bindingContext.height,
                width = $(element).parents('.resizable.container').innerWidth();

            bindingContext.width(width);
            radius = ko.unwrap(bindingContext.radius);

            // To avoid a floating point bug in d3 partition layout,
            // the range of the x scale needs to be rounded to integers.
            // For greater precision, 10 * 360 (degrees) is used.
            // Later, startAngle and endAngle functions will reconvert
            // those values to the expected [0, 2* Math.PI] range.
            x = d3.scale.linear().rangeRound([0, 3600]).clamp(true);
            y = d3.scale.sqrt().range([0, radius]);
            var d2r = d3.scale.linear().domain([0, 3600]).range([0, 2 * Math.PI]);

            var el = d3.select(element)
                    .attr('width', width)
                    .attr('height', height),

                partition = d3.layout.partition()
                    .value(function(d) { return d.size; }),

                arc = d3.svg.arc()
                    .startAngle(function(d) {
                        return Math.max(0, Math.min(2 * Math.PI, d2r(x(d.x))));
                    }).endAngle(function(d) {
                        return Math.max(0, Math.min(2 * Math.PI, d2r(x(d.x + d.dx))));
                    }).innerRadius(function(d) {
                        return Math.max(0, y(d.y - d.dy));
                    }).outerRadius(function(d) {
                        return Math.max(0, y(d.y));
                    });

            // clean up after the old rendering
            el.select('g').remove();

            var container = el.append('g')
                .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');

            // not sure about this pattern, saving references in DOM elements... hm
            element.sunburst = {
                container: container,
                partition: partition,
                arc: arc,
                totalSize: 0,
                bindingContext: bindingContext,
            };

            var val = ko.unwrap(valueAccessor()),
                data = ko.unwrap(val.hierarchy),
                nodes = element.sunburst.partition.nodes(data);

            colorizeHierarchy(data);

            var path = element.sunburst.container.data([data]).selectAll('path')
                .data(nodes).enter().append('g').append('path')
                .attr('d', element.sunburst.arc)
                .attr('fill-rule', 'evenodd')
                .style('fill', function (d) { return d.color; })
                .style('cursor', function (d) {
                    return d.children ? 'pointer' : 'normal';
                })
                .on('mouseover', hoverPath.bind(element.sunburst))
                .on('click', click.bind(element.sunburst));

            addLabels.bind(element.sunburst)(1, 2);

            // Get total size of the tree = value of root node from partition.
            element.sunburst.totalSize = path.node().__data__.value;

            // Highlight greatest leaf.
            var greatestLeaf = element.sunburst.container.select('path').datum();
            while (greatestLeaf.children) {
                greatestLeaf = greatestLeaf.children[0];
            }
            setTimeout(function () {
                hoverPath.bind(element.sunburst)(greatestLeaf);
            }, 50);  // To avoid race condition that breaks zoom.
        }