2012-10-31 20:38:09 +01:00
|
|
|
class Morris.Bar extends Morris.Grid
|
|
|
|
constructor: (options) ->
|
|
|
|
return new Morris.Bar(options) unless (@ instanceof Morris.Bar)
|
2012-10-31 22:58:26 +01:00
|
|
|
super($.extend {}, options, parseTime: false)
|
|
|
|
|
2012-10-31 20:38:09 +01:00
|
|
|
init: ->
|
2012-11-20 02:23:11 +01:00
|
|
|
@cumulative = @options.stacked
|
2012-10-31 22:58:26 +01:00
|
|
|
|
2012-12-17 21:08:39 +01:00
|
|
|
if @options.hideHover isnt 'always'
|
|
|
|
@hover = new Morris.Hover(parent: @el)
|
|
|
|
@on('hovermove', @onHoverMove)
|
|
|
|
@on('hoverout', @onHoverOut)
|
2012-12-31 12:02:14 +01:00
|
|
|
@on('gridclick', @onGridClick)
|
2012-12-17 21:08:39 +01:00
|
|
|
|
2012-10-31 20:38:09 +01:00
|
|
|
# Default configuration
|
|
|
|
#
|
|
|
|
defaults:
|
2012-10-31 22:58:26 +01:00
|
|
|
barSizeRatio: 0.75
|
2012-10-31 20:38:09 +01:00
|
|
|
barGap: 3
|
2012-10-31 22:58:26 +01:00
|
|
|
barColors: [
|
2012-10-31 20:38:09 +01:00
|
|
|
'#0b62a4'
|
|
|
|
'#7a92a3'
|
|
|
|
'#4da74d'
|
|
|
|
'#afd8f8'
|
|
|
|
'#edc240'
|
|
|
|
'#cb4b4b'
|
|
|
|
'#9440ed'
|
2013-11-09 18:23:07 +01:00
|
|
|
],
|
|
|
|
barOpacity: 1.0
|
|
|
|
barRadius: [0, 0, 0, 0]
|
2012-12-20 20:15:33 +01:00
|
|
|
xLabelMargin: 50
|
2014-07-14 00:15:21 +02:00
|
|
|
horizontal: false
|
2014-07-12 17:35:21 +02:00
|
|
|
shown: true
|
2012-10-31 22:58:26 +01:00
|
|
|
|
2012-10-31 20:38:09 +01:00
|
|
|
# Do any size-related calculations
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
calc: ->
|
|
|
|
@calcBars()
|
2012-12-17 21:08:39 +01:00
|
|
|
if @options.hideHover is false
|
|
|
|
@hover.update(@hoverContentForRow(@data.length - 1)...)
|
2012-10-31 22:58:26 +01:00
|
|
|
|
2012-10-31 20:38:09 +01:00
|
|
|
# calculate series data bars coordinates and sizes
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
calcBars: ->
|
2012-10-31 22:58:26 +01:00
|
|
|
for row, idx in @data
|
2014-07-14 00:15:21 +02:00
|
|
|
row._x = @xStart + @xSize * (idx + 0.5) / @data.length
|
2012-10-31 20:38:09 +01:00
|
|
|
row._y = for y in row.y
|
2012-11-06 08:48:23 +01:00
|
|
|
if y? then @transY(y) else null
|
2012-10-31 22:58:26 +01:00
|
|
|
|
2012-10-31 20:38:09 +01:00
|
|
|
# Draws the bar chart.
|
|
|
|
#
|
|
|
|
draw: ->
|
2013-06-23 23:47:44 +02:00
|
|
|
@drawXAxis() if @options.axes in [true, 'both', 'x']
|
2012-10-31 20:38:09 +01:00
|
|
|
@drawSeries()
|
2012-10-31 22:58:26 +01:00
|
|
|
|
2012-10-31 20:38:09 +01:00
|
|
|
# draw the x-axis labels
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
drawXAxis: ->
|
|
|
|
# draw x axis labels
|
2014-07-14 00:15:21 +02:00
|
|
|
if not @options.horizontal
|
|
|
|
basePos = @getXAxisLabelY()
|
|
|
|
else
|
|
|
|
basePos = @getYAxisLabelX()
|
|
|
|
|
2012-10-31 20:38:09 +01:00
|
|
|
prevLabelMargin = null
|
2013-05-09 23:15:05 +02:00
|
|
|
prevAngleMargin = null
|
2012-10-31 22:58:26 +01:00
|
|
|
for i in [0...@data.length]
|
|
|
|
row = @data[@data.length - 1 - i]
|
2014-07-14 00:15:21 +02:00
|
|
|
if not @options.horizontal
|
|
|
|
label = @drawXAxisLabel(row._x, basePos, row.label)
|
|
|
|
else
|
|
|
|
label = @drawYAxisLabel(basePos, row._x - 0.5 * @options.gridTextSize, row.label)
|
|
|
|
|
|
|
|
|
|
|
|
if not @options.horizontal
|
|
|
|
angle = @options.xLabelAngle
|
|
|
|
else
|
|
|
|
angle = 0
|
|
|
|
|
2013-05-09 23:15:05 +02:00
|
|
|
textBox = label.getBBox()
|
2014-07-14 00:15:21 +02:00
|
|
|
label.transform("r#{-angle}")
|
2012-10-31 20:38:09 +01:00
|
|
|
labelBox = label.getBBox()
|
2013-05-09 23:15:05 +02:00
|
|
|
label.transform("t0,#{labelBox.height / 2}...")
|
2014-07-14 00:15:21 +02:00
|
|
|
|
|
|
|
|
|
|
|
if angle != 0
|
2013-05-09 23:15:05 +02:00
|
|
|
offset = -0.5 * textBox.width *
|
2014-07-14 00:15:21 +02:00
|
|
|
Math.cos(angle * Math.PI / 180.0)
|
2013-05-09 23:15:05 +02:00
|
|
|
label.transform("t#{offset},0...")
|
2014-07-14 00:15:21 +02:00
|
|
|
|
|
|
|
|
|
|
|
if not @options.horizontal
|
|
|
|
startPos = labelBox.x
|
|
|
|
size = labelBox.width
|
|
|
|
maxSize = @el.width()
|
|
|
|
else
|
|
|
|
startPos = labelBox.y
|
|
|
|
size = labelBox.height
|
|
|
|
maxSize = @el.height()
|
|
|
|
|
2013-05-09 23:15:05 +02:00
|
|
|
# try to avoid overlaps
|
|
|
|
if (not prevLabelMargin? or
|
2014-07-14 00:15:21 +02:00
|
|
|
prevLabelMargin >= startPos + size or
|
|
|
|
prevAngleMargin? and prevAngleMargin >= startPos) and
|
|
|
|
startPos >= 0 and (startPos + size) < maxSize
|
|
|
|
if angle != 0
|
2013-05-09 23:15:05 +02:00
|
|
|
margin = 1.25 * @options.gridTextSize /
|
2014-07-14 00:15:21 +02:00
|
|
|
Math.sin(angle * Math.PI / 180.0)
|
|
|
|
prevAngleMargin = startPos - margin
|
|
|
|
if not @options.horizontal
|
|
|
|
prevLabelMargin = startPos - @options.xLabelMargin
|
|
|
|
else
|
|
|
|
prevLabelMargin = startPos
|
|
|
|
|
2012-10-31 20:38:09 +01:00
|
|
|
else
|
2013-05-09 23:15:05 +02:00
|
|
|
label.remove()
|
2012-10-31 22:58:26 +01:00
|
|
|
|
2014-07-14 00:15:21 +02:00
|
|
|
# get the Y position of a label on the X axis
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
getXAxisLabelY: ->
|
|
|
|
@bottom + (@options.xAxisLabelTopPadding || @options.padding / 2)
|
|
|
|
|
2012-10-31 20:38:09 +01:00
|
|
|
# draw the data series
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
drawSeries: ->
|
2014-07-14 00:15:21 +02:00
|
|
|
groupWidth = @xSize / @options.data.length
|
|
|
|
|
2014-07-12 17:35:21 +02:00
|
|
|
if @options.stacked
|
|
|
|
numBars = 1
|
|
|
|
else
|
|
|
|
numBars = 0
|
|
|
|
for i in [0..@options.ykeys.length]
|
|
|
|
if @hasToShow(i)
|
|
|
|
numBars += 1
|
|
|
|
|
2012-10-31 22:58:26 +01:00
|
|
|
barWidth = (groupWidth * @options.barSizeRatio - @options.barGap * (numBars - 1)) / numBars
|
2014-01-30 00:57:57 +01:00
|
|
|
barWidth = Math.min(barWidth, @options.barSize) if @options.barSize
|
|
|
|
spaceLeft = groupWidth - barWidth * numBars - @options.barGap * (numBars - 1)
|
|
|
|
leftPadding = spaceLeft / 2
|
2012-10-31 22:58:26 +01:00
|
|
|
zeroPos = if @ymin <= 0 and @ymax >= 0 then @transY(0) else null
|
|
|
|
@bars = for row, idx in @data
|
2012-11-20 02:23:11 +01:00
|
|
|
lastTop = 0
|
2012-10-31 22:58:26 +01:00
|
|
|
for ypos, sidx in row._y
|
2014-07-12 17:35:21 +02:00
|
|
|
if not @hasToShow(sidx)
|
|
|
|
continue
|
2012-10-31 22:58:26 +01:00
|
|
|
if ypos != null
|
|
|
|
if zeroPos
|
|
|
|
top = Math.min(ypos, zeroPos)
|
|
|
|
bottom = Math.max(ypos, zeroPos)
|
2012-10-31 20:38:09 +01:00
|
|
|
else
|
2012-10-31 22:58:26 +01:00
|
|
|
top = ypos
|
|
|
|
bottom = @bottom
|
2012-11-20 02:23:11 +01:00
|
|
|
|
2014-07-14 00:15:21 +02:00
|
|
|
left = @xStart + idx * groupWidth + leftPadding
|
2012-11-20 02:23:11 +01:00
|
|
|
left += sidx * (barWidth + @options.barGap) unless @options.stacked
|
|
|
|
size = bottom - top
|
|
|
|
|
2013-11-16 00:53:37 +01:00
|
|
|
if @options.verticalGridCondition and @options.verticalGridCondition(row.x)
|
2014-07-15 14:57:37 +02:00
|
|
|
if not @options.horizontal
|
|
|
|
@drawBar(@xStart + idx * groupWidth, @yEnd, groupWidth, @ySize, @options.verticalGridColor, @options.verticalGridOpacity, @options.barRadius)
|
|
|
|
else
|
|
|
|
@drawBar(@yStart, @xStart + idx * groupWidth, @ySize, groupWidth, @options.verticalGridColor, @options.verticalGridOpacity, @options.barRadius)
|
|
|
|
|
2013-11-16 00:53:37 +01:00
|
|
|
|
2012-11-20 02:23:11 +01:00
|
|
|
top -= lastTop if @options.stacked
|
2014-07-14 00:15:21 +02:00
|
|
|
if not @options.horizontal
|
|
|
|
@drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar'),
|
|
|
|
@options.barOpacity, @options.barRadius)
|
2014-07-15 14:57:37 +02:00
|
|
|
lastTop += size
|
2014-07-14 00:15:21 +02:00
|
|
|
else
|
|
|
|
@drawBar(top, left, size, barWidth, @colorFor(row, sidx, 'bar'),
|
|
|
|
@options.barOpacity, @options.barRadius)
|
2014-07-15 14:57:37 +02:00
|
|
|
lastTop -= size
|
2012-11-20 02:23:11 +01:00
|
|
|
|
2014-07-14 00:15:21 +02:00
|
|
|
|
2012-10-31 22:58:26 +01:00
|
|
|
else
|
|
|
|
null
|
|
|
|
|
2012-11-06 09:18:19 +01:00
|
|
|
# @private
|
|
|
|
#
|
|
|
|
# @param row [Object] row data
|
|
|
|
# @param sidx [Number] series index
|
2012-12-17 21:08:39 +01:00
|
|
|
# @param type [String] "bar", "hover" or "label"
|
2012-11-06 09:18:19 +01:00
|
|
|
colorFor: (row, sidx, type) ->
|
|
|
|
if typeof @options.barColors is 'function'
|
|
|
|
r = { x: row.x, y: row.y[sidx], label: row.label }
|
|
|
|
s = { index: sidx, key: @options.ykeys[sidx], label: @options.labels[sidx] }
|
|
|
|
@options.barColors.call(@, r, s, type)
|
|
|
|
else
|
|
|
|
@options.barColors[sidx % @options.barColors.length]
|
2012-12-17 21:08:39 +01:00
|
|
|
|
2013-06-17 19:46:40 +02:00
|
|
|
# hit test - returns the index of the row at the given x-coordinate
|
2012-12-17 21:08:39 +01:00
|
|
|
#
|
2014-07-14 00:15:21 +02:00
|
|
|
hitTest: (x, y) ->
|
2013-02-06 08:35:54 +01:00
|
|
|
return null if @data.length == 0
|
2014-07-14 00:15:21 +02:00
|
|
|
if not @options.horizontal
|
|
|
|
pos = x
|
|
|
|
else
|
|
|
|
pos = y
|
|
|
|
|
|
|
|
pos = Math.max(Math.min(pos, @xEnd), @xStart)
|
2012-12-17 21:08:39 +01:00
|
|
|
Math.min(@data.length - 1,
|
2014-07-14 00:15:21 +02:00
|
|
|
Math.floor((pos - @xStart) / (@xSize / @data.length)))
|
2012-12-17 21:08:39 +01:00
|
|
|
|
2012-12-31 12:02:14 +01:00
|
|
|
# click on grid event handler
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
onGridClick: (x, y) =>
|
2014-07-29 16:37:15 +02:00
|
|
|
index = @hitTest(x, y)
|
2013-11-09 21:34:48 +01:00
|
|
|
@fire 'click', index, @data[index].src, x, y
|
2012-12-31 12:02:14 +01:00
|
|
|
|
2012-12-19 23:23:36 +01:00
|
|
|
# hover movement event handler
|
2012-12-17 21:08:39 +01:00
|
|
|
#
|
2012-12-19 23:23:36 +01:00
|
|
|
# @private
|
2012-12-17 21:08:39 +01:00
|
|
|
onHoverMove: (x, y) =>
|
2014-07-14 00:15:21 +02:00
|
|
|
index = @hitTest(x, y)
|
2012-12-17 21:08:39 +01:00
|
|
|
@hover.update(@hoverContentForRow(index)...)
|
|
|
|
|
2012-12-19 23:23:36 +01:00
|
|
|
# hover out event handler
|
|
|
|
#
|
|
|
|
# @private
|
2012-12-17 21:08:39 +01:00
|
|
|
onHoverOut: =>
|
2013-05-12 18:12:59 +02:00
|
|
|
if @options.hideHover isnt false
|
2012-12-17 21:08:39 +01:00
|
|
|
@hover.hide()
|
|
|
|
|
|
|
|
# hover content for a point
|
|
|
|
#
|
|
|
|
# @private
|
|
|
|
hoverContentForRow: (index) ->
|
2013-04-14 11:25:14 +02:00
|
|
|
row = @data[index]
|
|
|
|
content = "<div class='morris-hover-row-label'>#{row.label}</div>"
|
|
|
|
for y, j in row.y
|
2014-07-10 18:54:29 +02:00
|
|
|
if @options.labels[j] is false
|
|
|
|
continue
|
|
|
|
|
2013-04-14 11:25:14 +02:00
|
|
|
content += """
|
|
|
|
<div class='morris-hover-point' style='color: #{@colorFor(row, j, 'label')}'>
|
|
|
|
#{@options.labels[j]}:
|
2014-07-15 17:55:56 +02:00
|
|
|
#{@yLabelFormat(y, j)}
|
2013-04-14 11:25:14 +02:00
|
|
|
</div>
|
|
|
|
"""
|
2012-12-19 23:23:36 +01:00
|
|
|
if typeof @options.hoverCallback is 'function'
|
2013-11-09 21:34:48 +01:00
|
|
|
content = @options.hoverCallback(index, @options, content, row.src)
|
2014-07-14 00:15:21 +02:00
|
|
|
|
|
|
|
if not @options.horizontal
|
|
|
|
x = @left + (index + 0.5) * @width / @data.length
|
|
|
|
[content, x]
|
|
|
|
else
|
|
|
|
x = @left + 0.5 * @width
|
|
|
|
y = @top + (index + 0.5) * @height / @data.length
|
|
|
|
[content, x, y, true]
|
2012-12-31 19:09:38 +01:00
|
|
|
|
|
|
|
drawXAxisLabel: (xPos, yPos, text) ->
|
|
|
|
label = @raphael.text(xPos, yPos, text)
|
|
|
|
.attr('font-size', @options.gridTextSize)
|
2013-05-12 17:25:06 +02:00
|
|
|
.attr('font-family', @options.gridTextFamily)
|
|
|
|
.attr('font-weight', @options.gridTextWeight)
|
2012-12-31 19:09:38 +01:00
|
|
|
.attr('fill', @options.gridTextColor)
|
|
|
|
|
2013-11-09 18:23:07 +01:00
|
|
|
drawBar: (xPos, yPos, width, height, barColor, opacity, radiusArray) ->
|
|
|
|
maxRadius = Math.max(radiusArray...)
|
|
|
|
if maxRadius == 0 or maxRadius > height
|
2013-10-24 22:44:27 +02:00
|
|
|
path = @raphael.rect(xPos, yPos, width, height)
|
|
|
|
else
|
2013-11-09 18:23:07 +01:00
|
|
|
path = @raphael.path @roundedRect(xPos, yPos, width, height, radiusArray)
|
2013-10-24 22:44:27 +02:00
|
|
|
path
|
2012-12-31 19:09:38 +01:00
|
|
|
.attr('fill', barColor)
|
2013-10-24 22:44:27 +02:00
|
|
|
.attr('fill-opacity', opacity)
|
2013-11-10 23:09:01 +01:00
|
|
|
.attr('stroke', 'none')
|
2013-10-24 22:44:27 +02:00
|
|
|
|
|
|
|
roundedRect: (x, y, w, h, r = [0,0,0,0]) ->
|
2013-11-09 18:23:07 +01:00
|
|
|
[ "M", x, r[0] + y, "Q", x, y, x + r[0], y,
|
|
|
|
"L", x + w - r[1], y, "Q", x + w, y, x + w, y + r[1],
|
|
|
|
"L", x + w, y + h - r[2], "Q", x + w, y + h, x + w - r[2], y + h,
|
|
|
|
"L", x + r[3], y + h, "Q", x, y + h, x, y + h - r[3], "Z" ]
|
2013-10-24 22:44:27 +02:00
|
|
|
|