Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | espaco | 1 | class Morris.Bar extends Morris.Grid |
| 2 | constructor: (options) -> |
||
| 3 | return new Morris.Bar(options) unless (@ instanceof Morris.Bar) |
||
| 4 | super($.extend {}, options, parseTime: false) |
||
| 5 | |||
| 6 | init: -> |
||
| 7 | @cumulative = @options.stacked |
||
| 8 | |||
| 9 | if @options.hideHover isnt 'always' |
||
| 10 | @hover = new Morris.Hover(parent: @el) |
||
| 11 | @on('hovermove', @onHoverMove) |
||
| 12 | @on('hoverout', @onHoverOut) |
||
| 13 | @on('gridclick', @onGridClick) |
||
| 14 | |||
| 15 | # Default configuration |
||
| 16 | # |
||
| 17 | defaults: |
||
| 18 | barSizeRatio: 0.75 |
||
| 19 | barGap: 3 |
||
| 20 | barColors: [ |
||
| 21 | '#0b62a4' |
||
| 22 | '#7a92a3' |
||
| 23 | '#4da74d' |
||
| 24 | '#afd8f8' |
||
| 25 | '#edc240' |
||
| 26 | '#cb4b4b' |
||
| 27 | '#9440ed' |
||
| 28 | ], |
||
| 29 | barOpacity: 1.0 |
||
| 30 | barRadius: [0, 0, 0, 0] |
||
| 31 | xLabelMargin: 50 |
||
| 32 | |||
| 33 | # Do any size-related calculations |
||
| 34 | # |
||
| 35 | # @private |
||
| 36 | calc: -> |
||
| 37 | @calcBars() |
||
| 38 | if @options.hideHover is false |
||
| 39 | @hover.update(@hoverContentForRow(@data.length - 1)...) |
||
| 40 | |||
| 41 | # calculate series data bars coordinates and sizes |
||
| 42 | # |
||
| 43 | # @private |
||
| 44 | calcBars: -> |
||
| 45 | for row, idx in @data |
||
| 46 | row._x = @left + @width * (idx + 0.5) / @data.length |
||
| 47 | row._y = for y in row.y |
||
| 48 | if y? then @transY(y) else null |
||
| 49 | |||
| 50 | # Draws the bar chart. |
||
| 51 | # |
||
| 52 | draw: -> |
||
| 53 | @drawXAxis() if @options.axes in [true, 'both', 'x'] |
||
| 54 | @drawSeries() |
||
| 55 | |||
| 56 | # draw the x-axis labels |
||
| 57 | # |
||
| 58 | # @private |
||
| 59 | drawXAxis: -> |
||
| 60 | # draw x axis labels |
||
| 61 | ypos = @bottom + (@options.xAxisLabelTopPadding || @options.padding / 2) |
||
| 62 | prevLabelMargin = null |
||
| 63 | prevAngleMargin = null |
||
| 64 | for i in [0...@data.length] |
||
| 65 | row = @data[@data.length - 1 - i] |
||
| 66 | label = @drawXAxisLabel(row._x, ypos, row.label) |
||
| 67 | textBox = label.getBBox() |
||
| 68 | label.transform("r#{-@options.xLabelAngle}") |
||
| 69 | labelBox = label.getBBox() |
||
| 70 | label.transform("t0,#{labelBox.height / 2}...") |
||
| 71 | if @options.xLabelAngle != 0 |
||
| 72 | offset = -0.5 * textBox.width * |
||
| 73 | Math.cos(@options.xLabelAngle * Math.PI / 180.0) |
||
| 74 | label.transform("t#{offset},0...") |
||
| 75 | # try to avoid overlaps |
||
| 76 | if (not prevLabelMargin? or |
||
| 77 | prevLabelMargin >= labelBox.x + labelBox.width or |
||
| 78 | prevAngleMargin? and prevAngleMargin >= labelBox.x) and |
||
| 79 | labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width() |
||
| 80 | if @options.xLabelAngle != 0 |
||
| 81 | margin = 1.25 * @options.gridTextSize / |
||
| 82 | Math.sin(@options.xLabelAngle * Math.PI / 180.0) |
||
| 83 | prevAngleMargin = labelBox.x - margin |
||
| 84 | prevLabelMargin = labelBox.x - @options.xLabelMargin |
||
| 85 | else |
||
| 86 | label.remove() |
||
| 87 | |||
| 88 | # draw the data series |
||
| 89 | # |
||
| 90 | # @private |
||
| 91 | drawSeries: -> |
||
| 92 | groupWidth = @width / @options.data.length |
||
| 93 | numBars = if @options.stacked then 1 else @options.ykeys.length |
||
| 94 | barWidth = (groupWidth * @options.barSizeRatio - @options.barGap * (numBars - 1)) / numBars |
||
| 95 | barWidth = Math.min(barWidth, @options.barSize) if @options.barSize |
||
| 96 | spaceLeft = groupWidth - barWidth * numBars - @options.barGap * (numBars - 1) |
||
| 97 | leftPadding = spaceLeft / 2 |
||
| 98 | zeroPos = if @ymin <= 0 and @ymax >= 0 then @transY(0) else null |
||
| 99 | @bars = for row, idx in @data |
||
| 100 | lastTop = 0 |
||
| 101 | for ypos, sidx in row._y |
||
| 102 | if ypos != null |
||
| 103 | if zeroPos |
||
| 104 | top = Math.min(ypos, zeroPos) |
||
| 105 | bottom = Math.max(ypos, zeroPos) |
||
| 106 | else |
||
| 107 | top = ypos |
||
| 108 | bottom = @bottom |
||
| 109 | |||
| 110 | left = @left + idx * groupWidth + leftPadding |
||
| 111 | left += sidx * (barWidth + @options.barGap) unless @options.stacked |
||
| 112 | size = bottom - top |
||
| 113 | |||
| 114 | if @options.verticalGridCondition and @options.verticalGridCondition(row.x) |
||
| 115 | @drawBar(@left + idx * groupWidth, @top, groupWidth, Math.abs(@top - @bottom), @options.verticalGridColor, @options.verticalGridOpacity, @options.barRadius) |
||
| 116 | |||
| 117 | top -= lastTop if @options.stacked |
||
| 118 | @drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar'), |
||
| 119 | @options.barOpacity, @options.barRadius) |
||
| 120 | |||
| 121 | lastTop += size |
||
| 122 | else |
||
| 123 | null |
||
| 124 | |||
| 125 | # @private |
||
| 126 | # |
||
| 127 | # @param row [Object] row data |
||
| 128 | # @param sidx [Number] series index |
||
| 129 | # @param type [String] "bar", "hover" or "label" |
||
| 130 | colorFor: (row, sidx, type) -> |
||
| 131 | if typeof @options.barColors is 'function' |
||
| 132 | r = { x: row.x, y: row.y[sidx], label: row.label } |
||
| 133 | s = { index: sidx, key: @options.ykeys[sidx], label: @options.labels[sidx] } |
||
| 134 | @options.barColors.call(@, r, s, type) |
||
| 135 | else |
||
| 136 | @options.barColors[sidx % @options.barColors.length] |
||
| 137 | |||
| 138 | # hit test - returns the index of the row at the given x-coordinate |
||
| 139 | # |
||
| 140 | hitTest: (x) -> |
||
| 141 | return null if @data.length == 0 |
||
| 142 | x = Math.max(Math.min(x, @right), @left) |
||
| 143 | Math.min(@data.length - 1, |
||
| 144 | Math.floor((x - @left) / (@width / @data.length))) |
||
| 145 | |||
| 146 | # click on grid event handler |
||
| 147 | # |
||
| 148 | # @private |
||
| 149 | onGridClick: (x, y) => |
||
| 150 | index = @hitTest(x) |
||
| 151 | @fire 'click', index, @data[index].src, x, y |
||
| 152 | |||
| 153 | # hover movement event handler |
||
| 154 | # |
||
| 155 | # @private |
||
| 156 | onHoverMove: (x, y) => |
||
| 157 | index = @hitTest(x) |
||
| 158 | @hover.update(@hoverContentForRow(index)...) |
||
| 159 | |||
| 160 | # hover out event handler |
||
| 161 | # |
||
| 162 | # @private |
||
| 163 | onHoverOut: => |
||
| 164 | if @options.hideHover isnt false |
||
| 165 | @hover.hide() |
||
| 166 | |||
| 167 | # hover content for a point |
||
| 168 | # |
||
| 169 | # @private |
||
| 170 | hoverContentForRow: (index) -> |
||
| 171 | row = @data[index] |
||
| 172 | content = "<div class='morris-hover-row-label'>#{row.label}</div>" |
||
| 173 | for y, j in row.y |
||
| 174 | content += """ |
||
| 175 | <div class='morris-hover-point' style='color: #{@colorFor(row, j, 'label')}'> |
||
| 176 | #{@options.labels[j]}: |
||
| 177 | #{@yLabelFormat(y)} |
||
| 178 | </div> |
||
| 179 | """ |
||
| 180 | if typeof @options.hoverCallback is 'function' |
||
| 181 | content = @options.hoverCallback(index, @options, content, row.src) |
||
| 182 | x = @left + (index + 0.5) * @width / @data.length |
||
| 183 | [content, x] |
||
| 184 | |||
| 185 | drawXAxisLabel: (xPos, yPos, text) -> |
||
| 186 | label = @raphael.text(xPos, yPos, text) |
||
| 187 | .attr('font-size', @options.gridTextSize) |
||
| 188 | .attr('font-family', @options.gridTextFamily) |
||
| 189 | .attr('font-weight', @options.gridTextWeight) |
||
| 190 | .attr('fill', @options.gridTextColor) |
||
| 191 | |||
| 192 | drawBar: (xPos, yPos, width, height, barColor, opacity, radiusArray) -> |
||
| 193 | maxRadius = Math.max(radiusArray...) |
||
| 194 | if maxRadius == 0 or maxRadius > height |
||
| 195 | path = @raphael.rect(xPos, yPos, width, height) |
||
| 196 | else |
||
| 197 | path = @raphael.path @roundedRect(xPos, yPos, width, height, radiusArray) |
||
| 198 | path |
||
| 199 | .attr('fill', barColor) |
||
| 200 | .attr('fill-opacity', opacity) |
||
| 201 | .attr('stroke', 'none') |
||
| 202 | |||
| 203 | roundedRect: (x, y, w, h, r = [0,0,0,0]) -> |
||
| 204 | [ "M", x, r[0] + y, "Q", x, y, x + r[0], y, |
||
| 205 | "L", x + w - r[1], y, "Q", x + w, y, x + w, y + r[1], |
||
| 206 | "L", x + w, y + h - r[2], "Q", x + w, y + h, x + w - r[2], y + h, |
||
| 207 | "L", x + r[3], y + h, "Q", x, y + h, x, y + h - r[3], "Z" ] |
||
| 208 |