| 21627 |
kshitij.so |
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 |
|
|
|
29 |
# Create and render a donut chart.
|
|
|
30 |
#
|
|
|
31 |
constructor: (options) ->
|
|
|
32 |
if not (this instanceof Morris.Donut)
|
|
|
33 |
return new Morris.Donut(options)
|
|
|
34 |
|
|
|
35 |
if typeof options.element is 'string'
|
|
|
36 |
@el = $ document.getElementById(options.element)
|
|
|
37 |
else
|
|
|
38 |
@el = $ options.element
|
|
|
39 |
|
|
|
40 |
@options = $.extend {}, @defaults, options
|
|
|
41 |
|
|
|
42 |
if @el == null || @el.length == 0
|
|
|
43 |
throw new Error("Graph placeholder not found.")
|
|
|
44 |
|
|
|
45 |
# bail if there's no data
|
|
|
46 |
if options.data is undefined or options.data.length is 0
|
|
|
47 |
return
|
|
|
48 |
@data = options.data
|
|
|
49 |
@values = (parseFloat(row.value) for row in @data)
|
|
|
50 |
|
|
|
51 |
@redraw()
|
|
|
52 |
|
|
|
53 |
# Clear and redraw the chart.
|
|
|
54 |
#
|
|
|
55 |
# If you need to re-size your charts, call this method after changing the
|
|
|
56 |
# size of the container element.
|
|
|
57 |
redraw: ->
|
|
|
58 |
@el.empty()
|
|
|
59 |
|
|
|
60 |
@raphael = new Raphael(@el[0])
|
|
|
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 |
@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.apply(null, value for value in @values)
|
|
|
92 |
idx = 0
|
|
|
93 |
for value in @values
|
|
|
94 |
if value == max_value
|
|
|
95 |
@select idx
|
|
|
96 |
break
|
|
|
97 |
idx += 1
|
|
|
98 |
|
|
|
99 |
# @private
|
|
|
100 |
click: (idx) =>
|
|
|
101 |
@fire 'click', idx, @data[idx]
|
|
|
102 |
|
|
|
103 |
# Select the segment at the given index.
|
|
|
104 |
select: (idx) =>
|
|
|
105 |
s.deselect() for s in @segments
|
|
|
106 |
segment = @segments[idx]
|
|
|
107 |
segment.select()
|
|
|
108 |
row = @data[idx]
|
|
|
109 |
@setLabels(row.label, @options.formatter(row.value, row))
|
|
|
110 |
|
|
|
111 |
# @private
|
|
|
112 |
setLabels: (label1, label2) ->
|
|
|
113 |
inner = (Math.min(@el.width() / 2, @el.height() / 2) - 10) * 2 / 3
|
|
|
114 |
maxWidth = 1.8 * inner
|
|
|
115 |
maxHeightTop = inner / 2
|
|
|
116 |
maxHeightBottom = inner / 3
|
|
|
117 |
@text1.attr(text: label1, transform: '')
|
|
|
118 |
text1bbox = @text1.getBBox()
|
|
|
119 |
text1scale = Math.min(maxWidth / text1bbox.width, maxHeightTop / text1bbox.height)
|
|
|
120 |
@text1.attr(transform: "S#{text1scale},#{text1scale},#{text1bbox.x + text1bbox.width / 2},#{text1bbox.y + text1bbox.height}")
|
|
|
121 |
@text2.attr(text: label2, transform: '')
|
|
|
122 |
text2bbox = @text2.getBBox()
|
|
|
123 |
text2scale = Math.min(maxWidth / text2bbox.width, maxHeightBottom / text2bbox.height)
|
|
|
124 |
@text2.attr(transform: "S#{text2scale},#{text2scale},#{text2bbox.x + text2bbox.width / 2},#{text2bbox.y}")
|
|
|
125 |
|
|
|
126 |
drawEmptyDonutLabel: (xPos, yPos, color, fontSize, fontWeight) ->
|
|
|
127 |
text = @raphael.text(xPos, yPos, '')
|
|
|
128 |
.attr('font-size', fontSize)
|
|
|
129 |
.attr('fill', color)
|
|
|
130 |
text.attr('font-weight', fontWeight) if fontWeight?
|
|
|
131 |
return text
|
|
|
132 |
|
|
|
133 |
|
|
|
134 |
# A segment within a donut chart.
|
|
|
135 |
#
|
|
|
136 |
# @private
|
|
|
137 |
class Morris.DonutSegment extends Morris.EventEmitter
|
|
|
138 |
constructor: (@cx, @cy, @inner, @outer, p0, p1, @color, @backgroundColor, @index, @raphael) ->
|
|
|
139 |
@sin_p0 = Math.sin(p0)
|
|
|
140 |
@cos_p0 = Math.cos(p0)
|
|
|
141 |
@sin_p1 = Math.sin(p1)
|
|
|
142 |
@cos_p1 = Math.cos(p1)
|
|
|
143 |
@is_long = if (p1 - p0) > Math.PI then 1 else 0
|
|
|
144 |
@path = @calcSegment(@inner + 3, @inner + @outer - 5)
|
|
|
145 |
@selectedPath = @calcSegment(@inner + 3, @inner + @outer)
|
|
|
146 |
@hilight = @calcArc(@inner)
|
|
|
147 |
|
|
|
148 |
calcArcPoints: (r) ->
|
|
|
149 |
return [
|
|
|
150 |
@cx + r * @sin_p0,
|
|
|
151 |
@cy + r * @cos_p0,
|
|
|
152 |
@cx + r * @sin_p1,
|
|
|
153 |
@cy + r * @cos_p1]
|
|
|
154 |
|
|
|
155 |
calcSegment: (r1, r2) ->
|
|
|
156 |
[ix0, iy0, ix1, iy1] = @calcArcPoints(r1)
|
|
|
157 |
[ox0, oy0, ox1, oy1] = @calcArcPoints(r2)
|
|
|
158 |
return (
|
|
|
159 |
"M#{ix0},#{iy0}" +
|
|
|
160 |
"A#{r1},#{r1},0,#{@is_long},0,#{ix1},#{iy1}" +
|
|
|
161 |
"L#{ox1},#{oy1}" +
|
|
|
162 |
"A#{r2},#{r2},0,#{@is_long},1,#{ox0},#{oy0}" +
|
|
|
163 |
"Z")
|
|
|
164 |
|
|
|
165 |
calcArc: (r) ->
|
|
|
166 |
[ix0, iy0, ix1, iy1] = @calcArcPoints(r)
|
|
|
167 |
return (
|
|
|
168 |
"M#{ix0},#{iy0}" +
|
|
|
169 |
"A#{r},#{r},0,#{@is_long},0,#{ix1},#{iy1}")
|
|
|
170 |
|
|
|
171 |
render: ->
|
|
|
172 |
@arc = @drawDonutArc(@hilight, @color)
|
|
|
173 |
@seg = @drawDonutSegment(
|
|
|
174 |
@path,
|
|
|
175 |
@color,
|
|
|
176 |
@backgroundColor,
|
|
|
177 |
=> @fire('hover', @index),
|
|
|
178 |
=> @fire('click', @index)
|
|
|
179 |
)
|
|
|
180 |
|
|
|
181 |
drawDonutArc: (path, color) ->
|
|
|
182 |
@raphael.path(path)
|
|
|
183 |
.attr(stroke: color, 'stroke-width': 2, opacity: 0)
|
|
|
184 |
|
|
|
185 |
drawDonutSegment: (path, fillColor, strokeColor, hoverFunction, clickFunction) ->
|
|
|
186 |
@raphael.path(path)
|
|
|
187 |
.attr(fill: fillColor, stroke: strokeColor, 'stroke-width': 3)
|
|
|
188 |
.hover(hoverFunction)
|
|
|
189 |
.click(clickFunction)
|
|
|
190 |
|
|
|
191 |
select: =>
|
|
|
192 |
unless @selected
|
|
|
193 |
@seg.animate(path: @selectedPath, 150, '<>')
|
|
|
194 |
@arc.animate(opacity: 1, 150, '<>')
|
|
|
195 |
@selected = true
|
|
|
196 |
|
|
|
197 |
deselect: =>
|
|
|
198 |
if @selected
|
|
|
199 |
@seg.animate(path: @path, 150, '<>')
|
|
|
200 |
@arc.animate(opacity: 0, 150, '<>')
|
|
|
201 |
@selected = false
|