Skip to content

Instantly share code, notes, and snippets.

@eesur
Last active June 27, 2017 17:11
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save eesur/7b63401950344f2dca1025d09cc7386d to your computer and use it in GitHub Desktop.
d3 | reusable circle legend
function circleLegend(selection) {
var instance = {}
// set some defaults
var api = {
domain: [0, 100], // the values min and max
range: [0, 80], // the circle area/size mapping
values: [8, 34, 89], // values for circles
width: 500,
height: 500,
suffix:'', // ability to pass in a suffix
circleColor: '#888',
textPadding: 40,
textColor: '#454545'
}
var sqrtScale = d3.scaleSqrt()
.domain(api.domain)
.range(api.range)
instance.render = function () {
var s = selection.append('g')
.attr('class', 'legend-wrap')
// push down to radius of largest circle
.attr('transform', 'translate(0,' + sqrtScale(d3.max(api.values)) + ')')
// append the values for circles
s.append('g')
.attr('class', 'values-wrap')
.selectAll('circle')
.data(api.values)
.enter().append('circle')
.attr('class', function (d) { return 'values values-' + d; })
.attr('r', function (d) { return sqrtScale(d); })
.attr('cx', api.width/2)
.attr('cy', function (d) { return api.height/2 - sqrtScale(d); })
.style('fill', 'none')
.style('stroke', api.circleColor)
.style('opacity', 0.5)
// append some lines based on values
s.append('g')
.attr('class', 'values-line-wrap')
.selectAll('.values-labels')
.data(api.values)
.enter().append('line')
.attr('x1', function (d) { return api.width/2 + sqrtScale(d); })
.attr('x2', api.width/2 + sqrtScale(api.domain[1]) + 10)
.attr('y1', function (d) { return api.height/2 - sqrtScale(d); })
.attr('y2', function (d) { return api.height/2 - sqrtScale(d); })
.style('stroke', api.textColor)
.style('stroke-dasharray', ('2,2'))
// append some labels from values
s.append('g')
.attr('class', 'values-labels-wrap')
.selectAll('.values-labels')
.data(api.values)
.enter().append('text')
.attr('x', api.width/2 + sqrtScale(api.domain[1]) + api.textPadding)
.attr('y', function (d) { return (api.height/2 - sqrtScale(d)) + 5; })
.attr('shape-rendering', 'crispEdges')
.style('text-anchor', 'end')
.style('fill', api.textColor)
.text(function (d) { return d + api.suffix; })
return instance
}
for (var key in api) {
instance[key] = getSet(key, instance).bind(api)
}
return instance
// https://gist.github.com/gneatgeek/5892586
function getSet(option, component) {
return function (_) {
if (! arguments.length) {
return this[option];
}
this[option] = _;
return component;
}
}
}

needed a simple circle legend for a bubble chart that I could pass in values and a domain

to initiate pass in a d3 selection to the circleLegend function and your dataset domain and values:

    var l = circleLegend(d3.select('svg g#legend'))
        .domain([0, 100])) // the dataset min and max
        .range( [0, 80]) // the circle area/size mapping
        .values( [8, 34, 89]) // pass in values (e.g. min,mean/median & max)
        // optional
        .width(500) // it centers to this
        .height( 500) // it centers to this
        .suffix('') // ability to pass in a suffix e.g. '%'
        .circleColor( '#888') // stroke of the circles
        .textPadding(40) // left padding on text
        .textColor( '#454545') // the fill for text

    // and render it
    l.render()

note: the number of values you pass in will correlate to the number of circles

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
body { font-family: Consolas, monaco, monospace; background: #f43530;}
circle {
stroke-width: 3px;
}
</style>
</head>
<body>
<svg width="960" height="500">
<g id="vis" transform="translate(10, 10)"></g>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<!-- d3 code -->
<script src=".script-compiled.js" charset="utf-8"></script>
<!-- render code -->
<script>
circleLegend(d3.select('svg g#vis'))
.width(960)
.height(500)
.circleColor('#46454b')
.textColor('#e0e5da')
.suffix('%')
.textPadding(45)
.render()
</script>
</body>
</html>
function circleLegend(selection) {
let instance = {}
// set some defaults
const api = {
domain: [0, 100], // the values min and max
range: [0, 80], // the circle area/size mapping
values: [8, 34, 89], // values for circles
width: 500,
height: 500,
suffix:'', // ability to pass in a suffix
circleColor: '#888',
textPadding: 40,
textColor: '#454545'
}
const sqrtScale = d3.scaleSqrt()
.domain(api.domain)
.range(api.range)
instance.render = function () {
const s = selection.append('g')
.attr('class', 'legend-wrap')
// push down to radius of largest circle
.attr('transform', 'translate(0,' + sqrtScale(d3.max(api.values)) + ')')
// append the values for circles
s.append('g')
.attr('class', 'values-wrap')
.selectAll('circle')
.data(api.values)
.enter().append('circle')
.attr('class', d => 'values values-' + d)
.attr('r', d => sqrtScale(d))
.attr('cx', api.width/2)
.attr('cy', d => api.height/2 - sqrtScale(d))
.style('fill', 'none')
.style('stroke', api.circleColor)
.style('opacity', 0.5)
// append some lines based on values
s.append('g')
.attr('class', 'values-line-wrap')
.selectAll('.values-labels')
.data(api.values)
.enter().append('line')
.attr('x1', d => api.width/2 + sqrtScale(d))
.attr('x2', api.width/2 + sqrtScale(api.domain[1]) + 10)
.attr('y1', d => api.height/2 - sqrtScale(d))
.attr('y2', d => api.height/2 - sqrtScale(d))
.style('stroke', api.textColor)
.style('stroke-dasharray', ('2,2'))
// append some labels from values
s.append('g')
.attr('class', 'values-labels-wrap')
.selectAll('.values-labels')
.data(api.values)
.enter().append('text')
.attr('x', api.width/2 + sqrtScale(api.domain[1]) + api.textPadding)
.attr('y', d => (api.height/2 - sqrtScale(d)) + 5)
.attr('shape-rendering', 'crispEdges')
.style('text-anchor', 'end')
.style('fill', api.textColor)
.text(d => d + api.suffix)
return instance
}
for (let key in api) {
instance[key] = getSet(key, instance).bind(api)
}
return instance
// https://gist.github.com/gneatgeek/5892586
function getSet(option, component) {
return function (_) {
if (! arguments.length) {
return this[option];
}
this[option] = _;
return component;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment