Skip to content

Instantly share code, notes, and snippets.

@timproDev
Last active April 6, 2018 21:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timproDev/5278ace1176bd55a41d39a06af8b9523 to your computer and use it in GitHub Desktop.
Save timproDev/5278ace1176bd55a41d39a06af8b9523 to your computer and use it in GitHub Desktop.
Radial Barchart aka the Seashell
@media only screen and (min-width: 641px) {
body, html {
overflow-x: hidden; } }
@media only screen and (max-width: 40em) {
html {
-webkit-text-size-adjust: 100%; } }
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
height: auto;
display: block; }
article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary {
display: block; }
img {
border: none; }
button, input, optgroup, select, textarea {
color: inherit;
/* 1 */
font: inherit;
/* 2 */
margin: 0;
/* 3 */
-webkit-font-smoothing: antialiased; }
input, button, select {
outline: none; }
*,
*:before,
*:after {
box-sizing: border-box; }
.chart-ui-container {
display: block;
width: auto;
margin: 0 auto 2rem auto;
text-align: left;
border-bottom: 2px solid #f1f1f1;
float: left;
padding-bottom: .75rem; }
.chart-ui-container .chart-form__radio {
display: inline-block;
padding: 0;
margin-right: 1rem;
padding: 1rem 0; }
.chart-ui-container .chart-form__radio:last-of-type {
margin-right: 0; }
.chart-ui-container .chart-form__radio.stacked {
display: block;
padding: .25rem 0; }
.chart-ui-container .chart-form__radio-label {
display: inline-block;
padding: 0 .15rem;
font-family: 'Raleway', sans-serif;
font-weight:300;
line-height: 1;
font-size: 1rem; }
@-webkit-keyframes click-wave {
0% {
height: 20px;
width: 20px;
opacity: 0.35;
position: relative; }
100% {
height: 40px;
width: 40px;
margin-left: -10px;
margin-top: -10px;
opacity: 0; } }
@keyframes click-wave {
0% {
height: 20px;
width: 20px;
opacity: 0.35;
position: relative; }
100% {
height: 40px;
width: 40px;
margin-left: -10px;
margin-top: -10px;
opacity: 0; } }
.chart-ui-container .chart-form__radio-input {
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
position: relative;
top: .3rem;
right: 0;
bottom: 0;
left: 0;
height: 20px;
width: 20px;
transition: all 0.15s ease-out 0s;
background: #f3f3f3;
border: none;
color: #006D9E;
cursor: pointer;
display: inline-block;
margin-right: 0.75rem;
outline: none;
position: relative;
z-index: 1000;
box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.15);
transition: all 350ms; }
.chart-ui-container .chart-form__radio-input:hover {
background: #e2e2e2; }
.chart-ui-container .chart-form__radio-input:checked {
background: #A6E2EF; }
.chart-ui-container .chart-form__radio-input::before {
height: 12px;
width: 12px;
position: absolute;
top: 4px;
left: 4px;
background-color: #ffffff;
border-radius: 50%;
content: '';
display: block;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.15);
opacity: 0;
transition: opacity 250ms; }
.chart-ui-container .chart-form__radio-input:hover::before {
opacity: .55; }
.chart-ui-container .chart-form__radio-input:checked::before {
opacity: 1; }
.chart-ui-container .chart-form__radio-input:checked::after {
-webkit-animation: click-wave 0.65s;
animation: click-wave 0.65s;
background: #00A8C8;
content: '';
display: block;
position: relative;
z-index: 100; }
.chart-ui-container .chart-form__radio-input {
border-radius: 50%; }
.chart-ui-container .chart-form__radio-input::after {
border-radius: 50%; }
line {
shape-rendering: crispEdges; }
.aboveLabelTickText {
font-family: 'Raleway', sans-serif;
font-weight:400;
font-size: 1rem;
fill: #404040; }
.barLabel {
font-family: 'Raleway', sans-serif;
font-weight:700;
font-size: 1.15rem; }
.legend-item text {
font-family: 'Raleway', sans-serif;
font-weight:400;
font-size: .85rem; }
.center-label {
font-family: 'Raleway', sans-serif;
font-weight:400;
font-size: 1rem;
letter-spacing: -.5px;
fill: #808080;
text-transform: uppercase; }
.center-label-value-null {
font-family: 'Raleway', sans-serif;
font-weight:700;
font-size: 1.5rem;
letter-spacing: -.75px; }
#lineChartMulti .cov-text, #lineChartMultiBlocks .cov-text {
font-family: 'Raleway', sans-serif;
font-weight:700;
font-size: 13px; }
.barLabelLight {
font-family: 'Raleway', sans-serif;
font-weight:400;
font-size: .75rem; }
text.barLabelLight {
cursor: default;
letter-spacing: -.5px; }
.tree-label {
font-family: 'Raleway', sans-serif;
font-weight:400;
font-size: 1.5rem; }
#radialBarChartBlocks {
max-width: 100% !important;
}
#radialBarChartBlocks .chart-ui-container {
position: relative !important;
}
#radialBarChartBlocks .data-card {
width: auto;
height: auto;
position: absolute;
top: 90px;
left: 0px;
text-align: left;
}
#radialBarChartBlocks .data-card h5, #radialBarChartBlocks .data-card p {
padding: 0;
margin: 0;
}
#radialBarChartBlocks .data-card h5 {
font-family: 'Raleway', sans-serif;
font-weight:400;
font-size: 1.25rem;
color: #bfbfbf;
background-color: #ffffff;
padding: 5px;
}
#radialBarChartBlocks .data-card h5 span {
font-size: .75rem;
text-transform: uppercase;
color: #808080;
}
.global-chart-container {
width: 80%;
margin:0 auto;
text-align: center;
padding-top: 6rem;
}
.mx-dv-container {
text-align: center;
margin: 2rem auto;
padding: .5rem 1rem 1rem 1rem;
border: 1px solid #f2f2f2;
border-radius: 3px;
max-width: 775px;
}
.mx-dv-container .plot-wrap.no-svg {
display: none;
padding-bottom: 1rem; }
.mx-dv-container .plot-wrap.no-svg img {
width: 100%;
max-width: 706px; }
.mx-dv-container .svg-container {
position: relative; }
.mx-dv-container .svg-container canvas {
display: block;
height: 100%;
visibility: hidden; }
.mx-dv-container .svg-container svg {
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%; }
.tooltip {
top: -1000px;
position: fixed;
padding: .65rem;
pointer-events: none;
max-width: 10%;
z-index: 500;
opacity: .9; }
.tooltip-info {
font-family: 'Raleway', sans-serif;
font-weight:400;
color: #45555f;
padding: 0;
margin: 0;
display: block;
width: 100%; }
.tooltip-info span {
font-family: 'Raleway', sans-serif;
font-weight:700;
}
.tooltip-hidden {
opacity: 0;
transition: all .3s;
transition-delay: .1s; }
.tooltip.small {
padding: .25rem; }
.tooltip.small .tooltip-info {
font-size: .85rem; }
@media only screen and (max-width: 40em) {
.mx-dv-container .plot-wrap.no-svg {
display: block; }
.mx-dv-container .plot-wrap.w-svg {
display: none; } }
Genre # of Purchases % of Purchases Total Sales
Afro-Cuban jazz 87 3% 174684683
Asian American jazz 88 75% 137743702
Avant-garde jazz 13 23% 619994198
Bebop 77 53% 750121661
Bossa nova 76 66% 332979326
British dance band 32 48% 582600862
Cape jazz 14 17% 345163980
Chamber jazz 78 59% 250204738
Continental jazz 69 3% 672517151
Cool jazz 95 65% 140426355
Crossover jazz 1 44% 609292260
Dark jazz 86 10% 386447607
Dixieland 49 47% 255288253
Ethno jazz 51 48% 775112221
European free jazz 46 9% 366884291
Jazz rap 95 55% 646051127
Jazz rock 98 43% 457095597
Kansas City blues 61 13% 417192918
Kansas City jazz 90 2% 730893189
Latin jazz 9 9% 646205607
M-Base 77 76% 550498534
Mainstream jazz 37 68% 524713442
Modal jazz 9 35% 639550549
Neo-bop jazz 84 56% 212152907
Neo-swing 45 62% 542559060
Novelty ragtime 86 6% 653895795
Nu jazz 88 74% 698836166
Orchestral jazz 54 42% 733885545
Post-bop 99 80% 414970130
Punk jazz 34 30% 556478090
Ragtime 45 46% 615029635
Ska jazz 38 77% 497266205
Smooth jazz 62 19% 755215104
Soul jazz 99 39% 165600927
Straight-ahead 3 63% 417700887
Stride jazz 59 6% 760375467
Swing 92 73% 424907081
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Radial Barchart | Seashell Chart</title>
<link href="https://fonts.googleapis.com/css?family=Raleway:300,400,700,900i" rel="stylesheet">
<link rel="stylesheet" href="app.css">
</head>
<body>
<div class="global-chart-container">
<section style="width:100%; display: inline-block;">
<div class="mx-dv-container" id="radialBarChartBlocks">
<form class="chart-ui-container">
<div class="chart-form__radio stacked">
<input class="chart-form__radio-input" type="radio" name="dataset" value="Total Sales" checked><label class="chart-form__radio-label">Total Sales for Jazz Genre</label>
</div>
<div class="chart-form__radio stacked">
<input class="chart-form__radio-input" type="radio" name="dataset" value="% of Purchases"><label class="chart-form__radio-label">Percent of Purchases</label>
</div>
</form>
<div class="plot-wrap w-svg"></div>
<i>* This data is randomly generated</i>
</div>
</section>
</div>
<script src="radial-barchart.js"></script>
<script src="radial-barchart-init.js"></script>
<script src="https://rawgit.com/timproDev/d3-first-timer/master/js/d3v4-473-jetpack.js"></script>
</body>
</html>
document.addEventListener("DOMContentLoaded", function(){
// set global variables
var path = '';
var dataFile = 'data-music.csv';
var elemContainer = ['#radialBarChartBlocks'];
var uiEl = '.chart-ui-container input';
var cssFile = 'radial-barchart.css';
// instantiate constructor methods
var radialBarChart = new RadialBarChart({
elem:document.querySelector(elemContainer[0]),
transSpeed:350
});
// fire chart and events
// chartInit(instance, element, dataPath)
chartInit(radialBarChart, elemContainer[0], dataFile);
styleLink();
function chartInit(chartInst, elem, dataFile) {
d3.csv((path + dataFile), function(data) {
data.forEach(function(d) {
d['Total Sales'] = +d['Total Sales'];
d['% of Purchases'] = d['% of Purchases'].replace('%','');
d['% of Purchases'] = +d['% of Purchases'];
});
data.sort(function(a,b){
return d3.ascending(a['Total Sales'], b['Total Sales'])
});
console.log("init data:",data);
chartInst.setData(data);
// ui events
var inputs = document.querySelectorAll(elem + ' ' + uiEl);
for (var i = 0; i < inputs.length; i++) {
inputs[i].addEventListener('click', function(){
var value = this.value;
data.sort(function(a,b){
return d3.ascending(a[value], b[value])
});
chartInst.setDataPoint(value);
});
}
});
}
// include resize event
// inject custom chart css
function styleLink(){
var linkElem = document.createElement('link');
document.getElementsByTagName('head')[0].appendChild(linkElem);
linkElem.rel = 'stylesheet';
linkElem.type = 'text/css';
linkElem.href = (path + cssFile);
}
// include resize event
d3.select(window).on('resize', function(){
radialBarChart.resize();
});
});
var RadialBarChart = function(opts){
this.transSpeed = opts.transSpeed;
this.colorScheme = d3.schemeCategory20b || this.colorScheme; // set up customize
this.element = opts.elem.querySelector('.plot-wrap.w-svg');
this.colorSetOne = '#8fbc8f';
this.colorSetOneActive = '#3b8e67';
this.colorSetTwo = '#f766c1';
this.colorSetTwoActive = '#c71585';
this.colorInactive ='#f2f2f2';
this.colorText = '#505050';
this.dataPointOne = 'Total Sales';
this.dataPointTwo = '% of Purchases';
this.dataPoint = this.dataPoint || this.dataPointOne;
}
RadialBarChart.prototype.setData = function(newData){
this.data = newData;
this.draw();
}
RadialBarChart.prototype.setDataPoint = function(newData){
this.dataPoint = newData;
this.draw();
}
RadialBarChart.prototype.draw = function(){
var self = this
this.margin = {
top:20,
left:20,
right:20,
bottom:20
};
this.width = this.element.offsetWidth - this.margin.left - this.margin.right;
this.height = this.width / 1.5 - (this.margin.top - this.margin.bottom);
this.element.innerHTML = '';
var svg = d3.select(this.element).append('svg');
svg.attr('width', this.width + (this.margin.left + this.margin.right));
svg.attr('height', (this.height + this.margin.top + this.margin.bottom));
// position
this.plot = svg.append('g')
.attr("transform", "translate(" + this.width / 2 + "," + this.height / 1.6 + ")");
this.formatVal = d3.format(",");
this.createScales()
this.createRBC()
this.createLabels()
this.addMouseEvent()
}
RadialBarChart.prototype.createScales = function() {
var self = this;
this.centerLabel = this.plot
.append('text')
.text(self.dataPoint)
.attr('text-anchor','middle')
.attr('x',0)
.attr('y',10)
.classed('center-label', true)
.call(this.wrapText, 180);
this.centerValue = this.plot
.append('text')
.classed('center-label-value-null', true)
.attr('fill',this.colorInactive)
.text(function(d){
return (self.dataPoint === self.dataPointOne) ? ('$000,000') : ('00%');
})
.attr('text-anchor','middle')
.attr('dy','-8px');
this.innerRadius = 90;
this.outerRadius = (Math.min(this.width, this.height) / 3);
this.xScaleOffset = Math.PI * 97/180;
this.x = d3.scaleBand()
.range([this.xScaleOffset, 2 * Math.PI + this.xScaleOffset])
.align(0);
this.y = d3.scaleLinear()
.range([this.innerRadius, this.outerRadius]);
this.x.domain(this.data.map(function(d) { return d.Genre; }));
// adjust domain based on input
if (self.dataPoint === self.dataPointOne) {
this.y.domain([0, d3.max(this.data, function(d) { return d[self.dataPoint]; })]);
} else if (self.dataPoint === self.dataPointTwo) {
// this.y.domain([0, 80]);
this.y.domain([0, 60]);
}
this.yAxis = this.plot.append("g")
.attr("text-anchor", "middle");
}
RadialBarChart.prototype.createRBC = function() {
var self = this
this.radArc = d3.arc()
.innerRadius(function(d) { return self.y(0); })
.outerRadius(function(d) { return self.y(d[self.dataPoint]); })
.startAngle(function(d) { return self.x(d.Genre); })
.endAngle(function(d) { return self.x(d.Genre) + self.x.bandwidth(); })
.padAngle(0.025)
.padRadius(this.innerRadius);
this.hoverCircle = this.plot
.append('circle')
.attr('fill','transparent')
.attr('cx',0)
.attr('cy',0)
.attr('r',this.outerRadius);
this.barGroup = this.plot.append('g')
.selectAll("path")
.data(this.data)
.enter()
.append("g");
this.barGroup
.append('path')
.classed('radial-bar',true)
.attr("fill", function(d){
return (self.dataPoint === self.dataPointOne) ? self.colorSetOne : self.colorSetTwo;
})
.attr("d", this.radArc);
}
RadialBarChart.prototype.createLabels = function(){
var self = this
this.label = this.plot.append("g")
.selectAll("g")
.data(this.data)
.enter()
.append("g")
.attr("text-anchor", "start")
.attr("transform", function(d) {return "rotate(" + ((self.x(d.Genre) + self.x.bandwidth() / 2) * 180 / Math.PI - 90) + ")translate(" + ((self.y(d[self.dataPoint]) + self.x.bandwidth() / 2) + 16) + ",0)"; });
this.label.append("circle.circle-label")
.attr("cx", function(d) { return (((d.Genre % 5) == 0) | (d.Genre == '1')) ? -14 : -8 })
.attr('cy',0)
.attr('r',2)
.attr("fill", this.colorInactive);
this.label.append("text")
.classed('barLabelLight',true)
.attr('fill',this.colorText)
.attr("transform", function(d) {
if (((self.x(d.Genre) + self.x.bandwidth() / 2 + Math.PI / 2) % (2 * Math.PI) < Math.PI )) {
return "rotate(0)translate(0,5)";
} else {
return "rotate(0)translate(0,4)";
}
})
.text(function(d) {
return d.Genre; })
.attr('dy',function(d){
if(d.Genre === "Financial Institutions" && self.dataPoint === self.dataPointOne){
// return '12px';
return '1px';
} else if(d.Genre === "Financial Institutions" && self.dataPoint !== self.dataPointOne){
// return '5px';
return '0px';
}
})
.style('font-size', function(d){
return (self.y(d[self.dataPoint]) / 10) + "px";
});
// data card
this.dataCardDiv = d3.select(this.element)
.attr("style","position:relative;")
.append('div.data-card');
}
RadialBarChart.prototype.wrapText = function(text,width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0, //<-- 0!
lineHeight = 1, // ems
x = text.attr("x"), //<-- include the x!
y = text.attr("y"),
dy = text.attr("dy") ? text.attr("dy") : 0; //<-- null check
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
RadialBarChart.prototype.addMouseEvent = function () {
var self = this
this.plot.selectAll('path.radial-bar')
.on('mouseenter', function(d){
var thisData = d;
d3.selectAll('.barLabelLight').attr('opacity',function(d){return (d === thisData) ? "1" : ".15";});
d3.select(this).attr('fill', self.colorSetOneActive);
// d3.selectAll('path.radial-bar').attr('fill', function(d){return (d === thisData) ? self.colorSetOneActive : self.colorSetOne;});
d3.selectAll('path.radial-bar').attr('fill', function(d){
if(self.dataPoint === self.dataPointOne) {
if(d === thisData) {
return self.colorSetOneActive;
} else {
return self.colorSetOne;
}
} else if(self.dataPoint === self.dataPointTwo) {
if(d === thisData) {
return self.colorSetTwoActive;
} else {
return self.colorSetTwo;
}
}
});
d3.selectAll('circle.circle-label').attr('fill', function(d){
if(self.dataPoint === self.dataPointOne) {
if(d === thisData) {
return self.colorSetOneActive;
} else {
return self.colorInactive;
}
} else if(self.dataPoint === self.dataPointTwo) {
if(d === thisData) {
return self.colorSetTwoActive;
} else {
return self.colorInactive;
}
}
});
d3.selectAll('text.center-label-value-null')
.text(function(){
return (self.dataPoint === self.dataPointOne) ? ('$' + self.formatVal(d[self.dataPoint])) : (d[self.dataPoint] + '%');
})
.attr('fill',function(d){
return (self.dataPoint === self.dataPointOne) ? self.colorSetOneActive : self.colorSetTwoActive;
});
d3.selectAll('.data-card')
.html('<h5><span>Genre:</span> ' + d.Genre + '</h5>');
d3.selectAll('.data-card h5')
.style('color', function(){
return (self.dataPoint === self.dataPointOne) ? self.colorSetOneActive : self.colorSetTwoActive;
});
})
.on('mouseleave', function(d){});
this.plot.selectAll('text.barLabelLight')
.on('mouseenter', function(d){
var thisData = d;
d3.selectAll('.barLabelLight').attr('opacity',function(d){
return (d === thisData) ? "1" : ".15";
});
d3.selectAll('path.radial-bar').attr('fill', function(d){
if(self.dataPoint === self.dataPointOne) {
if(d === thisData) {
return self.colorSetOneActive;
} else {
return self.colorSetOne;
}
} else if(self.dataPoint === self.dataPointTwo) {
if(d === thisData) {
return self.colorSetTwoActive;
} else {
return self.colorSetTwo;
}
}
});
d3.selectAll('circle.circle-label').attr('fill', function(d){
if(self.dataPoint === self.dataPointOne) {
if(d === thisData) {
return self.colorSetOneActive;
} else {
return self.colorInactive;
}
} else if(self.dataPoint === self.dataPointTwo) {
if(d === thisData) {
return self.colorSetTwoActive;
} else {
return self.colorInactive;
}
}
});
d3.selectAll('text.center-label-value-null')
.text(function(){
return (self.dataPoint === self.dataPointOne) ? ('$' + self.formatVal(d[self.dataPoint])) : (d[self.dataPoint] + '%');
})
.attr('fill',function(d){
return (self.dataPoint === self.dataPointOne) ? self.colorSetOneActive : self.colorSetTwoActive;
});
d3.selectAll('.data-card')
.html('<h5><span>Genre:</span> ' + d.Genre + '</h5>');
d3.selectAll('.data-card h5')
.style('color', function(){
return (self.dataPoint === self.dataPointOne) ? self.colorSetOneActive : self.colorSetTwoActive;
});
})
.on('mouseleave', function(d){});
this.hoverCircle.on('mouseleave', function(){
d3.selectAll('.barLabelLight').attr('opacity','1');
d3.selectAll('path.radial-bar').attr("fill", function(d){
return (self.dataPoint === self.dataPointOne) ? self.colorSetOne : self.colorSetTwo;
});
d3.selectAll('circle.circle-label').attr('fill', self.colorInactive);
d3.select('.center-label-value-null')
.text(function(d){
return (self.dataPoint === self.dataPointOne) ? ('$000,000,000') : ('00%');
})
.attr('fill',self.colorInactive);
d3.selectAll('.data-card').html('');
});
}
RadialBarChart.prototype.resize = function(){
this.width = parseInt(d3.select(this.element).style('width'), 10);
this.draw();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment