| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 | # Donut charts.## @example#   Morris.Donut({#     el: $('#donut-container'),#     data: [#       { label: 'yin',  value: 50 },#       { label: 'yang', value: 50 }#     ]#   });class Morris.Donut extends Morris.EventEmitter  defaults:    colors: [      '#0B62A4'      '#3980B5'      '#679DC6'      '#95BBD7'      '#B0CCE1'      '#095791'      '#095085'      '#083E67'      '#052C48'      '#042135'    ],    backgroundColor: '#FFFFFF',     labelColor: '#000000',    formatter: Morris.commas    resize: false  # Create and render a donut chart.  #  constructor: (options) ->    return new Morris.Donut(options) unless (@ instanceof Morris.Donut)    @options = $.extend {}, @defaults, options    if typeof options.element is 'string'      @el = $ document.getElementById(options.element)    else      @el = $ options.element    if @el == null || @el.length == 0      throw new Error("Graph placeholder not found.")    # bail if there's no data    if options.data is undefined or options.data.length is 0      return    @raphael = new Raphael(@el[0])    if @options.resize      $(window).bind 'resize', (evt) =>        if @timeoutId?          window.clearTimeout @timeoutId        @timeoutId = window.setTimeout @resizeHandler, 100    @setData options.data  # Clear and redraw the chart.  redraw: ->    @raphael.clear()    cx = @el.width() / 2    cy = @el.height() / 2    w = (Math.min(cx, cy) - 10) / 3    total = 0    total += value for value in @values    min = 5 / (2 * w)    C = 1.9999 * Math.PI - min * @data.length    last = 0    idx = 0    @segments = []    for value, i in @values      next = last + min + C * (value / total)      seg = new Morris.DonutSegment(        cx, cy, w*2, w, last, next,        @data[i].color || @options.colors[idx % @options.colors.length],        @options.backgroundColor, idx, @raphael)      seg.render()      @segments.push seg      seg.on 'hover', @select      seg.on 'click', @click      last = next      idx += 1    @text1 = @drawEmptyDonutLabel(cx, cy - 10, @options.labelColor, 15, 800)    @text2 = @drawEmptyDonutLabel(cx, cy + 10, @options.labelColor, 14)    max_value = Math.max @values...    idx = 0    for value in @values      if value == max_value        @select idx        break      idx += 1  setData: (data) ->    @data = data    @values = (parseFloat(row.value) for row in @data)    @redraw()  # @private  click: (idx) =>    @fire 'click', idx, @data[idx]  # Select the segment at the given index.  select: (idx) =>    s.deselect() for s in @segments    segment = @segments[idx]    segment.select()    row = @data[idx]    @setLabels(row.label, @options.formatter(row.value, row))  # @private  setLabels: (label1, label2) ->    inner = (Math.min(@el.width() / 2, @el.height() / 2) - 10) * 2 / 3    maxWidth = 1.8 * inner    maxHeightTop = inner / 2    maxHeightBottom = inner / 3    @text1.attr(text: label1, transform: '')    text1bbox = @text1.getBBox()    text1scale = Math.min(maxWidth / text1bbox.width, maxHeightTop / text1bbox.height)    @text1.attr(transform: "S#{text1scale},#{text1scale},#{text1bbox.x + text1bbox.width / 2},#{text1bbox.y + text1bbox.height}")    @text2.attr(text: label2, transform: '')    text2bbox = @text2.getBBox()    text2scale = Math.min(maxWidth / text2bbox.width, maxHeightBottom / text2bbox.height)    @text2.attr(transform: "S#{text2scale},#{text2scale},#{text2bbox.x + text2bbox.width / 2},#{text2bbox.y}")  drawEmptyDonutLabel: (xPos, yPos, color, fontSize, fontWeight) ->    text = @raphael.text(xPos, yPos, '')      .attr('font-size', fontSize)      .attr('fill', color)    text.attr('font-weight', fontWeight) if fontWeight?    return text  resizeHandler: =>    @timeoutId = null    @raphael.setSize @el.width(), @el.height()    @redraw()# A segment within a donut chart.## @privateclass Morris.DonutSegment extends Morris.EventEmitter  constructor: (@cx, @cy, @inner, @outer, p0, p1, @color, @backgroundColor, @index, @raphael) ->    @sin_p0 = Math.sin(p0)    @cos_p0 = Math.cos(p0)    @sin_p1 = Math.sin(p1)    @cos_p1 = Math.cos(p1)    @is_long = if (p1 - p0) > Math.PI then 1 else 0    @path = @calcSegment(@inner + 3, @inner + @outer - 5)    @selectedPath = @calcSegment(@inner + 3, @inner + @outer)    @hilight = @calcArc(@inner)  calcArcPoints: (r) ->    return [      @cx + r * @sin_p0,      @cy + r * @cos_p0,      @cx + r * @sin_p1,      @cy + r * @cos_p1]  calcSegment: (r1, r2) ->    [ix0, iy0, ix1, iy1] = @calcArcPoints(r1)    [ox0, oy0, ox1, oy1] = @calcArcPoints(r2)    return (      "M#{ix0},#{iy0}" +      "A#{r1},#{r1},0,#{@is_long},0,#{ix1},#{iy1}" +      "L#{ox1},#{oy1}" +      "A#{r2},#{r2},0,#{@is_long},1,#{ox0},#{oy0}" +      "Z")  calcArc: (r) ->    [ix0, iy0, ix1, iy1] = @calcArcPoints(r)    return (      "M#{ix0},#{iy0}" +      "A#{r},#{r},0,#{@is_long},0,#{ix1},#{iy1}")  render: ->    @arc = @drawDonutArc(@hilight, @color)    @seg = @drawDonutSegment(      @path,       @color,       @backgroundColor,       => @fire('hover', @index),      => @fire('click', @index)    )  drawDonutArc: (path, color) ->    @raphael.path(path)      .attr(stroke: color, 'stroke-width': 2, opacity: 0)  drawDonutSegment: (path, fillColor, strokeColor, hoverFunction, clickFunction) ->    @raphael.path(path)      .attr(fill: fillColor, stroke: strokeColor, 'stroke-width': 3)      .hover(hoverFunction)      .click(clickFunction)  select: =>    unless @selected      @seg.animate(path: @selectedPath, 150, '<>')      @arc.animate(opacity: 1, 150, '<>')      @selected = true  deselect: =>    if @selected      @seg.animate(path: @path, 150, '<>')      @arc.animate(opacity: 0, 150, '<>')      @selected = false
 |