CodePen

HTML

            
              <main ng-app="myApp">
  <figure ng-controller="chartCtrl">
    <div peak-chart data="data">
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      <span face></span>
      
      <label></label>
      <label></label>
      <label></label>
      <label></label>
      <label></label>
      <label></label>
      <label></label>
      <label></label>
      <label></label>
    </div>
    <figcaption>
      Some data. &nbsp;&nbsp;
      <button ng-click="paused=!paused">
        {{ paused && '\u25b8' || '\u2759\u2759' }}
      </button>
    </figcaption>
  </figure>
</main> 
            
          
!

↑ Insert the most common viewport meta tag

CSS

            
              // vim: et:sts=2:sw=2

@import url("http://fonts.googleapis.com/css?family=Cabin:400,700,400italic");

@chart-blue: hsl(207, 60%, 60%);

body, input, button, textarea, select {
  font: 16px Cabin;
  color: #bdbebf;
  background: #4a4844;
}

figure {
  text-align: center;
}

[peak-chart] { .peak-chart; }

figcaption {
  line-height: 36px;
  font-style: italic;

  button {
    background: none;
    border: 1px solid lightgray;
    cursor: pointer;
    font-size: 90%;
    margin: 0;
    padding: 1px 0.5em 0;
    line-height: 1.3;
    border-radius: 0.5em;
    vertical-align: text-top;

    &:hover {
      background: rgba(0,0,0,0.04);
    }
  }
}

.peak-chart(
    @r: 120px,

    @clr1: hsl(192, 60%, 60%),
    @clr2: @chart-blue,
    @clr3: hsl(234, 32%, 60%),
    @clr4: hsl(345, 58%, 60%),
    @clr5: hsl(185, 27%, 60%),
    @clr6: hsl(298, 29%, 60%),
    @clr7: hsl(220,  6%, 90%),
    @clr8: hsl(220,  6%, 90%),
    @clr9: hsl(220,  6%, 90%)
  ) {

  position: relative;
  display: inline-block;
  width: (@r * 2);
  height: (@r * 2);
  border-radius: @r;
  margin-top: @r;

  -webkit-transform: rotateX(45deg);
     -moz-transform: rotateX(45deg);
      -ms-transform: rotateX(45deg);
          transform: rotateX(45deg);
  
  &, * {
    -webkit-transform-style: preserve-3d;
       -moz-transform-style: preserve-3d;
        -ms-transform-style: preserve-3d;
            transform-style: preserve-3d;
  }

  > [face] {
    position: absolute;
    bottom: 50%;
    left: 50%;
    width: @r;
    height: @r;

    -webkit-transform-origin: 0 100%;
       -moz-transform-origin: 0 100%;
        -ms-transform-origin: 0 100%;
            transform-origin: 0 100%;
    
    .transition;

    .fill(@color) {
      background-image:
        -webkit-linear-gradient(bottom right, fade(white, 0), fade(white, 25%) 50%, transparent 50%),
        -webkit-linear-gradient(bottom right, @color, @color 50%, transparent 50%);
      background-image:
        linear-gradient(to top left, fade(white, 0), fade(white, 25%) 50%, transparent 50%),
        linear-gradient(to top left, @color, @color 50%, transparent 50%);
    }

    .bkfill(@color) {
      .fill((darken(@color, 10%)));
    }
    
     &:nth-child(1) { .bkfill(@clr1); }
     &:nth-child(2) {   .fill(@clr1); }
     &:nth-child(3) { .bkfill(@clr2); }
     &:nth-child(4) {   .fill(@clr2); }
     &:nth-child(5) { .bkfill(@clr3); }
     &:nth-child(6) {   .fill(@clr3); }
     &:nth-child(7) { .bkfill(@clr4); }
     &:nth-child(8) {   .fill(@clr4); }
     &:nth-child(9) { .bkfill(@clr5); }
    &:nth-child(10) {   .fill(@clr5); }
    &:nth-child(11) { .bkfill(@clr6); }
    &:nth-child(12) {   .fill(@clr6); }
    &:nth-child(13) { .bkfill(@clr7); }
    &:nth-child(14) {   .fill(@clr7); }
    &:nth-child(15) { .bkfill(@clr8); }
    &:nth-child(16) {   .fill(@clr8); }
    &:nth-child(17) { .bkfill(@clr9); }
    &:nth-child(18) {   .fill(@clr9); }
  }

  > label {
    position: absolute;
    font-weight: bold;
    font-size: 90%;
    
    .transition;

    &:before {
      content: '';
      position: absolute;
      width: 6px;
      height: 6px;
      border-radius: 3px;

      @grst: (lighten(@chart-blue, 30%));
      .gradient(~'@{grst}, @{chart-blue}', ~'to bottom left', ~'top right');
    }

    &[point=bottomright] {
      padding-right: 6px;
      padding-bottom: 9px;
      &:before {
        right: -3px;
        bottom: 3px;
      }
    }

    &[point=bottomleft] {
      padding-left: 6px;
      padding-bottom: 9px;
      &:before {
        left: -3px;
        bottom: 3px;
      }
    }

    &[point=topleft] {
      padding-left: 4px;
      padding-top: 4px;
      &:before {
        left: -3px;
        top: -3px;
      }
    }

    &[point=topright] {
      padding-right: 4px;
      padding-top: 4px;
      &:before {
        right: -3px;
        top: -3px;
      }
    }
  }
}

