Map Distorsions Chart main source: ··· Home

Notes:

  • Dynamic (for performance) shadow
  • Custom color scale related to index
  • Tooltip and minor style changes

Data: data.tsv

Code:

  • map-distorsions.ejs

    <%  demo = {category: "d3js", key: "map-distorsions", files: files}; %>
    
    <%- partial("../mixins/_demo_title", demo) %>
    
    <p><div class="slider"></div></p>
    <div id="chart" class="<%= demo.key %>-chart" style="margin-top: 30px;"></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) %>
  • map-distorsions.coffee

    ready = ->
      margin = {top: 90, right: 40, bottom: 20, left: 200}
      width = $('#chart').innerWidth() - margin.left - margin.right
      height = 750 - margin.top - margin.bottom
    
      colors = ['#7C7CC9','#429742', '#63BD28', '#D14141']
    
      dimensions = [{
          name: 'name', scale: d3.scale.ordinal().rangePoints([0, height]), type: String
        },
        {
          name: 'Acc. 40º 150%', scale: d3.scale.linear().range([0, height]), type: Number
        },
        {
          name: 'Scale', scale: d3.scale.linear().range([height, 0]), type: Number
        },
        {
          name: 'Areal', scale: d3.scale.sqrt().range([height, 0]), type: Number
        },
        {
          name: 'Angular', scale: d3.scale.linear().range([height, 0]), type: Number
        }
      ]
    
      
      svg = d3utils.svg('#chart', width, height, margin)
      
      title = 'Comparison of 41 map projections by four different types of distortion. Lower is better.'
      d3utils.middleTitle(svg, width, title, -60)
    
      x = d3.scale.ordinal().domain(dimensions.map((d)-> d.name )).rangePoints([0, width])
      line = d3.svg.line().defined((d)-> !isNaN(d[1]))
      yAxis = d3.svg.axis().orient('left')
    
      dimension = svg.selectAll('.dimension').data(dimensions).enter().append('g')
        .attr('class', 'dimension').attr('transform', (d)-> 'translate(' + x(d.name) + ')')
    
    
      d3.tsv('/data/d3js/map-distorsions/data.tsv', (data)->
        d3utils.filterColor('lines', svg, 2, .4)
        data = _.sortBy(data, 'name')
        colorFn = d3utils.colorsScale(colors, [0, data.length - 1])
    
        dimensions.forEach((dimension)->
          dimension.scale.domain(
            if dimension.type is Number
              d3.extent(data, (d)-> +d[dimension.name])
            else
              data.map((d)-> d[dimension.name]).sort()
          )
        )
    
    
        draw = ((d)->
          line(dimensions.map((dimension)->
            [x(dimension.name), dimension.scale(d[dimension.name])]
          ))
        )
    
        tooltipText = ((d)->
          vals = _.map(['Acc. 40º 150%', 'Scale', 'Areal', 'Angular'], (item)->
            String(Number(d[item]).toFixed(2))
          )
          d.name + ':  ' + vals.join(' - ')
        )
    
        svg.append('g').attr('class', 'background').selectAll('path').data(data)
          .enter().append('path').attr({d: draw, 'data-title': tooltipText})
    
        svg.append('g').attr('class', 'foreground').selectAll('path').data(data)
          .enter().append('path').attr({d: draw, 'data-title': tooltipText})
    
        dimension.append('g').attr('class', 'axis')
          .each((d)-> d3.select(this).call(yAxis.scale(d.scale))).append('text')
          .attr('class', 'title').attr('text-anchor', 'middle').attr('y', -9)
          .text((d)-> d.name )
    
        svg.select('.axis').selectAll('text:not(.title)').attr('class', 'label')
          .data(data, (d)-> d.name or d).style({fill: ((d, i)-> colorFn(i))})
    
        moveToFront = ( ->
          @parentNode.appendChild(this)
        )
    
        mouseover = ((d)->
          svg.selectAll('.foreground path').style({filter: 'none'})
          svg.classed('active', true)
          projection.classed('inactive', (p)-> p isnt d)
          projection.filter((p)-> p is d).each(moveToFront)
        )
    
        mouseout = ((d)->
          svg.selectAll('.foreground path').style({filter: 'url(#drop-shadow-lines)'})
          svg.classed('active', false)
          projection.classed('inactive', false)
        )
    
        svg.selectAll('.foreground path')
          .style({filter: 'url(#drop-shadow-lines)', stroke: ((d,i)-> colorFn(i))})
    
        projection = svg.selectAll('.axis text,.background path,.foreground path')
          .on('mouseover', mouseover).on('mouseout', mouseout)
    
        d3utils.tooltip('.background path, .foreground path', \
          {followMouse: true, leftOffst: 100, topOffst: 50})
      )
    
    
    $(document).ready(ready)
  • _map-distorsions.styl

    .map-distorsions-chart
      svg
        font-family: sans-serif;
        text-shadow 1px 1px 1px #ddd;
    
      .background path {
        fill: none;
        stroke: none;
        stroke-width: 20px;
        pointer-events: stroke;
      }
    
      .foreground path {
        fill: none;
        stroke-width: 1.5px;
      }
    
      .axis
        font-size 12px
    
      .axis .title {
        font-size: 11px;
        font-family: Times New Roman;
        font-weight: bold;
        text-transform: uppercase;
      }
    
      .axis line,
      .axis path {
        fill: none;
        stroke: #000;
        shape-rendering: crispEdges;
      }
    
      .label {
        -webkit-transition: fill 125ms linear;
    
        &.inactive {
          fill: #ccc !important;
        }
      }
    
      .active .label:not(.inactive) {
        font-weight: bold;
      }
    
      .foreground path.inactive {
        stroke: #ccc !important;
        stroke-opacity: .5;
        stroke-width: 1px;
      }
  • 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