Open dlrandy opened 6 years ago
<!DOCTYPE html>
<!DOCTYPE html>
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;
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 :)
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;
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();
// 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; }
/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'];
https://bost.ocks.org/mike/selection/
http://bl.ocks.org/dbuezas/9306799
<!DOCTYPE html>