Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 espaco 1
# Donut charts.
2
#
3
# @example
4
#   Morris.Donut({
5
#     el: $('#donut-container'),
6
#     data: [
7
#       { label: 'yin',  value: 50 },
8
#       { label: 'yang', value: 50 }
9
#     ]
10
#   });
11
class Morris.Donut extends Morris.EventEmitter
12
  defaults:
13
    colors: [
14
      '#0B62A4'
15
      '#3980B5'
16
      '#679DC6'
17
      '#95BBD7'
18
      '#B0CCE1'
19
      '#095791'
20
      '#095085'
21
      '#083E67'
22
      '#052C48'
23
      '#042135'
24
    ],
25
    backgroundColor: '#FFFFFF',
26
    labelColor: '#000000',
27
    formatter: Morris.commas
28
    resize: false
29
 
30
  # Create and render a donut chart.
31
  #
32
  constructor: (options) ->
33
    return new Morris.Donut(options) unless (@ instanceof Morris.Donut)
34
    @options = $.extend {}, @defaults, options
35
 
36
    if typeof options.element is 'string'
37
      @el = $ document.getElementById(options.element)
38
    else
39
      @el = $ options.element
40
 
41
    if @el == null || @el.length == 0
42
      throw new Error("Graph placeholder not found.")
43
 
44
    # bail if there's no data
45
    if options.data is undefined or options.data.length is 0
46
      return
47
 
48
    @raphael = new Raphael(@el[0])
49
 
50
    if @options.resize
51
      $(window).bind 'resize', (evt) =>
52
        if @timeoutId?
53
          window.clearTimeout @timeoutId
54
        @timeoutId = window.setTimeout @resizeHandler, 100
55
 
56
    @setData options.data
57
 
58
  # Clear and redraw the chart.
59
  redraw: ->
60
    @raphael.clear()
61
 
62
    cx = @el.width() / 2
63
    cy = @el.height() / 2
64
    w = (Math.min(cx, cy) - 10) / 3
65
 
66
    total = 0
67
    total += value for value in @values
68
 
69
    min = 5 / (2 * w)
70
    C = 1.9999 * Math.PI - min * @data.length
71
 
72
    last = 0
73
    idx = 0
74
    @segments = []
75
    for value, i in @values
76
      next = last + min + C * (value / total)
77
      seg = new Morris.DonutSegment(
78
        cx, cy, w*2, w, last, next,
79
        @data[i].color || @options.colors[idx % @options.colors.length],
80
        @options.backgroundColor, idx, @raphael)
81
      seg.render()
82
      @segments.push seg
83
      seg.on 'hover', @select
84
      seg.on 'click', @click
85
      last = next
86
      idx += 1
87
 
88
    @text1 = @drawEmptyDonutLabel(cx, cy - 10, @options.labelColor, 15, 800)
89
    @text2 = @drawEmptyDonutLabel(cx, cy + 10, @options.labelColor, 14)
90
 
91
    max_value = Math.max @values...
92
    idx = 0
93
    for value in @values
94
      if value == max_value
95
        @select idx
96
        break
97
      idx += 1
98
 
99
  setData: (data) ->
100
    @data = data
101
    @values = (parseFloat(row.value) for row in @data)
102
    @redraw()
103
 
104
  # @private
105
  click: (idx) =>
106
    @fire 'click', idx, @data[idx]
107
 
108
  # Select the segment at the given index.
109
  select: (idx) =>
110
    s.deselect() for s in @segments
111
    segment = @segments[idx]
112
    segment.select()
113
    row = @data[idx]
114
    @setLabels(row.label, @options.formatter(row.value, row))
115
 
116
 
117
 
118
  # @private
119
  setLabels: (label1, label2) ->
120
    inner = (Math.min(@el.width() / 2, @el.height() / 2) - 10) * 2 / 3
121
    maxWidth = 1.8 * inner
122
    maxHeightTop = inner / 2
123
    maxHeightBottom = inner / 3
124
    @text1.attr(text: label1, transform: '')
125
    text1bbox = @text1.getBBox()
126
    text1scale = Math.min(maxWidth / text1bbox.width, maxHeightTop / text1bbox.height)
