dlrandy / note-issues

2 stars 0 forks source link

D3 #92

Open dlrandy opened 6 years ago

dlrandy commented 6 years ago

https://bost.ocks.org/mike/selection/

http://bl.ocks.org/dbuezas/9306799

<!DOCTYPE html>

Document
dlrandy commented 6 years ago

<!DOCTYPE html>

Document
dlrandy commented 6 years ago

<!DOCTYPE html>

Document
dlrandy commented 6 years ago

import React from "react"; import ReactDOM from "react-dom"; import * as d3 from 'd3'; import { regionColors } from '../../../utils/Colors.js'; import { isIE11 } from '../../../utils/Helper.js'; class SurveyPie extends React.Component {

static defaultProps = { width: 500, height: 500, data: {}, }; constructor(props) { super(props); this.svgRef = React.createRef(); }

createPieChart() { const { isResponse, } = this.props;

const $selection = d3.select(this.svgRef.current);
$selection.selectAll('*').remove();
isIE11 ? $selection.style('transform', 'translate(60%, 41%)') : void(0);
$selection.attr('class', isResponse ? 'isReponsePie' : 'isSurveyPie');
function renderPie(pie, newArc) {
  newArc.innerRadius(50)
    .outerRadius(150)
    .padAngle(0.1);

  $selection
    .append('g')
    .attr('class', 'pieChart')
    .style('transform', 'translate(50%, 50%)')
    .selectAll('path')
    .data(pie)
    .enter()
    .append("path")
    // .attr("d", newArc)
    .style("fill", (d, i) => {
      console.log(d)
      return isResponse ? regionColors[i] : d3.schemeCategory10[(i + 9) % 10]
    })
    .style("stroke", "rgba(0,0,0,0)")
    .style("stroke-width", "20px")
    .transition()
    .duration(1000)
    .attrTween("d", function tweenDonut(b) {
      b.innerRadius = 0;
      var i = d3.interpolate({ startAngle: 0, endAngle: 0 }, b);
      return function (t) { return newArc(i(t)); };
    });
}

function renderLabels(pie, arc, texts, percents) {

  let filterPie = pie;//.filter(p => p.data);
  let filterChoices = texts;//.filter(c => c);
  let filterRates = percents;//.filter(r => r);

  let labels = $selection.select('g.pieChart')
    .selectAll("text.label")
    .data(filterPie);

  labels.enter()
    .append("text")
    .merge(labels)
    .attr("class", "label")
    .transition()
    .duration(1000)
    .attr("transform", function (d) {
      return "translate(" +
        arc.centroid(d) + ")";
    })
    .attr("dy", ".8em")
    .attr("text-anchor", "middle")
    .attr('fill', 'white')
    .text(function (d, i) {
      return percents[i] ? texts[i] : '';
    });

  let percent = $selection.select('g.pieChart')
    .selectAll("text.pert")
    .data(filterPie);
  console.log('pie', filterPie)

  percent.enter()
    .append("text")
    .merge(percent)
    .attr("class", "pert")
    .transition()
    .duration(1000)
    .attr("transform", function (d) {
      return "translate(" +
        arc.centroid(d) + ")";
    })
    .attr("dy", "0.01em")
    .attr("text-anchor", "middle")
    .attr('fill', 'white')
    .text(function (d, i) {
      return filterRates[i] ? filterRates[i] * 100 + '%' : '';
    });
}

let {
  percents = [],
  texts = [],
  numbers = [],
  totalCount = 0,
} = this.props.data;

let pieChart = d3.pie();
let surveyPie = pieChart(percents);
let newArc = d3.arc();

renderPie(surveyPie, newArc);
renderLabels(surveyPie, newArc, texts, percents)

}

createPieChartForSurvey() {

let {
  percents = [],
  texts = [],
  numbers = [],
  totalCount = 0,
} = this.props.data;

const {
  isResponse,
} = this.props;

let surveyPie,
  svg = d3.select("svg"),
  canvas = svg.append('g').attr('id', 'canvas'),
  art = canvas.append('g').attr('id', 'art'),
  labels = canvas.append('g').attr('id', 'labels');
svg.attr('class', isResponse ? 'isReponsePie' : 'isSurveyPie');
surveyPie = d3.pie()
surveyPie.value(function (d, i) {
  return d;
});

let cDim = {
  height: 500,
  width: 500,
  innerRadius: 50,
  outerRadius: 150,
  labelRadius: 200
}

svg.attr('height', cDim.height);
svg.attr('width', cDim.width);
canvas.attr("transform", "translate(" + (cDim.width / 2) + "," + (cDim.width / 2) + ")");

const pied_data = surveyPie(numbers);

const pied_arc = d3.arc()
  .innerRadius(50)
  .outerRadius(150)
  .padAngle(0.1);

const pied_colors = d3.schemeCategory10;

const enteringArcs = art.selectAll(".wedge").data(pied_data).enter();

enteringArcs.append("path")
  .attr("class", "wedge")
  // .attr("d", pied_arc)
  .style("fill", function (d, i) {
    return pied_colors[(i + 9) % 10];
  })
  .transition()
  .duration(1000)
  .attrTween("d", function tweenDonut(b) {
    b.innerRadius = 0;
    var i = d3.interpolate({ startAngle: 0, endAngle: 0 }, b);
    return function (t) { return pied_arc(i(t)); };
  });;

const enteringLabels = labels.selectAll(".label").data(pied_data).enter();
const labelGroups = enteringLabels.append("g").attr("class", "label");
labelGroups.append("circle").attr(
  'transform',
  function (d, i) {
    const centroid = pied_arc.centroid(d);
    return "translate(" + pied_arc.centroid(d) + ")";
  })
  .attr('class', "label-circle")
  .attr('dx', '0.57em');

const textLines = labelGroups.append("line").attr('x1', function (d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * (cDim.labelRadius - 40);
  return x;
})
  .attr('y1', function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const y = Math.sin(midAngle) * (cDim.labelRadius - 40);
    return y;
  })
  .attr('x2', function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const x = Math.cos(midAngle) * cDim.labelRadius;
    return x;
  })
  .attr(
  'y2',
  function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const y = Math.sin(midAngle) * cDim.labelRadius;
    return y;
  })
  .attr('class', 'label-line')
  .attr('stroke', function (d, i) {
    return d.data ? d3.schemeCategory10[(i + 9) % 10] : '';
  });

const textLabels = labelGroups.append("text").attr(
  'x',
  function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const x = Math.cos(midAngle) * cDim.labelRadius;
    const sign = (x > 0) ? 1 : -1
    const labelX = x + (5 * sign)
    return labelX;
  })
  .attr(
  'y',
  function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const y = Math.sin(midAngle) * cDim.labelRadius;
    return y;
  })
  .attr(
  'text-anchor',
  function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const x = Math.cos(midAngle) * cDim.labelRadius;
    return (x > 0) ? "start" : "end";
  }).text(function (d, i) {
    return d.data ? texts[i] : '';
  })
  .attr('class', 'label-text')
  .attr('width', '70');

const alpha = 0.5,
  spacing = 12;

function relax() {
  let again = false;
  textLabels.each(function (d, i) {
    let a = this,
      da = d3.select(a),
      y1 = da.attr("y");
    textLabels.each(function (d, j) {
      let b = this;
      // a & b are the same element and don't collide.
      if (a == b) return;
      let db = d3.select(b);
      // a & b are on opposite sides of the chart and
      // don't collide
      if (da.attr("text-anchor") != db.attr("text-anchor")) return;
      // Now let's calculate the distance between
      // these elements. 
      let y2 = db.attr("y");
      let deltaY = y1 - y2;

      // Our spacing is greater than our specified spacing,
      // so they don't collide.
      if (Math.abs(deltaY) > spacing) return;

      // If the labels collide, we'll push each 
      // of the two labels up and down a little bit.
      again = true;
      let sign = deltaY > 0 ? 1 : -1;
      const adjust = sign * alpha;
      da.attr("y", +y1 + adjust);
      db.attr("y", +y2 - adjust);

    });
  });
  // Adjust our line leaders here
  // so that they follow the labels. 
  console.log("textLabels ", textLabels);
  if (again) {
    let labelElements = textLabels._groups[0];
    console.log("textLabels ", textLabels);
    textLines.attr("y2", function (d, i) {
      let labelForLine = d3.select(labelElements[i]);
      return labelForLine.attr("y");
    });
    setTimeout(relax, 20)
  }
}

relax();
textLabels.call(dotme)
function dotme(text) {
  text.each(function () {
    var text = d3.select(this);
    var words = text.text().split('');

    var ellipsis = text.text('').append('tspan').attr('class', 'elip').text('...');
    var width = parseFloat(text.attr('width')) - ellipsis.node().getComputedTextLength();
    var numWords = words.length;

    var tspan = text.insert('tspan', ':first-child').text(words.join(''));

    while (tspan.node().getComputedTextLength() > width && words.length) {
      tspan.text(words.join('').substr(0, width - 3).trim());
      words.pop();
    }

    if (words.length === numWords) {
      ellipsis.remove();
    }

    text.append('title')
      .attr('class', 'tip')
      .text(function (d, i) {
        return d.data ? texts[i] : '';
      })
      .attr('fill', 'black');
  });
}

}

createPie = () => { const { isResponse, } = this.props;

isResponse ? this.createPieChart() : this.createPieChartForSurvey();

}

componentDidMount() { this.createPie(); } render() { const { width, height } = this.props; return (

);

}

componentDidUpdate() { this.createPie(); } } export default SurveyPie;

dlrandy commented 6 years ago

Responsive D3.js The only thing better than a nice, interactive D3 widget is one that’s mobile friendly. There are a variety of ways (using Javascript / CSS) to achieve this responsiveness. Here’s the way I do it when I’m making a visualization (no jQuery required):

function responsivefy(svg) { // get container + svg aspect ratio var container = d3.select(svg.node().parentNode), width = parseInt(svg.style("width")), height = parseInt(svg.style("height")), aspect = width / height;

// add viewBox and preserveAspectRatio properties,
// and call resize so that svg resizes on inital page load
svg.attr("viewBox", "0 0 " + width + " " + height)
    .attr("perserveAspectRatio", "xMinYMid")
    .call(resize);

// to register multiple listeners for same event type, 
// you need to add namespace, i.e., 'click.foo'
// necessary if you call invoke this function for multiple svgs
// api docs: https://github.com/mbostock/d3/wiki/Selections#on
d3.select(window).on("resize." + container.attr("id"), resize);

// get width of container and resize svg to fit it
function resize() {
    var targetWidth = parseInt(container.style("width"));
    svg.attr("width", targetWidth);
    svg.attr("height", Math.round(targetWidth / aspect));
}

} You can call this function when you add your SVG element to the page:

d3.select("#viz").append("svg") .attr("width", 960) .attr("height", 500) .call(responsivefy); And for completeness, here’s a working example with a randomly updating donut chart — try shrinking your browser window to see it adapt :)

https://brendansudol.com/writing/responsive-d3

dlrandy commented 6 years ago

import React from "react"; import ReactDOM from "react-dom"; import * as d3 from 'd3'; import { regionColors } from '../../../utils/Colors.js'; import { isIE11 } from '../../../utils/Helper.js';

import { responsivefy } from '../../../utils/Helper.js';

class SurveyPie extends React.Component {

static defaultProps = { width: 500, height: 500, data: {}, }; constructor(props) { super(props); // this.svgRef = React.createRef(); }

createPieChart() { const { isResponse, } = this.props;

const $selection = d3.select(this.svgRef);
$selection.selectAll('*').remove();
isIE11 ? $selection.style('transform', 'translate(60%, 41%)') : void(0);
$selection.attr('class', isResponse ? 'isReponsePie' : 'isSurveyPie');
function renderPie(pie, newArc) {
  newArc.innerRadius(47)
    .outerRadius(136)
    .padAngle(0.1);

  $selection
    .attr('height', 500)
    .attr('width', 500)
    .append('g')
    .attr('class', 'pieChart')
    .style('transform', 'translate(52%, 40%)')
    .selectAll('path')
    .data(pie)
    .enter()
    .append("path")
    // .attr("d", newArc)
    .style("fill", (d, i) => {
      console.log(d)
      return isResponse ? regionColors[i] : d3.schemeCategory10[(i + 9) % 10]
    })
    .style("stroke", "rgba(0,0,0,0)")
    .style("stroke-width", "20px")
    .transition()
    .duration(1000)
    .attrTween("d", function tweenDonut(b) {
      b.innerRadius = 0;
      var i = d3.interpolate({ startAngle: 0, endAngle: 0 }, b);
      return function (t) { return newArc(i(t)); };
    });
}

function renderLabels(pie, arc, texts, percents) {

  let filterPie = pie;//.filter(p => p.data);
  let filterChoices = texts;//.filter(c => c);
  let filterRates = percents;//.filter(r => r);

  let labels = $selection.select('g.pieChart')
    .selectAll("text.label")
    .data(filterPie);

  labels.enter()
    .append("text")
    .merge(labels)
    .attr("class", "label")
    .transition()
    .duration(1000)
    .attr("transform", function (d) {
      return "translate(" +
        arc.centroid(d) + ")";
    })
    .attr("dy", ".8em")
    .attr("text-anchor", "middle")
    .attr('fill', 'white')
    .text(function (d, i) {
      return percents[i] ? texts[i] : '';
    });

  let percent = $selection.select('g.pieChart')
    .selectAll("text.pert")
    .data(filterPie);

  percent.enter()
    .append("text")
    .merge(percent)
    .attr("class", "pert")
    .transition()
    .duration(1000)
    .attr("transform", function (d) {
      return "translate(" +
        arc.centroid(d) + ")";
    })
    .attr("dy", "0.01em")
    .attr("text-anchor", "middle")
    .attr('fill', 'white')
    .text(function (d, i) {
      return filterRates[i] ? filterRates[i] * 100 + '%' : '';
    });
}

let {
  percents = [],
  texts = [],
  numbers = [],
  totalCount = 0,
} = this.props.data;

let pieChart = d3.pie();
let surveyPie = pieChart(percents);
let newArc = d3.arc();

renderPie(surveyPie, newArc);
renderLabels(surveyPie, newArc, texts, percents);
$selection.call(responsivefy);

}

createPieChartForSurvey() {

let {
  percents = [],
  texts = [],
  numbers = [],
  totalCount = 0,
} = this.props.data;

const {
  isResponse,
} = this.props;

let surveyPie,
  svg = d3.select("svg"),
  canvas = svg.append('g').attr('id', 'canvas'),
  art = canvas.append('g').attr('id', 'art'),
  labels = canvas.append('g').attr('id', 'labels');
svg.attr('class', isResponse ? 'isReponsePie' : 'isSurveyPie');
isIE11 ? svg.style('transform', 'translate(50%, 50%)') : void(0);
surveyPie = d3.pie()
surveyPie.value(function (d, i) {
  return d;
});

let cDim = {
  height: 500,
  width: 500,
  innerRadius: 50,
  outerRadius: 150,
  labelRadius: 200
}

svg.attr('height', cDim.height);
svg.attr('width', '100%');
canvas.style('transform', 'translate(50%, 50%)');

const pied_data = surveyPie(numbers);

const pied_arc = d3.arc()
  .innerRadius(50)
  .outerRadius(150)
  .padAngle(0.1);

const pied_colors = d3.schemeCategory10;

const enteringArcs = art.selectAll(".wedge").data(pied_data).enter();

enteringArcs.append("path")
  .attr("class", "wedge")
  // .attr("d", pied_arc)
  .style("fill", function (d, i) {
    return pied_colors[(i + 9) % 10];
  })
  .transition()
  .duration(1000)
  .attrTween("d", function tweenDonut(b) {
    b.innerRadius = 0;
    var i = d3.interpolate({ startAngle: 0, endAngle: 0 }, b);
    return function (t) { return pied_arc(i(t)); };
  });;

const enteringLabels = labels.selectAll(".label").data(pied_data).enter();
const labelGroups = enteringLabels.append("g").attr("class", "label");
labelGroups.append("circle").attr(
  'transform',
  function (d, i) {
    const centroid = pied_arc.centroid(d);
    return "translate(" + pied_arc.centroid(d) + ")";
  })
  .attr('class', "label-circle")
  .attr('dx', '0.57em');

const textLines = labelGroups.append("line").attr('x1', function (d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * (cDim.labelRadius - 40);
  return x;
})
  .attr('y1', function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const y = Math.sin(midAngle) * (cDim.labelRadius - 40);
    return y;
  })
  .attr('x2', function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const x = Math.cos(midAngle) * cDim.labelRadius;
    return x;
  })
  .attr(
  'y2',
  function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const y = Math.sin(midAngle) * cDim.labelRadius;
    return y;
  })
  .attr('class', 'label-line')
  .attr('stroke', function (d, i) {
    return d.data ? d3.schemeCategory10[(i + 9) % 10] : '';
  });

const textLabels = labelGroups.append("text").attr(
  'x',
  function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const x = Math.cos(midAngle) * cDim.labelRadius;
    const sign = (x > 0) ? 1 : -1
    const labelX = x + (5 * sign)
    return labelX;
  })
  .attr(
  'y',
  function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const y = Math.sin(midAngle) * cDim.labelRadius;
    return y;
  })
  .attr(
  'text-anchor',
  function (d, i) {
    const centroid = pied_arc.centroid(d);
    const midAngle = Math.atan2(centroid[1], centroid[0]);
    const x = Math.cos(midAngle) * cDim.labelRadius;
    return (x > 0) ? "start" : "end";
  }).text(function (d, i) {
    return d.data ? texts[i] : '';
  })
  .attr('class', 'label-text')
  .attr('width', '70');

const percentLabels = labelGroups.append("text").attr( 'x', function (d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) cDim.labelRadius; const sign = (x > 0) ? 1 : -1 const labelX = x + (5 sign) return centroid[0]; }) .attr( 'y', function (d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const y = Math.sin(midAngle) cDim.labelRadius; return centroid[1]; }) .attr( 'text-anchor', function (d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) cDim.labelRadius; // return (x > 0) ? "start" : "end"; return 'middle'; }).text(function (d, i) { return d.data ? percents[i] * 100 + '%' : ''; }) .attr('fill', 'white') .attr('width', '70');

const alpha = 0.5,
  spacing = 12;

function relax() {
  let again = false;
  textLabels.each(function (d, i) {
    let a = this,
      da = d3.select(a),
      y1 = da.attr("y");
    textLabels.each(function (d, j) {
      let b = this;
      // a & b are the same element and don't collide.
      if (a == b) return;
      let db = d3.select(b);
      // a & b are on opposite sides of the chart and
      // don't collide
      if (da.attr("text-anchor") != db.attr("text-anchor")) return;
      // Now let's calculate the distance between
      // these elements. 
      let y2 = db.attr("y");
      let deltaY = y1 - y2;

      // Our spacing is greater than our specified spacing,
      // so they don't collide.
      if (Math.abs(deltaY) > spacing) return;

      // If the labels collide, we'll push each 
      // of the two labels up and down a little bit.
      again = true;
      let sign = deltaY > 0 ? 1 : -1;
      const adjust = sign * alpha;
      da.attr("y", +y1 + adjust);
      db.attr("y", +y2 - adjust);

    });
  });
  // Adjust our line leaders here
  // so that they follow the labels. 

  if (again) {
    let labelElements = textLabels._groups[0];
    textLines.attr("y2", function (d, i) {
      let labelForLine = d3.select(labelElements[i]);
      return labelForLine.attr("y");
    });
    setTimeout(relax, 20)
  }
}

relax();
textLabels.call(dotme);
svg.call(responsivefy);
function dotme(text) {
  text.each(function () {
    var text = d3.select(this);
    var words = text.text().split('');

    var ellipsis = text.text('').append('tspan').attr('class', 'elip').text('...');
    var width = parseFloat(text.attr('width')) - ellipsis.node().getComputedTextLength();
    var numWords = words.length;

    var tspan = text.insert('tspan', ':first-child').text(words.join(''));

    while (tspan.node().getComputedTextLength() > width && words.length) {
      tspan.text(words.join('').substr(0, width - 3).trim());
      words.pop();
    }

    if (words.length === numWords) {
      ellipsis.remove();
    }

    text.append('title')
      .attr('class', 'tip')
      .text(function (d, i) {
        return words.join('');
      })
      .attr('fill', 'black');
  });
}

}

createPie = () => { const { isResponse, } = this.props;

isResponse ? this.createPieChart() : this.createPieChartForSurvey();

}

componentDidMount() { this.createPie(); } render() { const { width, height } = this.props; return ( <svg ref={node => this.svgRef = node } width={width} height={height} preserveAspectRatio="xMidYMid meet">

  </svg>
);

}

componentDidUpdate() { this.createPie(); } } export default SurveyPie;

dlrandy commented 6 years ago

let percents = [0.1, 0.2, 0.7], texts = ["aaaa", "bbb", "ccc"], numbers = [10, 20, 70], totalCount = 100; const isCrossMinWidth = window.innerWidth <= 750; const fixHeight = 500; let width = isCrossMinWidth ? window.innerWidth : window.innerWidth / 3; let height = isCrossMinWidth ? window.innerHeight : window.innerHeight / 3;

let cDim = { height: height, width: width, innerRadius: width 0.1, outerRadius: width 0.4, labelRadius: width * 0.6 };

function createResPieChart() { const $selection = d3.select(".res-svg"); $selection.selectAll("*").remove();

function renderPie(pie, newArc) { newArc .innerRadius(cDim.innerRadius) .outerRadius(cDim.outerRadius) .padAngle(0.1);

$selection
  .attr("height", fixHeight)
  .attr("width", width)
  .append("g")
    .attr("class", "pieChart")
    .style("transform", "translate(75%, 55%)")
  .selectAll("path")
  .data(pie)
  .enter()
  .append("path")
    .style("fill", (d, i) => {
      return d3.schemeCategory10[(i + 9) % 10];
    })
    .style("stroke", "rgba(0,0,0,0)")
    .style("stroke-width", "20px")
    .transition()
    .duration(1000)
    .attrTween("d", function tweenDonut(b) {
      b.innerRadius = 0;
      let i = d3.interpolate(
        {
          startAngle: 0,
          endAngle: 0
        },
        b
      );
      return function(t) {
        return newArc(i(t));
      };
    });

}

function renderLabels(pie, arc, texts, percents) { let filterPie = pie, filterChoices = texts, filterRates = percents;

let labels = $selection
  .select("g.pieChart")
  .selectAll("text.label")
  .data(filterPie);

labels
  .enter()
  .append("text")
    .attr("class", "label")
    .transition()
    .duration(1000)
    .attr("transform", function(d) {
      return "translate(" + arc.centroid(d) + ")";
    })
    .attr("dy", ".8em")
    .attr("text-anchor", "middle")
    .attr("fill", "white")
    .text(function(d, i) {
      return percents[i] ? texts[i] : "";
    });

let percent = $selection
  .select("g.pieChart")
  .selectAll("text.pert")
  .data(filterPie);

percent
  .enter()
  .append("text")
    .attr("class", "pert")
    .transition()
    .duration(1000)
    .attr("transform", function(d) {
      return "translate(" + arc.centroid(d) + ")";
    })
    .attr("dy", "0.01em")
    .attr("text-anchor", "middle")
    .attr("fill", "white")
    .text(function(d, i) {
      return filterRates[i] ? filterRates[i] * 100 + "%" : "";
    });

}

let pieChart = d3.pie(); let surveyPie = pieChart(percents); let newArc = d3.arc();

renderPie(surveyPie, newArc); renderLabels(surveyPie, newArc, texts, percents); $selection.call(responsivefy); }

function createSurPieChart() { let surveyPie, svg = d3.select(".survey-svg"), canvas = svg.append("g").attr("id", "canvas"), art = canvas.append("g").attr("id", "art"), labels = canvas.append("g").attr("id", "labels");

surveyPie = d3.pie(); surveyPie.value(function(d, i) { return d; });

svg.attr("height", fixHeight); svg.attr("width", "100%"); canvas.style("transform", "translate(76%, 55%)");

const pied_data = surveyPie(numbers);

const pied_arc = d3 .arc() .innerRadius(cDim.innerRadius) .outerRadius(cDim.outerRadius) .padAngle(0.1);

const pied_colors = d3.schemeCategory10;

const enteringArcs = art .selectAll(".wedge") .data(pied_data) .enter();

enteringArcs .append("path") .attr("class", "wedge") // .attr("d", pied_arc) .style("fill", function(d, i) { return pied_colors[(i + 9) % 10]; }) .transition() .duration(1000) .attrTween("d", function tweenDonut(b) { b.innerRadius = 0; var i = d3.interpolate( { startAngle: 0, endAngle: 0 }, b ); return function(t) { return pied_arc(i(t)); }; });

const enteringLabels = labels .selectAll(".label") .data(pied_data) .enter(); const labelGroups = enteringLabels.append("g").attr("class", "label"); labelGroups .append("circle") .transition() .duration(1000) .attr("transform", function(d, i) { const centroid = pied_arc.centroid(d); return "translate(" + pied_arc.centroid(d) + ")"; }) .attr("class", "label-circle") .attr("dx", "0.57em");

const textLines = labelGroups .append("line") .transition() .duration(1000) .attr("x1", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) (cDim.labelRadius 0.7); return x; }) .attr("y1", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const y = Math.sin(midAngle) (cDim.labelRadius 0.7); return y; }) .attr("x2", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) (cDim.labelRadius 0.9); return x; }) .attr("y2", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const y = Math.sin(midAngle) (cDim.labelRadius 0.9); return y; }) .attr("class", "label-line") .attr("stroke", function(d, i) { return d.data ? d3.schemeCategory10[(i + 9) % 10] : ""; });

const textLabels = labelGroups .append("text") .attr("x", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) cDim.labelRadius; const sign = x > 0 ? 1 : -1; const labelX = x + 5 sign; return labelX 0.9; }) .attr("y", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const y = Math.sin(midAngle) cDim.labelRadius; return y 0.9; }) .attr("text-anchor", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) cDim.labelRadius; return x > 0 ? "start" : "end"; }) .text(function(d, i) { return d.data ? texts[i] : ""; }) .attr("class", "label-text") .attr("width", "70");

const percentLabels = labelGroups .append("text") .text(function(d, i) { return d.data ? percents[i] 100 + "%" : ""; }) .attr("fill", "white") .attr("width", "70") .transition() .duration(1000) .attr("x", function(d, i) { const centroid = pied_arc.centroid(d); return centroid[0]; }) .attr("y", function(d, i) { const centroid = pied_arc.centroid(d); return centroid[1]; }) .attr("text-anchor", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) cDim.labelRadius; // return (x > 0) ? "start" : "end"; return "middle"; });

const alpha = 0.5, spacing = 12;

function relax() { let again = false; textLabels.each(function(d, i) { let a = this, da = d3.select(a), y1 = da.attr("y"); textLabels.each(function(d, j) { let b = this; if (a == b) return; let db = d3.select(b); if (da.attr("text-anchor") != db.attr("text-anchor")) return; let y2 = db.attr("y"); let deltaY = y1 - y2; if (Math.abs(deltaY) > spacing) return; again = true; let sign = deltaY > 0 ? 1 : -1; const adjust = sign * alpha; da.attr("y", +y1 + adjust); db.attr("y", +y2 - adjust); }); });

if (again) {
  let labelElements = textLabels._groups[0];
  textLines.attr("y2", function(d, i) {
    let labelForLine = d3.select(labelElements[i]);
    return labelForLine.attr("y");
  });
  setTimeout(relax, 20);
}

}

relax(); textLabels.call(dotme); svg.call(responsivefy);

function dotme(text) { text.each(function() { var text = d3.select(this); var textbkp = text.text(); var words = text.text().split(""); var ellipsis = text .text("") .append("tspan") .attr("class", "elip") .text("...") .attr("font-size", "34"); var width = parseFloat(text.attr("width")) - ellipsis.node().getComputedTextLength(); var numWords = words.length;

  var tspan = text
    .insert("tspan", ":first-child")
    .text(words.join(""))
    .attr("font-size", "34");

  while (tspan.node().getComputedTextLength() > width && words.length) {
    tspan.text(
      words
        .join("")
        .substr(0, width - 3)
        .trim()
    );
    words.pop();
  }

  if (words.length === numWords) {
    ellipsis.remove();
  }

  text
    .append("title")
    .attr("class", "tip")
    .text(function(d, i) {
      return textbkp;
    })
    .attr("font-size", "34")
    .attr("fill", "black");
});

} }

function responsivefy(svg) { var container = d3.select(svg.node().parentNode), width = parseInt(svg.style("width")) 1.5, height = parseInt(svg.style("height")) 1.2, aspect = width / height; svg .attr("viewBox", "0 0 " + width + " " + height) .attr("perserveAspectRatio", "xMinYMid") .call(resize); d3.select(window).on("resize." + container.attr("id"), resize);

function resize() { var targetWidth = parseInt(container.style("width")); svg.attr("width", targetWidth); svg.attr("height", Math.round(targetWidth / aspect)); } }

createSurPieChart(); createResPieChart();

dlrandy commented 6 years ago
Bar chart with D3.js

// https://insights.stackoverflow.com/survey/2018/#technology-most-loved-dreaded-and-wanted-languages const sample = [ { language: 'Rust', value: 78.9, color: '#000000' }, { language: 'Kotlin', value: 75.1, color: '#00a2ee' }, { language: 'Python', value: 68.0, color: '#fbcb39' }, { language: 'TypeScript', value: 67.0, color: '#007bc8' }, { language: 'Go', value: 65.6, color: '#65cedb' }, { language: 'Swift', value: 65.1, color: '#ff6e52' }, { language: 'JavaScript', value: 61.9, color: '#f9de3f' }, { language: 'C#', value: 60.4, color: '#5d2f8e' }, { language: 'F#', value: 59.6, color: '#008fc9' }, { language: 'Clojure', value: 59.6, color: '#507dca' } ];

const svg = d3.select('svg');
const svgContainer = d3.select('#container');

const margin = 80;
const width = 1000 - 2 * margin;
const height = 600 - 2 * margin;

const chart = svg.append('g')
  .attr('transform', `translate(${margin}, ${margin})`);

const xScale = d3.scaleBand()
  .range([0, width])
  .domain(sample.map((s) => s.language))
  .padding(0.4)

const yScale = d3.scaleLinear()
  .range([height, 0])
  .domain([0, 100]);

// vertical grid lines
// const makeXLines = () => d3.axisBottom()
//   .scale(xScale)

const makeYLines = () => d3.axisLeft()
  .scale(yScale)

chart.append('g')
  .attr('transform', `translate(0, ${height})`)
  .call(d3.axisBottom(xScale));

chart.append('g')
  .call(d3.axisLeft(yScale));

// vertical grid lines
// chart.append('g')
//   .attr('class', 'grid')
//   .attr('transform', `translate(0, ${height})`)
//   .call(makeXLines()
//     .tickSize(-height, 0, 0)
//     .tickFormat('')
//   )

chart.append('g')
  .attr('class', 'grid')
  .call(makeYLines()
    .tickSize(-width, 0, 0)
    .tickFormat('')
  )

const barGroups = chart.selectAll()
  .data(sample)
  .enter()
  .append('g')

barGroups
  .append('rect')
  .attr('class', 'bar')
  .attr('x', (g) => xScale(g.language))
  .attr('y', (g) => yScale(g.value))
  .attr('height', (g) => height - yScale(g.value))
  .attr('width', xScale.bandwidth())
  .on('mouseenter', function (actual, i) {
    d3.selectAll('.value')
      .attr('opacity', 0)

    d3.select(this)
      .transition()
      .duration(300)
      .attr('opacity', 0.6)
      .attr('x', (a) => xScale(a.language) - 5)
      .attr('width', xScale.bandwidth() + 10)

    const y = yScale(actual.value)

    line = chart.append('line')
      .attr('id', 'limit')
      .attr('x1', 0)
      .attr('y1', y)
      .attr('x2', width)
      .attr('y2', y)

    barGroups.append('text')
      .attr('class', 'divergence')
      .attr('x', (a) => xScale(a.language) + xScale.bandwidth() / 2)
      .attr('y', (a) => yScale(a.value) + 30)
      .attr('fill', 'white')
      .attr('text-anchor', 'middle')
      .text((a, idx) => {
        const divergence = (a.value - actual.value).toFixed(1)

        let text = ''
        if (divergence > 0) text += '+'
        text += `${divergence}%`

        return idx !== i ? text : '';
      })

  })
  .on('mouseleave', function () {
    d3.selectAll('.value')
      .attr('opacity', 1)

    d3.select(this)
      .transition()
      .duration(300)
      .attr('opacity', 1)
      .attr('x', (a) => xScale(a.language))
      .attr('width', xScale.bandwidth())

    chart.selectAll('#limit').remove()
    chart.selectAll('.divergence').remove()
  })

barGroups 
  .append('text')
  .attr('class', 'value')
  .attr('x', (a) => xScale(a.language) + xScale.bandwidth() / 2)
  .attr('y', (a) => yScale(a.value) + 30)
  .attr('text-anchor', 'middle')
  .text((a) => `${a.value}%`)

svg
  .append('text')
  .attr('class', 'label')
  .attr('x', -(height / 2) - margin)
  .attr('y', margin / 2.4)
  .attr('transform', 'rotate(-90)')
  .attr('text-anchor', 'middle')
  .text('Love meter (%)')

svg.append('text')
  .attr('class', 'label')
  .attr('x', width / 2 + margin)
  .attr('y', height + margin * 1.7)
  .attr('text-anchor', 'middle')
  .text('Languages')

svg.append('text')
  .attr('class', 'title')
  .attr('x', width / 2 + margin)
  .attr('y', 40)
  .attr('text-anchor', 'middle')
  .text('Most loved programming languages in 2018')

svg.append('text')
  .attr('class', 'source')
  .attr('x', width - margin / 2)
  .attr('y', height + margin * 1.7)
  .attr('text-anchor', 'start')
  .text('Source: Stack Overflow, 2018')

body { font-family: 'Open Sans', sans-serif; }

div#layout { text-align: center; }

div#container { width: 500px; height: 600px; margin: auto; background-color: #2F4A6D; }

svg { width: 100%; height: 100%; }

.bar { fill: #80cbc4; }

text { font-size: 12px; fill: #fff; }

path { stroke: gray; }

line { stroke: gray; }

line#limit { stroke: #FED966; stroke-width: 3; stroke-dasharray: 3 6; }

.grid path { stroke-width: 0; }

.grid .tick line { stroke: #9FAAAE; stroke-opacity: 0.3; }

text.divergence { font-size: 14px; fill: #2F4A6D; }

text.value { font-size: 14px; }

text.title { font-size: 22px; font-weight: 600; }

text.label { font-size: 14px; font-weight: 400; }

text.source { font-size: 10px; }

dlrandy commented 6 years ago

/bar.js/ import React from 'react'; import ReactDOM from 'react-dom'; import * as d3 from 'd3';

import { regionColors, choiceColors } from '../../../utils/Colors.js'; import { getComputedPercentage } from '../../../utils/Helper.js'; class SurveyBar extends React.Component {

static defaultProps = { width: 500, height: 500, data: {}, }; constructor(props) { super(props); // this.svgRef = React.createRef(); }

createBarChart(){ const { isResponse, } = this.props;

let {
  percents = [],
  texts = [],
  numbers = [],
  totalCount = 0,
} = this.props.data;

let rx = 0,
  ry = 0,
  areaHeight = d3.select(this.svgRef).attr('height') * 0.7,
  lineHeight = -10;

let voteLenScale = d3.scaleLinear()
  .domain([0, totalCount])
  .range([0, 1]);

let voteLineScale = d3.scaleLinear()
  .domain([0, texts.length + 1])
  .range([0, areaHeight]);

let $voteDataArea = d3.select(this.svgRef);
 $voteDataArea.selectAll('*').remove();

function generateRect($selection) {
  let ele = $selection
    .append('rect');
  ele.attr('rx', rx)
    .attr('ry', ry)
    .attr('height', 28)
    .attr('width', 0)
    .attr('y', function (d, i) {
      return voteLineScale(i + 1);
    })
    .attr('x', 0)
    .attr('fill', 'lightgray');

  return ele;
}
let $backgroundBar = $voteDataArea.selectAll('.backgroundbar')
  .data(texts)
  .enter();

generateRect($backgroundBar)
  .attr('class', 'backgroundbar')
  .attr('width', function (d, i) {
    return '100%';
  });

let $votesBar = $voteDataArea.selectAll('.votesbar')
  .data(numbers)
  .enter();

generateRect($votesBar)
  .attr('fill', function (d, i) {
    return isResponse ? regionColors[texts[i]] : choiceColors[i];
  })
  .transition()
  .delay(100)
  .duration(500)
  .attr('width', function (d, i) {
    return voteLenScale(d) * 100 + '%';
  });
function createText($selection) {
  let $text = $selection
    .append("text");

  $text.attr('y', function (d, i) {
      return voteLineScale(i + 1) + lineHeight;
    })
    .attr('x', 0)
    .attr('lengthAdjust', "spacingAndGlyphs")
    .attr("font-size", "15px")
    .attr("fill", "blue");

  return $text;
}

function createTitle($selection) {
  let $group = $selection
    .append("g");

  let $title = $group
    .append("text");

  $title.attr('y', function (d, i) {
      return voteLineScale(i + 1) + lineHeight;
    })
    .attr('x', 0)
    .attr('lengthAdjust', "spacingAndGlyphs")
    .attr("font-size", "15px")
    .attr("fill", "blue");

  $group.append('title')
    .attr('class', 'tip')
    .text(function (d) {
      return d;
    })
    .attr('x', 48)
    .attr('fill', 'black');

  return $title;
}
let $voteCountSelection = $voteDataArea.selectAll('text.count')
  .data(numbers) 
  .enter();
createText($voteCountSelection)
  .attr('class', 'count')
  .attr('x', 10)
  .attr('y', function (d, i) {
      return voteLineScale(i + 1) + 18;
    })
  .attr('fill', isResponse ? 'rgba(0,0,0,0)' : 'white')
  .text(function (d) {
    return d;
  });

let $percentSelection = $voteDataArea.selectAll('text.percent')
  .data(percents)
  .enter();
createText($percentSelection)
  .attr('class', 'percent')
  .text(function (d) {
    return getComputedPercentage(d);
  });

let $titleSelection = $voteDataArea.selectAll('text.title')
  .data(texts)
  .enter();
createTitle($titleSelection)
  .attr('class', 'title')
  .text(function (d) {
    return d;
  })
  .attr('x', 60)
  .attr('width', 450)
  .attr('fill', 'black');

function dotme(text) {
  text.each(function () {
    var text = d3.select(this);
    var words = text.text().split('');

    var ellipsis = text.text('').append('tspan').attr('class', 'elip').text('...');
    var width = parseFloat(text.attr('width')) - ellipsis.node().getComputedTextLength();
    var numWords = words.length;

    var tspan = text.insert('tspan', ':first-child').text(words.join(''));

    while (tspan.node().getComputedTextLength() > width && words.length) {
      tspan.text(words.join('').substr(0, width - 4).trim());
      words.pop();
    }

    if (words.length === numWords) {
      ellipsis.remove();
    }
  });
}

// $voteDataArea.selectAll('text.title').call(wrap);
$voteDataArea.selectAll('text.title').call(dotme);

} componentDidMount(){ this.createBarChart(); } render() { const { width, height, survey } = this.props; return ( <svg ref={node => this.svgRef = node} width={width} height={height} preserveAspectRatio="xMidYMid meet"> ); }

componentDidUpdate(){ this.createBarChart(); } } export default SurveyBar;

/pie.js/ import React from "react"; import ReactDOM from "react-dom"; import * as d3 from 'd3'; import { regionColors, choiceColors } from '../../../utils/Colors.js'; import { isIE11, getComputedPercentage } from '../../../utils/Helper.js';

import { responsivefy } from '../../../utils/Helper.js';

class SurveyPie extends React.Component {

static defaultProps = { width: 500, height: 500, data: {}, }; constructor(props) { super(props); // this.svgRef = React.createRef(); }

createPieChart() { const isCrossMinWidth = window.innerWidth <= 750; const fixHeight = 450; let width = isCrossMinWidth ? window.innerWidth : window.innerWidth / 3; let height = isCrossMinWidth ? window.innerHeight : window.innerHeight / 3;

let cDim = {
  height: height,
  width: width,
  innerRadius: width * 0.1,
  outerRadius: width * 0.4,
  labelRadius: width * 0.6
};
const {
  isResponse,
} = this.props;

const $selection = d3.select(this.svgRef);
$selection.selectAll('*').remove();
isIE11 ? $selection.style('transform', 'translate(50%, 38%)') : void(0);
$selection.attr('class', isResponse ? 'isReponsePie' : 'isSurveyPie');
function renderPie(pie, newArc) {
  newArc.innerRadius(cDim.innerRadius)
    .outerRadius(cDim.outerRadius)
    .padAngle(0.1);

  $selection
  .attr("height", fixHeight)
  .attr("width", width)
  .append("g")
    .attr("class", "pieChart")
    .style("transform", "translate(50%, 55%)")
  .selectAll("path")
  .data(pie)
  .enter()
  .append("path")
    .style("fill", (d, i) => {
      return isResponse ? regionColors[texts[i]] : choiceColors[i];
    })
    .style("stroke", "rgba(0,0,0,0)")
    .style("stroke-width", "20px")
    .transition()
    .duration(1000)
    .attrTween("d", function tweenDonut(b) {
      b.innerRadius = 0;
      var i = d3.interpolate({ startAngle: 0, endAngle: 0 }, b);
      return function (t) { return newArc(i(t)); };
    });

}

function renderLabels(pie, arc, texts, percents) {

  let filterPie = pie;//.filter(p => p.data);
  let filterChoices = texts;//.filter(c => c);
  let filterRates = percents;//.filter(r => r);

  let labels = $selection.select('g.pieChart')
    .selectAll("text.label")
    .data(filterPie);

  labels.enter()
    .append("text")
    .merge(labels)
    .attr("class", "label")
    .transition()
    .duration(1000)
    .attr("transform", function (d) {
      return "translate(" +
        arc.centroid(d) + ")";
    })
    .attr("dy", ".8em")
    .attr("text-anchor", "middle")
    .attr('fill', 'white')
    .text(function (d, i) {
      return percents[i] ? texts[i] : '';
    })
    .attr('font-size', 34);

  let percent = $selection.select('g.pieChart')
    .selectAll("text.pert")
    .data(filterPie);

  percent.enter()
    .append("text")
    .merge(percent)
    .attr("class", "pert")
    .transition()
    .duration(1000)
    .attr("transform", function (d) {
      return "translate(" +
        arc.centroid(d) + ")";
    })
    .attr("dy", "0.01em")
    .attr("text-anchor", "middle")
    .attr('fill', 'white')
    .text(function (d, i) {
      return filterRates[i] ? getComputedPercentage(filterRates[i]) : '';
    })
    .attr('font-size', 34);
}

let {
  percents = [],
  texts = [],
  numbers = [],
  totalCount = 0,
} = this.props.data;

let pieChart = d3.pie();
let surveyPie = pieChart(percents);
let newArc = d3.arc();

renderPie(surveyPie, newArc);
renderLabels(surveyPie, newArc, texts, percents);
$selection.call(responsivefy, true);

}

createPieChartForSurvey() {

const isCrossMinWidth = window.innerWidth <= 750;
const fixHeight = 500;
let width = isCrossMinWidth ? window.innerWidth : window.innerWidth / 3;
let height = isCrossMinWidth ? window.innerHeight : window.innerHeight / 3;
let cDim = {
  height: height,
  width: width,
  innerRadius: width * 0.1,
  outerRadius: width * 0.4,
  labelRadius: width * 0.6
};

let {
  percents = [],
  texts = [],
  numbers = [],
  totalCount = 0,
} = this.props.data;

const {
  isResponse,
} = this.props;

let surveyPie,
  svg = d3.select(this.svgRef),
  canvas = svg.append('g').attr('id', 'canvas'),
  art = canvas.append('g').attr('id', 'art'),
  labels = canvas.append('g').attr('id', 'labels');
svg.attr('class', isResponse ? 'isReponsePie' : 'isSurveyPie');
isIE11 ? svg.style('transform', 'translate(50%, 33%)') : void(0);
surveyPie = d3.pie()
surveyPie.value(function (d, i) {
  return d;
});

svg.attr("height", fixHeight);
svg.attr("width", cDim.width);
canvas.style("transform", "translate(75%,80%)");

const pied_data = surveyPie(numbers);

const pied_arc = d3
  .arc()
  .innerRadius(cDim.innerRadius)
  .outerRadius(cDim.outerRadius)
  .padAngle(0.1);

const enteringArcs = art.selectAll(".wedge").data(pied_data).enter();

enteringArcs.append("path")
  .attr("class", "wedge")
  // .attr("d", pied_arc)
  .style("fill", function (d, i) {
    return choiceColors[i];
  })
  .transition()
  .duration(1000)
  .attrTween("d", function tweenDonut(b) {
    b.innerRadius = 0;
    var i = d3.interpolate({ startAngle: 0, endAngle: 0 }, b);
    return function (t) { return pied_arc(i(t)); };
  });;

const enteringLabels = labels .selectAll(".label") .data(pied_data) .enter(); const labelGroups = enteringLabels.append("g").attr("class", "label"); labelGroups .append("circle") .transition() .duration(1000) .attr("transform", function(d, i) { const centroid = pied_arc.centroid(d); return "translate(" + pied_arc.centroid(d) + ")"; }) .attr("class", "label-circle") .attr("dx", "0.57em");

const textLines = labelGroups
.append("line")
.transition()
.duration(1000)
.attr("x1", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * (cDim.labelRadius * 0.7);
  return x;
})
.attr("y1", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const y = Math.sin(midAngle) * (cDim.labelRadius * 0.7);
  return y;
})
.attr("x2", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * (cDim.labelRadius * 0.9);
  return x;
})
.attr("y2", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const y = Math.sin(midAngle) * (cDim.labelRadius * 0.9);
  return y;
})
.attr("class", "label-line")
.attr("stroke", function(d, i) {
  return d.data ? choiceColors[i] : "";
});

 const textLabels = labelGroups
.append("text")
.attr("x", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * cDim.labelRadius;
  const sign = x > 0 ? 1 : -1;
  const labelX = x + 5 * sign;
  return labelX * 0.9;
})
.attr("y", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const y = Math.sin(midAngle) * cDim.labelRadius;
  return y * 0.9;
})
.attr("text-anchor", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * cDim.labelRadius;
  return "middle";
})
.text(function(d, i) {
  return d.data ? texts[i] : "";
})
.attr("class", "label-text")
.attr("dx", function(d, i){
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * cDim.labelRadius;
  return x > 0 ? (isIE11 ?  "1em": "-2em") : (isIE11 ? "1em": "2em");

})
.attr("dy", function(d, i){
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const y = Math.sin(midAngle) * cDim.labelRadius;
  return y > 0 ?  (isIE11 ? "1em": "2.3em") : (isIE11 ? "0em" : "-1.5em");
})
.attr("width", cDim.width / 2);

const percentLabels = labelGroups .append("text") .text(function(d, i) { return d.data ? getComputedPercentage(percents[i]) : ""; }) .attr("fill", "white") .attr("width", "120") .attr("font-size", 34) .transition() .duration(1000) .attr("x", function(d, i) { const centroid = pied_arc.centroid(d); return centroid[0]; }) .attr("y", function(d, i) { const centroid = pied_arc.centroid(d); return centroid[1]; }) .attr("text-anchor", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) * cDim.labelRadius; // return (x > 0) ? "start" : "end"; return "middle"; });

const alpha = 0.5,
  spacing = 12;

function relax() { let again = false; textLabels.each(function(d, i) { let a = this, da = d3.select(a), y1 = da.attr("y"); textLabels.each(function(d, j) { let b = this; if (a == b) return; let db = d3.select(b); if (da.attr("text-anchor") != db.attr("text-anchor")) return; let y2 = db.attr("y"); let deltaY = y1 - y2; if (Math.abs(deltaY) > spacing) return; again = true; let sign = deltaY > 0 ? 1 : -1; const adjust = sign * alpha; da.attr("y", +y1 + adjust); db.attr("y", +y2 - adjust); }); });

if (again) {
  let labelElements = textLabels._groups[0];
  textLines.attr("y2", function(d, i) {
    let labelForLine = d3.select(labelElements[i]);
    return labelForLine.attr("y");
  });
  setTimeout(relax, 20);
}

}

// relax();
textLabels.call(wrap);
svg.call(responsivefy);
function dotme(text) {
  text.each(function() {
  var text = d3.select(this);
  var textbkp = text.text();
  var words = text.text().split("");
  var ellipsis = text
    .text("")
    .append("tspan")
    .attr("class", "elip")
    .text("...")
    .attr("font-size", "34");
  var width =
    parseFloat(text.attr("width")) -
    ellipsis.node().getComputedTextLength();
  var numWords = words.length;

  var tspan = text
    .insert("tspan", ":first-child")
    .text(words.join(""))
    .attr("font-size", "34");

  while (tspan.node().getComputedTextLength() > width && words.length) {
    tspan.text(
      words
        .join("")
        .substr(0, width - 3)
        .trim()
    );
    words.pop();
  }

  if (words.length === numWords) {
    ellipsis.remove();
  }

  text
    .append("title")
    .attr("class", "tip")
    .text(function(d, i) {
      return textbkp;
    })
    .attr("font-size", "34")
    .attr("fill", "black");
});

} function wrap(text) { text.each(function () { var text = d3.select(this); var words = text.text().split(' ').reverse(); var lineHeight = 30; var width = parseFloat(text.attr('width')); var y = parseFloat(text.attr('y')); var x = text.attr('x'); var anchor = text.attr('text-anchor');

var tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('text-anchor', anchor).attr("font-size", "34");
var lineNumber = 0;
var line = [];
var tspans = [];
var word = words.pop();
var isBottom = !(~text.attr('dy').indexOf('-'));
tspans.push(tspan); 
while (word) {
  line.push(word);
  tspan.text(line.join(' '));
  if (tspan.node().getComputedTextLength() > width) {
    lineNumber += 1;
    line.pop();
    tspan.text(line.join(' '));
    line = [word];
    tspan = text.append('tspan').attr('x', x).attr('y', y + lineNumber * lineHeight).attr('anchor', anchor)
      .text(word);
    tspan.attr("font-size", "34");
    tspans.push(tspan);
  }
  word = words.pop();
}
var dy = +text.attr('dy').match(/^(^-?\d+(?:\.\d+)?)(.*)$/)[1];
isBottom && tspans.shift();
lineNumber > 0 && tspans.forEach(t => {
    t.attr('dy',  isBottom ? lineHeight : -1 * lineHeight);
})

text.attr("font-size", "34");

}); } }

