Skip to content

Instantly share code, notes, and snippets.

@eesur
Last active February 27, 2018 09:23
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 eesur/8ba5c66700589874ef5a0920481b36fb to your computer and use it in GitHub Desktop.
Save eesur/8ba5c66700589874ef5a0920481b36fb to your computer and use it in GitHub Desktop.
d3js | quadrant with circles
license: mit
height: 500
border: no

sketch of quadrant with circles showing values, adapted from proportional circle area chart

domain, labels and colours (including background rect) can be changed just by passing in overrides via the config

*{box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Consolas,monaco,monospace}
!function(c){function n(e){if(g[e])return g[e].exports;var t=g[e]={i:e,l:!1,exports:{}};return c[e].call(t.exports,t,t.exports,n),t.l=!0,t.exports}var g={};n.m=c,n.c=g,n.i=function(c){return c},n.d=function(c,g,e){n.o(c,g)||Object.defineProperty(c,g,{configurable:!1,enumerable:!0,get:e})},n.n=function(c){var g=c&&c.__esModule?function(){return c.default}:function(){return c};return n.d(g,"a",g),g},n.o=function(c,n){return Object.prototype.hasOwnProperty.call(c,n)},n.p="",n(n.s=0)}([function(module,exports,__webpack_require__){"use strict";eval("\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar d3 = window.d3;\n\n// selection passed in is a d3 selection\nd3.areaCircle = function (selection, value, config) {\n config = _extends({\n width: 300,\n height: 300,\n axis: [25, 50, 75, 100],\n domain: [0, 100],\n title: '',\n bgRectColor: '#eee',\n bgGridColor: '#00b8b8',\n circleColor: '#de3d83',\n circleTextColor: '#e4bd0b'\n }, config);\n\n console.log('config', config);\n\n var _config = config,\n width = _config.width,\n height = _config.height,\n axis = _config.axis,\n domain = _config.domain;\n\n\n var distance = width > height ? height : width;\n\n var sqrtScale = d3.scaleSqrt().domain(domain).range([0, distance / 3]);\n\n if (selection.select('.area-circle').empty()) {\n // append background rect\n selection.append('g').attr('class', 'bg-rect').append('rect').attr('width', width).attr('height', height).style('fill', config.bgRectColor);\n // append titles\n selection.append('g').attr('class', 'bg-title').append('text').attr('x', 20).attr('y', 40).style('font-size', '24px').style('fill', '#454545').text(config.title);\n // append the axis circles\n selection.append('g').attr('class', 'axis-wrap').selectAll('circle').data(axis).enter().append('circle').attr('class', function (d) {\n return 'axis axis-' + d;\n }).attr('r', function (d) {\n return sqrtScale(d);\n }).attr('cx', width / 2).attr('cy', height / 2).style('fill', 'none').style('stroke', config.bgGridColor).style('opacity', 0.5);\n\n // append some axis labels\n selection.append('g').attr('class', 'axis-labels-wrap').selectAll('.axis-labels').data(axis).enter().append('text').attr('x', width / 2).attr('y', function (d) {\n return height / 2 + sqrtScale(d) + 5;\n }).style('text-anchor', 'end').style('fill', config.bgGridColor).text(function (d) {\n return d + '%';\n });\n\n // append a circle for the value\n selection.append('circle').attr('class', 'area-circle').attr('cx', width / 2).attr('cy', height / 2).style('fill', config.circleColor).style('opacity', 0.9);\n // append it's value\n selection.append('text').attr('class', 'area-label').attr('x', width / 2).attr('y', height / 2 + 12).style('text-anchor', 'middle').style('font-size', '48px').style('fill', config.circleTextColor);\n }\n // update\n selection.select('.area-circle').transition().ease(d3.easeLinear).duration(500).attr('r', function (d) {\n return sqrtScale(value);\n });\n selection.select('.area-label').transition().delay(100).duration(500).text(value + '%');\n};//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9zY3JpcHQuanM/OWE5NSJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBkMyA9IHdpbmRvdy5kM1xuXG4vLyBzZWxlY3Rpb24gcGFzc2VkIGluIGlzIGEgZDMgc2VsZWN0aW9uXG5kMy5hcmVhQ2lyY2xlID0gZnVuY3Rpb24gKHNlbGVjdGlvbiwgdmFsdWUsIGNvbmZpZykge1xuICBjb25maWcgPSB7XG4gICAgd2lkdGg6IDMwMCxcbiAgICBoZWlnaHQ6IDMwMCxcbiAgICBheGlzOiBbMjUsIDUwLCA3NSwgMTAwXSxcbiAgICBkb21haW46IFswLCAxMDBdLFxuICAgIHRpdGxlOiAnJyxcbiAgICBiZ1JlY3RDb2xvcjogJyNlZWUnLFxuICAgIGJnR3JpZENvbG9yOiAnIzAwYjhiOCcsXG4gICAgY2lyY2xlQ29sb3I6ICcjZGUzZDgzJyxcbiAgICBjaXJjbGVUZXh0Q29sb3I6ICcjZTRiZDBiJyxcbiAgICAuLi5jb25maWdcbiAgfVxuXG4gIGNvbnNvbGUubG9nKCdjb25maWcnLCBjb25maWcpXG5cbiAgY29uc3QgeyB3aWR0aCwgaGVpZ2h0LCBheGlzLCBkb21haW4gfSA9IGNvbmZpZ1xuXG4gIGNvbnN0IGRpc3RhbmNlID0gKHdpZHRoID4gaGVpZ2h0KSA/IGhlaWdodCA6IHdpZHRoXG5cbiAgY29uc3Qgc3FydFNjYWxlID0gZDMuc2NhbGVTcXJ0KClcbiAgICAuZG9tYWluKGRvbWFpbilcbiAgICAucmFuZ2UoWzAsIGRpc3RhbmNlIC8gM10pXG5cbiAgaWYgKHNlbGVjdGlvbi5zZWxlY3QoJy5hcmVhLWNpcmNsZScpLmVtcHR5KCkpIHtcbiAgICAvLyBhcHBlbmQgYmFja2dyb3VuZCByZWN0XG4gICAgc2VsZWN0aW9uLmFwcGVuZCgnZycpXG4gICAgICAuYXR0cignY2xhc3MnLCAnYmctcmVjdCcpXG4gICAgICAuYXBwZW5kKCdyZWN0JylcbiAgICAgIC5hdHRyKCd3aWR0aCcsIHdpZHRoKVxuICAgICAgLmF0dHIoJ2hlaWdodCcsIGhlaWdodClcbiAgICAgIC5zdHlsZSgnZmlsbCcsIGNvbmZpZy5iZ1JlY3RDb2xvcilcbiAgICAvLyBhcHBlbmQgdGl0bGVzXG4gICAgc2VsZWN0aW9uLmFwcGVuZCgnZycpXG4gICAgICAuYXR0cignY2xhc3MnLCAnYmctdGl0bGUnKVxuICAgICAgLmFwcGVuZCgndGV4dCcpXG4gICAgICAuYXR0cigneCcsIDIwKVxuICAgICAgLmF0dHIoJ3knLCA0MClcbiAgICAgIC5zdHlsZSgnZm9udC1zaXplJywgJzI0cHgnKVxuICAgICAgLnN0eWxlKCdmaWxsJywgJyM0NTQ1NDUnKVxuICAgICAgLnRleHQoY29uZmlnLnRpdGxlKVxuICAgIC8vIGFwcGVuZCB0aGUgYXhpcyBjaXJjbGVzXG4gICAgc2VsZWN0aW9uLmFwcGVuZCgnZycpXG4gICAgICAuYXR0cignY2xhc3MnLCAnYXhpcy13cmFwJylcbiAgICAgIC5zZWxlY3RBbGwoJ2NpcmNsZScpXG4gICAgICAuZGF0YShheGlzKVxuICAgICAgLmVudGVyKCkuYXBwZW5kKCdjaXJjbGUnKVxuICAgICAgLmF0dHIoJ2NsYXNzJywgZCA9PiAnYXhpcyBheGlzLScgKyBkKVxuICAgICAgLmF0dHIoJ3InLCBkID0+IHNxcnRTY2FsZShkKSlcbiAgICAgIC5hdHRyKCdjeCcsIHdpZHRoIC8gMilcbiAgICAgIC5hdHRyKCdjeScsIGhlaWdodCAvIDIpXG4gICAgICAuc3R5bGUoJ2ZpbGwnLCAnbm9uZScpXG4gICAgICAuc3R5bGUoJ3N0cm9rZScsIGNvbmZpZy5iZ0dyaWRDb2xvcilcbiAgICAgIC5zdHlsZSgnb3BhY2l0eScsIDAuNSlcblxuICAgIC8vIGFwcGVuZCBzb21lIGF4aXMgbGFiZWxzXG4gICAgc2VsZWN0aW9uLmFwcGVuZCgnZycpXG4gICAgICAuYXR0cignY2xhc3MnLCAnYXhpcy1sYWJlbHMtd3JhcCcpXG4gICAgICAuc2VsZWN0QWxsKCcuYXhpcy1sYWJlbHMnKVxuICAgICAgLmRhdGEoYXhpcylcbiAgICAgIC5lbnRlcigpLmFwcGVuZCgndGV4dCcpXG4gICAgICAuYXR0cigneCcsIHdpZHRoIC8gMilcbiAgICAgIC5hdHRyKCd5JywgZCA9PiBoZWlnaHQgLyAyICsgc3FydFNjYWxlKGQpICsgNSlcbiAgICAgIC5zdHlsZSgndGV4dC1hbmNob3InLCAnZW5kJylcbiAgICAgIC5zdHlsZSgnZmlsbCcsIGNvbmZpZy5iZ0dyaWRDb2xvcilcbiAgICAgIC50ZXh0KGQgPT4gZCArICclJylcblxuICAgIC8vIGFwcGVuZCBhIGNpcmNsZSBmb3IgdGhlIHZhbHVlXG4gICAgc2VsZWN0aW9uLmFwcGVuZCgnY2lyY2xlJylcbiAgICAgIC5hdHRyKCdjbGFzcycsICdhcmVhLWNpcmNsZScpXG4gICAgICAuYXR0cignY3gnLCB3aWR0aCAvIDIpXG4gICAgICAuYXR0cignY3knLCBoZWlnaHQgLyAyKVxuICAgICAgLnN0eWxlKCdmaWxsJywgY29uZmlnLmNpcmNsZUNvbG9yKVxuICAgICAgLnN0eWxlKCdvcGFjaXR5JywgMC45KVxuICAgIC8vIGFwcGVuZCBpdCdzIHZhbHVlXG4gICAgc2VsZWN0aW9uLmFwcGVuZCgndGV4dCcpXG4gICAgICAuYXR0cignY2xhc3MnLCAnYXJlYS1sYWJlbCcpXG4gICAgICAuYXR0cigneCcsIHdpZHRoIC8gMilcbiAgICAgIC5hdHRyKCd5JywgaGVpZ2h0IC8gMiArIDEyKVxuICAgICAgLnN0eWxlKCd0ZXh0LWFuY2hvcicsICdtaWRkbGUnKVxuICAgICAgLnN0eWxlKCdmb250LXNpemUnLCAnNDhweCcpXG4gICAgICAuc3R5bGUoJ2ZpbGwnLCBjb25maWcuY2lyY2xlVGV4dENvbG9yKVxuICB9XG4gIC8vIHVwZGF0ZVxuICBzZWxlY3Rpb24uc2VsZWN0KCcuYXJlYS1jaXJjbGUnKS50cmFuc2l0aW9uKClcbiAgICAuZWFzZShkMy5lYXNlTGluZWFyKVxuICAgIC5kdXJhdGlvbig1MDApXG4gICAgLmF0dHIoJ3InLCBkID0+IHNxcnRTY2FsZSh2YWx1ZSkpXG4gIHNlbGVjdGlvbi5zZWxlY3QoJy5hcmVhLWxhYmVsJykudHJhbnNpdGlvbigpXG4gICAgLmRlbGF5KDEwMClcbiAgICAuZHVyYXRpb24oNTAwKVxuICAgIC50ZXh0KHZhbHVlICsgJyUnKVxufVxuXG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIHNjcmlwdC5qcyJdLCJtYXBwaW5ncyI6Ijs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFUQTtBQUNBO0FBWUE7QUFDQTtBQWZBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUNBO0FBZ0JBO0FBQ0E7QUFDQTtBQUNBO0FBR0E7QUFDQTtBQUNBO0FBTUE7QUFDQTtBQVFBO0FBQ0E7QUFLQTtBQUFBO0FBQ0E7QUFBQTtBQUNBO0FBTUE7QUFDQTtBQU1BO0FBQUE7QUFHQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBTUE7QUFDQTtBQU9BO0FBQ0E7QUFDQTtBQUdBO0FBQUE7QUFDQTtBQUlBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///0\n")}]);
<!DOCTYPE html>
<title>blockup</title>
<link href="https://unpkg.com/basscss@8.0.2/css/basscss.min.css" rel="stylesheet">
<link href='dist.css' rel='stylesheet' />
<body>
<div class="flex flex-wrap content-start mx-auto mt2" style="min-height: 600px; width:610px">
<div class="col-6 q-1">
<svg width="300" height="300"><g id="q-1-g"></g></svg>
</div>
<div class="col-6 q-2">
<svg width="300" height="300"><g id="q-2-g"></g></svg>
</div>
<div class="col-6 q-3">
<svg width="300" height="300"><g id="q-3-g"></g></svg>
</div>
<div class="col-6 q-4">
<svg width="300" height="300"><g id="q-4-g"></g></svg>
</div>
</div>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='dist.js'></script>
<script>
(function () {
const d3 = window.d3
const config = {
axis: [60, 80, 100],
domain: [50, 100]
}
// render q1
d3.areaCircle(d3.select('svg #q-1-g'), 76, {
title: 'A',
circleColor: '#de3d83',
...config
})
// render q1
d3.areaCircle(d3.select('svg #q-2-g'), 82, {
title: 'B',
circleColor: '#00b8b8',
...config
})
// render q3
d3.areaCircle(d3.select('svg #q-3-g'), 90, {
title: 'D',
circleColor: '#de3d83',
...config
})
// render q4
d3.areaCircle(d3.select('svg #q-4-g'), 94, {
title: 'C',
circleColor: '#00b8b8',
...config
})
// change frame height for bl.ocks
d3.select(self.frameElement).style('height', '620px')
})()
</script>
</body>
const d3 = window.d3
// selection passed in is a d3 selection
d3.areaCircle = function (selection, value, config) {
config = {
width: 300,
height: 300,
axis: [25, 50, 75, 100],
domain: [0, 100],
title: '',
bgRectColor: '#eee',
bgGridColor: '#00b8b8',
circleColor: '#de3d83',
circleTextColor: '#e4bd0b',
...config
}
console.log('config', config)
const { width, height, axis, domain } = config
const distance = (width > height) ? height : width
const sqrtScale = d3.scaleSqrt()
.domain(domain)
.range([0, distance / 3])
if (selection.select('.area-circle').empty()) {
// append background rect
selection.append('g')
.attr('class', 'bg-rect')
.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', config.bgRectColor)
// append titles
selection.append('g')
.attr('class', 'bg-title')
.append('text')
.attr('x', 20)
.attr('y', 40)
.style('font-size', '24px')
.style('fill', '#454545')
.text(config.title)
// append the axis circles
selection.append('g')
.attr('class', 'axis-wrap')
.selectAll('circle')
.data(axis)
.enter().append('circle')
.attr('class', d => 'axis axis-' + d)
.attr('r', d => sqrtScale(d))
.attr('cx', width / 2)
.attr('cy', height / 2)
.style('fill', 'none')
.style('stroke', config.bgGridColor)
.style('opacity', 0.5)
// append some axis labels
selection.append('g')
.attr('class', 'axis-labels-wrap')
.selectAll('.axis-labels')
.data(axis)
.enter().append('text')
.attr('x', width / 2)
.attr('y', d => height / 2 + sqrtScale(d) + 5)
.style('text-anchor', 'end')
.style('fill', config.bgGridColor)
.text(d => d + '%')
// append a circle for the value
selection.append('circle')
.attr('class', 'area-circle')
.attr('cx', width / 2)
.attr('cy', height / 2)
.style('fill', config.circleColor)
.style('opacity', 0.9)
// append it's value
selection.append('text')
.attr('class', 'area-label')
.attr('x', width / 2)
.attr('y', height / 2 + 12)
.style('text-anchor', 'middle')
.style('font-size', '48px')
.style('fill', config.circleTextColor)
}
// update
selection.select('.area-circle').transition()
.ease(d3.easeLinear)
.duration(500)
.attr('r', d => sqrtScale(value))
selection.select('.area-label').transition()
.delay(100)
.duration(500)
.text(value + '%')
}
*
box-sizing border-box
body
font-family: -apple-system, BlinkMacSystemFont, Consolas, monaco, monospace
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment