Concentric Circles Chart main source: ··· Home

Notes:

  • Data of baby names in New York 2012
  • Custom color scale
  • Added box shadow filter

Code:

  • concentric-circles.ejs

    <%  demo = {category: "d3js", key: "concentric-circles", files: files}; %>
    
    <%- partial("../mixins/_demo_title", demo) %>
    
    <div id="chart" class="<%= demo.key %>-chart"></div>
    
    <script type="text/javascript" src="/vendors/bower/d3/d3.min.js"></script>
    <script type="text/javascript" src="/js/d3js-utils.js"></script>
    <script type="text/javascript" src="/js/d3js/<%= demo.key %>.js"></script>
    
    <%- partial("../mixins/_notes", demo) %>
    <%- partial("../mixins/_sources_data", demo) %>
    <%- partial("../mixins/_code", demo) %>
  • concentric-circles.coffee

    ready = ->
      d3.csv('/data/d3js/concentric-circles/data.csv', (data)->
        colours = ['#7C7CC9','#52D552','#337233','#323247']
    
        c = d3.scale.linear().domain(d3.extent(data, (d)->+d.count)).range([0,1])
        heatmapColour = d3.scale.linear().domain(d3.range(0, 1, 1.0 / (colours.length))).range(colours)
        colorize = (d)-> heatmapColour(c(d.count))
    
        margin = {top: 20, right: 20, bottom: 20, left: 20}
        width = $('#chart').innerWidth() - margin.left - margin.right
        height = d3.max(data, (d)-> +d.count) * 2.5
        strokeWidth = '2px'
        dataMax = d3.max(data, (d)-> +d.count)
    
        svg = d3.select('#chart').append('svg').attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.left + margin.right).append('g')
    
        defs = svg.append('defs')
        filter = defs.append('filter').attr('id', 'drop-shadow')
        filter.append('feGaussianBlur').attr('in', 'SourceAlpha').attr('stdDeviation', 9)
        filter.append('feOffset').attr('dx', 5).attr('dy', 5)
        filter.append('feComponentTransfer').append('feFuncA').attr({type: 'linear', slope: '.3'})
        feMerge = filter.append('feMerge')
        feMerge.append('feMergeNode')
        feMerge.append('feMergeNode').attr('in', 'SourceGraphic')
    
        circleGroup = svg.selectAll('g').data(data,(d)-> d.name).enter().append('g')
    
        circles = circleGroup.append('svg:circle')
    
        rScale = d3.scale.pow().exponent(.9).range([5,300]).domain(d3.extent(data, (d)-> +d.count))
    
        circles.attr('cx', width / 2).attr('cy', height / 2).attr('r', (d)-> rScale(d.count))
          .attr('class', 'name-circle').attr('data-title', (d)-> d.name + ': ' + d.count)
          .style('fill','#FFF').style('stroke',(d)-> colorize(d)).style('stroke-width', strokeWidth)
          .style('filter', (d)-> if d.count > dataMax / 3 then \
            return 'url(#drop-shadow)' else return '')
          .on('mouseover', ->
            d3.select(this).style('stroke', '#D88021').style('stroke-width', '10px')
          )
          .on('mouseleave', ->
            d3.select(this).style('stroke', (d)-> colorize(d)).style('stroke-width', strokeWidth)
          )
    
        d3utils.tooltip('.name-circle', {followMouse: true})
    
        svg.append('text')
          .text('Circles radius are proportional to the count of times the name appears')
          .attr('transform', 'translate(' + width / 2 + ',' + (height - 10) + ')')
          .attr('width', '20px')
      )
    
    $(document).ready(ready)
  • d3js-utils.coffee

    d3utils = {
      middleTitle: ((svg, width, text, top = -15)->
        element = svg.append('text').attr({class: 'chart-title', 'text-anchor': 'middle', \
          transform: 'translate(' + String(width / 2) + ',' + top + ')'})
          .text(text).style({'font-weight': 'bold'})
      )
    
      svg: ((selector, width, height, margin)->
        d3.select(selector).text('').append('svg')
          .attr({width: width + margin.left + margin.right, \
            height: height + margin.top + margin.bottom})
          .append('g').attr({transform: 'translate(' + margin.left + ',' + margin.top + ')'})
      )
    
      filterBlackOpacity: ((id, svg, deviation, slope)->
        defs = svg.append('defs')
        filter = defs.append('filter').attr({id: 'drop-shadow-' + id, width: '500%', height: '500%', \
          x: '-200%', y: '-200%'})
        filter.append('feGaussianBlur').attr({in: 'SourceAlpha', stdDeviation: deviation})
        filter.append('feOffset').attr({dx: 1, dy: 1})
        filter.append('feComponentTransfer').append('feFuncA').attr({type: 'linear', slope: slope})
        feMerge = filter.append('feMerge')
        feMerge.append('feMergeNode')
        feMerge.append('feMergeNode').attr('in', 'SourceGraphic')
      )
    
      filterColor: ((id, svg, deviation, slope, extra = false)->
        defs = svg.append('defs')
        filter = defs.append('filter').attr({id:'drop-shadow-' + id})
        filter.attr({width: '500%', height: '500%', x: '-200%', y: '-200%'}) if extra
        filter.append('feOffset').attr({result: 'offOut', in: 'SourceGraphic', dx: .5, dy: .5})
        filter.append('feGaussianBlur')
          .attr({result: 'blurOut', in: 'offOut', stdDeviation: deviation})
        filter.append('feBlend').attr({in: 'SourceGraphic', in2: 'blurOut', mode: 'normal'})
        filter.append('feComponentTransfer').append('feFuncA').attr({type: 'linear', slope: slope})
      )
    
    
    
      tooltip: ((selector, customOpts = {})->
        defaultOpts = {
          followMouse: false, followElement: false, elementSelector: ''
          leftOffst: 60, topOffst: 40
          tOpts: {container: 'body', viewport: {selector: '#chart svg'}}
        }
        opts = _.merge(defaultOpts, customOpts)
    
        # Bootstrap tooltip.
        $(selector).tooltip(opts.tOpts)
    
        # For SVG forms, it is better to position the tooltip manually.
        if opts.followMouse
          $(selector).hover((e)->
            $('.tooltip')
              .css({top: String(e.pageY - opts.topOffst) + 'px', \
                left: String(e.pageX - opts.leftOffst) + 'px'})
          )
        else if opts.followElement
          $(selector).hover((e)->
            $('.tooltip')
              .css({top: String($(opts.elementSelector).position().top - opts.topOffst) + 'px', \
                left: String($(opts.elementSelector).position().left - opts.leftOffst) + 'px'})
          )
      )
    
    
      colorsScale: ((colors, extent)->
        c = d3.scale.linear().domain(extent).range([0,1])
        colorScale = d3.scale.linear()
          .domain(d3.range(0, 1, 1.0 / (colors.length))).range(colors)
        return ((p)-> colorScale(c(p)))
      )
    
    }
    
    window.d3utils = d3utils

Home