127
    @text1.attr(transform: "S#{text1scale},#{text1scale},#{text1bbox.x + text1bbox.width / 2},#{text1bbox.y + text1bbox.height}")
128
    @text2.attr(text: label2, transform: '')
129
    text2bbox = @text2.getBBox()
130
    text2scale = Math.min(maxWidth / text2bbox.width, maxHeightBottom / text2bbox.height)
131
    @text2.attr(transform: "S#{text2scale},#{text2scale},#{text2bbox.x + text2bbox.width / 2},#{text2bbox.y}")
132
 
133
  drawEmptyDonutLabel: (xPos, yPos, color, fontSize, fontWeight) ->
134
    text = @raphael.text(xPos, yPos, '')
135
      .attr('font-size', fontSize)
136
      .attr('fill', color)
137
    text.attr('font-weight', fontWeight) if fontWeight?
138
    return text
139
 
140
  resizeHandler: =>
141
    @timeoutId = null
142
    @raphael.setSize @el.width(), @el.height()
143
    @redraw()
144
 
145
 
146
# A segment within a donut chart.
147
#
148
# @private
149
class Morris.DonutSegment extends Morris.EventEmitter
150
  constructor: (@cx, @cy, @inner, @outer, p0, p1, @color, @backgroundColor, @index, @raphael) ->
151
    @sin_p0 = Math.sin(p0)
152
    @cos_p0 = Math.cos(p0)
153
    @sin_p1 = Math.sin(p1)
154
    @cos_p1 = Math.cos(p1)
155
    @is_long = if (p1 - p0) > Math.PI then 1 else 0
156
    @path = @calcSegment(@inner + 3, @inner + @outer - 5)
157
    @selectedPath = @calcSegment(@inner + 3, @inner + @outer)
158
    @hilight = @calcArc(@inner)
159
 
160
  calcArcPoints: (r) ->
161
    return [
162
      @cx + r * @sin_p0,
163
      @cy + r * @cos_p0,
164
      @cx + r * @sin_p1,
165
      @cy + r * @cos_p1]
166
 
167
  calcSegment: (r1, r2) ->
168
    [ix0, iy0, ix1, iy1] = @calcArcPoints(r1)
169
    [ox0, oy0, ox1, oy1] = @calcArcPoints(r2)
170
    return (
171
      "M#{ix0},#{iy0}" +
172
      "A#{r1},#{r1},0,#{@is_long},0,#{ix1},#{iy1}" +
173
      "L#{ox1},#{oy1}" +
174
      "A#{r2},#{r2},0,#{@is_long},1,#{ox0},#{oy0}" +
175
      "Z")
176
 
177
  calcArc: (r) ->
178
    [ix0, iy0, ix1, iy1] = @calcArcPoints(r)
179
    return (
180
      "M#{ix0},#{iy0}" +
181
      "A#{r},#{r},0,#{@is_long},0,#{ix1},#{iy1}")
182
 
183
  render: ->
184
    @arc = @drawDonutArc(@hilight, @color)
185
    @seg = @drawDonutSegment(
186
      @path,
187
      @color,
188
      @backgroundColor,
189
      => @fire('hover', @index),
190
      => @fire('click', @index)
191
    )
192
 
193
  drawDonutArc: (path, color) ->
194
    @raphael.path(path)
195
      .attr(stroke: color, 'stroke-width': 2, opacity: 0)
196
 
197
  drawDonutSegment: (path, fillColor, strokeColor, hoverFunction, clickFunction) ->
198
    @raphael.path(path)
199
      .attr(fill: fillColor, stroke: strokeColor, 'stroke-width': 3)
200
      .hover(hoverFunction)
201
      .click(clickFunction)
202
 
203
  select: =>
204
    unless @selected
205
      @seg.animate(path: @selectedPath, 150, '<>')
206
      @arc.animate(opacity: 1, 150, '<>')
207
      @selected = true
208
 
209
  deselect: =>
210
    if @selected
211
      @seg.animate(path: @path, 150, '<>')
212
      @arc.animate(opacity: 0, 150, '<>')
213
      @selected = false