createPieChartForRes() {

const isCrossMinWidth = window.innerWidth <= 750;
const fixHeight = 500;
let width = isCrossMinWidth ? window.innerWidth : window.innerWidth / 3;
let height = isCrossMinWidth ? window.innerHeight : window.innerHeight / 3;
let cDim = {
  height: height,
  width: width,
  innerRadius: width * 0.1,
  outerRadius: width * 0.4,
  labelRadius: width * 0.6
};

let {
  percents = [],
  texts = [],
  numbers = [],
  totalCount = 0,
} = this.props.data;

const {
  isResponse,
} = this.props;

let surveyPie,
  svg = d3.select(this.svgRef);
  svg.selectAll('*').remove();

  let canvas = svg.append('g').attr('id', 'res-canvas'),
  art = canvas.append('g').attr('id', 'art'),
  labels = canvas.append('g').attr('id', 'labels');
svg.attr('class', isResponse ? 'isReponsePie' : 'isSurveyPie');
isIE11 ? svg.style('transform', 'translate(50%, 33%)') : void(0);
surveyPie = d3.pie()
surveyPie.value(function (d, i) {
  return d;
});

svg.attr("height", fixHeight);
svg.attr("width", cDim.width);
canvas.style("transform", "translate(35%,45%)");

const pied_data = surveyPie(numbers);

const pied_arc = d3
  .arc()
  .innerRadius(cDim.innerRadius)
  .outerRadius(cDim.outerRadius)
  .padAngle(0.1);

const enteringArcs = art.selectAll(".wedge").data(pied_data).enter();

enteringArcs.append("path")
  .attr("class", "wedge")
  .style("fill", function (d, i) {
    return regionColors[texts[i]];
  })
  .transition()
  .duration(1000)
  .attrTween("d", function tweenDonut(b) {
    b.innerRadius = 0;
    var i = d3.interpolate({ startAngle: 0, endAngle: 0 }, b);
    return function (t) { return pied_arc(i(t)); };
  });;

const enteringLabels = labels .selectAll(".label") .data(pied_data) .enter(); const labelGroups = enteringLabels.append("g").attr("class", "label"); labelGroups .append("circle") .transition() .duration(1000) .attr("transform", function(d, i) { const centroid = pied_arc.centroid(d); return "translate(" + pied_arc.centroid(d) + ")"; }) .attr("class", "label-circle") .attr("dx", "0.57em");

const textLines = labelGroups
.append("line")
.transition()
.duration(1000)
.attr("x1", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * (cDim.labelRadius * 0.7);
  return x;
})
.attr("y1", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const y = Math.sin(midAngle) * (cDim.labelRadius * 0.7);
  return y;
})
.attr("x2", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * (cDim.labelRadius * 0.9);
  return x;
})
.attr("y2", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const y = Math.sin(midAngle) * (cDim.labelRadius * 0.9);
  return y;
})
.attr("class", "label-line")
.attr("stroke", function(d, i) {
  return d.data ? regionColors[texts[i]] : "";
});

 const textLabels = labelGroups
.append("text")
.attr("text-anchor", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * cDim.labelRadius;
  //  return (x < 0) ? "start" : "end";
  return "middle";
})
.text(function(d, i) {
  return d.data ? texts[i] : "";
})
.attr("font-size", 34)
// .attr("dx", function(d, i){
//   const centroid = pied_arc.centroid(d);
//   const midAngle = Math.atan2(centroid[1], centroid[0]);
//   const x = Math.cos(midAngle) * cDim.labelRadius;
//   return x > 0 ? "-2em" : "2em";
// })
// .attr("dy", function(d, i){
//   const centroid = pied_arc.centroid(d);
//   const midAngle = Math.atan2(centroid[1], centroid[0]);
//   const y = Math.sin(midAngle) * cDim.labelRadius;
//   return y > 0 ? '2.3em': '-1.1em';
// })
.attr("width", cDim.width / 2)
.transition()
.duration(1000)
.attr("x", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const x = Math.cos(midAngle) * cDim.labelRadius;
  const sign = x > 0 ? 1 : -1;
  const labelX = x + 5 * sign;
  return labelX * 1.1;
})
.attr("y", function(d, i) {
  const centroid = pied_arc.centroid(d);
  const midAngle = Math.atan2(centroid[1], centroid[0]);
  const y = Math.sin(midAngle) * cDim.labelRadius;
  return y;
});

const percentLabels = labelGroups .append("text") .text(function(d, i) { return d.data ? getComputedPercentage(percents[i]) : ""; }) .attr("fill", "blue") .attr("width", "120") .attr("font-size", 34) .transition() .duration(1000) .attr("x", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) cDim.labelRadius; const sign = x > 0 ? 1 : -1; const labelX = x + 5 sign; return labelX; }) .attr("y", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const y = Math.sin(midAngle) cDim.labelRadius; return y; }) .attr("text-anchor", function(d, i) { const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) cDim.labelRadius; // return (x < 0) ? "start" : "end"; return "middle"; }) .attr("dx", function(d, i){ const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const x = Math.cos(midAngle) cDim.labelRadius; return x > 0 ? "1em" : "-1em"; }) .attr("dy", function(d, i){ const centroid = pied_arc.centroid(d); const midAngle = Math.atan2(centroid[1], centroid[0]); const y = Math.sin(midAngle) cDim.labelRadius; return '1em'; });

svg.call(responsivefy, true);

}

createPie = () => { const { isResponse, } = this.props;

isResponse ? this.createPieChartForRes() : this.createPieChartForSurvey();

}

componentDidMount() { this.createPie(); } render() { const { width, height } = this.props; return ( <svg ref={node => this.svgRef = node } width={width} height={height} preserveAspectRatio="xMidYMid">

  </svg>
);

}

componentDidUpdate() { this.createPie(); } } export default SurveyPie; /chart.js/ import React from "react"; import ReactDOM from "react-dom";

import BarChart from '../../components/charts/Bar/index.js'; import PieChart from '../../components/charts/Pie/index.js'; import { BarIcon, DonutIcon } from '../../components/Icons/index.js'; export default class Chart extends React.Component { initialState = { chartType: this.props.chartType ? this.props.chartType : 'bar' };

static defaultProps = { survey: { noData: true, } }

constructor(props) { super(props); this.state = this.initialState; }

toggle = () => { const { chartType } = this.state; let nextType = 'bar'; chartType === 'bar' ? (nextType = 'donut') : ''; this.setState({ chartType: nextType, });

}

render() { const { survey, needLineDonut, isResponse, } = this.props;

const { noData, question, totalCount, regions, choices, counts, totalCountRatioArr = '[]', } = survey;

if(noData) { return null; } const { chartType } = this.state;

let percents, texts, numbers, totalCounts;
if(isResponse){
  percents = regions.map(r => r.countRation).filter(c => c);
  texts = regions.map(r => r.name).filter(c => c);
  numbers = regions.map(r => r.count).filter(c => c);
  totalCounts = numbers.reduce((s, i) => s + i, 0) ; //totalCount
} else {
  percents = JSON.parse(totalCountRatioArr);
  texts = choices.map(c =>c.choiceName);
  numbers = [...counts];
  totalCounts = totalCount;
}
const finalData = {
  percents,
  texts,
  numbers,
  totalCount : totalCounts,
}
if(finalData.percents.length <= 0){
  return <div>No Data Available.</div>
}
const surveyId = survey.id || survey.surveyId;
return (
  <div id={isResponse ? surveyId + 'res' : surveyId } className="chart-canvas">
    {
      chartType === 'bar' ?  <BarChart {...this.props} width={'94%'} data={finalData} /> : 
      <PieChart  {...this.props}  width={'94%'}  data={finalData} />
    }
    <div onClick={this.toggle} className="survey-charts-control">
      <div><BarIcon active = {chartType === 'bar'} /></div>
      <div><DonutIcon active = { chartType === 'donut'}/></div>
    </div>
 </div>
);

} }

/util/ export function responsivefy(svg, isRes) { var container = d3.select(svg.node().parentNode), width = parseInt(svg.style("width")) (isRes ? 1.8 : 1.8), height = parseInt(svg.style("height")) (isRes ? 1.9 : 2), aspect = width / height; svg .attr("viewBox", "0 0 " + width + " " + height) .attr("perserveAspectRatio", "xMinYMid") .call(resize); d3.select(window).on("resize." + container.attr("id") + (isRes ? 'rse' : ''), resize);

function resize() { var targetWidth = parseInt(container.style("width")) || false; if(targetWidth){ svg.attr("width", targetWidth); svg.attr("height", Math.round(targetWidth / aspect)); }

isRes ? d3.select(svg.node().querySelector('#res-canvas')).style('transform', 'translate(48%,43%)') : null;

} }

export const regionColors = {"APAC":"#00843D","EMEA":"#007377","NAM":"#949300","LATAM":"#c99700","Others":"#ED8B00"};

export const choiceColors = ['#00B0B9','#84BD00','#C4D600','#FFCD00','#ED8B00','#C6007E','#A05EB5', '#007377','#00843D','#949300','#c99700'];