// -------
// Helpers
// -------

.gradient(@stops, @angle: to bottom, @oldAngle: top) {
  background-image: -webkit-linear-gradient(@oldAngle, @stops);
  background-image: linear-gradient(@angle, @stops);
}

.transition(@props: all, @dur: 0.25s, @easing: ease-in) {
  -webkit-transition: @props @dur @easing;
     -moz-transition: @props @dur @easing;
          transition: @props @dur @easing;
}

            
          
!
? ?
? ?
Must be a valid URL.
+ add another resource
via CSS Lint

JS

            
              // vim: et:sts=2:sw=2

var angular = window.angular
  , console = window.console;

window.chartCtrl = ['$scope', function($scope) {
  $scope.data = new Array(9);
  
  $scope.sample = function() {
    if (!$scope.paused) {
      for (var i=0, l=$scope.data.length; i < l; i++) {
        $scope.data[i] = Math.random() * 95 + 5;
      }
    }
  };
  
  $scope.paused = false;
  
  $scope.sample();
  $scope.sampler = setInterval(function() {
    if (!$scope.paused) {
      $scope.$apply($scope.sample);
    }
  }, 2000);
}];

angular.module('myApp', []).

directive('peakChart', ['$window', function($win) {
  var PI   = Math.PI
    , sin  = Math.sin
    , cos  = Math.cos
    , tan  = Math.tan
    , atan = Math.atan
    , acos = Math.acos
    , sqrt = Math.sqrt
    , pow  = Math.pow
    , abs  = Math.abs
    , rSkRo = /^(?:sk|ro)/;
  
  return {
    link: function(scope, elm, attrs) {
      var faces  = elm[0].querySelectorAll('[face]')
        , labels = elm[0].getElementsByTagName('label')
        , r      = elm[0].offsetWidth / 2
        , charth = 1.5 * r; // max height of peaks: revisit

      scope.$watch(attrs.data, function(data) {
        var sum = 0;
        angular.forEach(data, function(d) { sum += d; });
        
        // iterate slices
        for (var i=0, face; (face = faces[2*i]); i++) {
          var bkFace   = faces[2*i + 1]
           //
           // split the peaks evenly around the graph
           //
            , slice    = 2 * PI / (faces.length/2)
            , npos     = i * slice - PI
           //
           // give the slice height based on data - assume data values are percentages
           // also, tone down the peaks in front (HACK)
           //
            , val      = (i >= 4 ? data[i] * 0.6 : data[i])
            , h        = val * charth / 100
           //
           // face angle - each face covers half the slice
           //
            , fang     = slice/2
           // 
           // we want the faces to meet at a point "h" pixels above the
           // midpoint of the slice's arc
           //
            , chord    = 2 * r * sin(fang/2)
           //
           // chord's angle with radius
           //  
            , chang    = (PI - fang) / 2
           //
           // altitude from radius to arc midpoint
           //
            , alt2mp   = chord * sin(chang)

            , slope    = atan(h / alt2mp)
            , slopeLen = sqrt(pow(h,2) + pow(alt2mp,2))
           //
           // the radial triangles are half squares so h = r
           //
            , scaleY   = slopeLen / r
            , skew     = atan(chord * cos(chang) / slopeLen)
            ;
          
          setTransform(face,
            'translateZ', 2,
            'rotateZ',    npos,
            'rotateX',    slope - Math.PI,
            'skewX',      skew,
            'scaleY',     scaleY
          );
          
          setTransform(bkFace,
            'translateZ', 2,
            'rotateZ',    npos + slice,
            'rotateX',    -slope,
            'skewX',      skew,
            'scaleY',     scaleY
          );

          var lbl = labels[i];
          lbl.textContent = Math.round(val);

          var rlbl   = r
            , nlbl   = -npos - slice/2
            , lblx   = rlbl * cos(nlbl)
            , lbly   = rlbl * sin(nlbl)
            , lblxat = lblx > 0 ? 'left'   : 'right'
            , lblyat = lbly > 0 ? 'bottom' : 'top';

          lbl.style[lblxat] = r + abs(lblx) + 'px';
          lbl.style[lblyat] = r + abs(lbly) + 15 + 'px';
          lbl.setAttribute('point', lblyat + lblxat); // e.g., [point=topleft]

          if (i < 5) {
            setTransform(lbl,
              'rotateX',    deg(-45)
            , 'translateY', -(h / sqrt(2))
            , 'translateZ', h / sqrt(2)
            );
          }
        }
        
      }, /* deep */ true);
    }
  };
  
  function rad(x) {
    return (Math.round(x * 1000) / 1000) + 'rad';
  }
  
  
  function setTransform(node) {
    var ns = node.style, trans = '';
    for (var i=1, l=arguments.length; i < l; i += 2) {
      var nm = arguments[i], val = arguments[i+1];
      trans += nm + '(' + (
          rSkRo.test(nm) ? rad(val)
        : nm.slice(0, 2) === 'tr' ? val + 'px'
        : val) + ') ';
    }
    ns.webkitTransform =
      ns.mozTransform =
      ns.msTransform =
      ns.transform = trans;
  }

  function deg(x) {
      return { valueOf: function() { return x * PI / 180; } };
  }
  
}]);

            
          
!
Must be a valid URL.
+ add another resource
via JS Hint
Loading ..................