mirror of
https://github.com/morrisjs/morris.js.git
synced 2024-11-13 07:11:12 +01:00
063957657c
Uses perceptualdiff to catch regressions / changes in charts as they are displayed by a browser (in this case, phantomjs). Currently has exemplaries for basic line, area, bar and stacked bar charts.
203 lines
6.2 KiB
CoffeeScript
203 lines
6.2 KiB
CoffeeScript
class Morris.Bar extends Morris.Grid
|
|
constructor: (options) ->
|
|
return new Morris.Bar(options) unless (@ instanceof Morris.Bar)
|
|
super($.extend {}, options, parseTime: false)
|
|
|
|
init: ->
|
|
@cumulative = @options.stacked
|
|
|
|
if @options.hideHover isnt 'always'
|
|
@hover = new Morris.Hover(parent: @el)
|
|
@on('hovermove', @onHoverMove)
|
|
@on('hoverout', @onHoverOut)
|
|
@on('gridclick', @onGridClick)
|
|
|
|
# Default configuration
|
|
#
|
|
defaults:
|
|
barSizeRatio: 0.75
|
|
barGap: 3
|
|
barColors: [
|
|
'#0b62a4'
|
|
'#7a92a3'
|
|
'#4da74d'
|
|
'#afd8f8'
|
|
'#edc240'
|
|
'#cb4b4b'
|
|
'#9440ed'
|
|
],
|
|
barOpacity: 1.0
|
|
barRadius: [0, 0, 0, 0]
|
|
xLabelMargin: 50
|
|
|
|
# Do any size-related calculations
|
|
#
|
|
# @private
|
|
calc: ->
|
|
@calcBars()
|
|
if @options.hideHover is false
|
|
@hover.update(@hoverContentForRow(@data.length - 1)...)
|
|
|
|
# calculate series data bars coordinates and sizes
|
|
#
|
|
# @private
|
|
calcBars: ->
|
|
for row, idx in @data
|
|
row._x = @left + @width * (idx + 0.5) / @data.length
|
|
row._y = for y in row.y
|
|
if y? then @transY(y) else null
|
|
|
|
# Draws the bar chart.
|
|
#
|
|
draw: ->
|
|
@drawXAxis() if @options.axes in [true, 'both', 'x']
|
|
@drawSeries()
|
|
|
|
# draw the x-axis labels
|
|
#
|
|
# @private
|
|
drawXAxis: ->
|
|
# draw x axis labels
|
|
ypos = @bottom + (@options.xAxisLabelTopPadding || @options.padding / 2)
|
|
prevLabelMargin = null
|
|
prevAngleMargin = null
|
|
for i in [0...@data.length]
|
|
row = @data[@data.length - 1 - i]
|
|
label = @drawXAxisLabel(row._x, ypos, row.label)
|
|
textBox = label.getBBox()
|
|
label.transform("r#{-@options.xLabelAngle}")
|
|
labelBox = label.getBBox()
|
|
label.transform("t0,#{labelBox.height / 2}...")
|
|
if @options.xLabelAngle != 0
|
|
offset = -0.5 * textBox.width *
|
|
Math.cos(@options.xLabelAngle * Math.PI / 180.0)
|
|
label.transform("t#{offset},0...")
|
|
# try to avoid overlaps
|
|
if (not prevLabelMargin? or
|
|
prevLabelMargin >= labelBox.x + labelBox.width or
|
|
prevAngleMargin? and prevAngleMargin >= labelBox.x) and
|
|
labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width()
|
|
if @options.xLabelAngle != 0
|
|
margin = 1.25 * @options.gridTextSize /
|
|
Math.sin(@options.xLabelAngle * Math.PI / 180.0)
|
|
prevAngleMargin = labelBox.x - margin
|
|
prevLabelMargin = labelBox.x - @options.xLabelMargin
|
|
else
|
|
label.remove()
|
|
|
|
# draw the data series
|
|
#
|
|
# @private
|
|
drawSeries: ->
|
|
groupWidth = @width / @options.data.length
|
|
numBars = if @options.stacked? then 1 else @options.ykeys.length
|
|
barWidth = (groupWidth * @options.barSizeRatio - @options.barGap * (numBars - 1)) / numBars
|
|
leftPadding = groupWidth * (1 - @options.barSizeRatio) / 2
|
|
zeroPos = if @ymin <= 0 and @ymax >= 0 then @transY(0) else null
|
|
@bars = for row, idx in @data
|
|
lastTop = 0
|
|
for ypos, sidx in row._y
|
|
if ypos != null
|
|
if zeroPos
|
|
top = Math.min(ypos, zeroPos)
|
|
bottom = Math.max(ypos, zeroPos)
|
|
else
|
|
top = ypos
|
|
bottom = @bottom
|
|
|
|
left = @left + idx * groupWidth + leftPadding
|
|
left += sidx * (barWidth + @options.barGap) unless @options.stacked
|
|
size = bottom - top
|
|
|
|
top -= lastTop if @options.stacked
|
|
@drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar'),
|
|
@options.barOpacity, @options.barRadius)
|
|
|
|
lastTop += size
|
|
else
|
|
null
|
|
|
|
# @private
|
|
#
|
|
# @param row [Object] row data
|
|
# @param sidx [Number] series index
|
|
# @param type [String] "bar", "hover" or "label"
|
|
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]
|
|
|
|
# hit test - returns the index of the row at the given x-coordinate
|
|
#
|
|
hitTest: (x) ->
|
|
return null if @data.length == 0
|
|
x = Math.max(Math.min(x, @right), @left)
|
|
Math.min(@data.length - 1,
|
|
Math.floor((x - @left) / (@width / @data.length)))
|
|
|
|
# click on grid event handler
|
|
#
|
|
# @private
|
|
onGridClick: (x, y) =>
|
|
index = @hitTest(x)
|
|
@fire 'click', index, @data[index].src, x, y
|
|
|
|
# hover movement event handler
|
|
#
|
|
# @private
|
|
onHoverMove: (x, y) =>
|
|
index = @hitTest(x)
|
|
@hover.update(@hoverContentForRow(index)...)
|
|
|
|
# hover out event handler
|
|
#
|
|
# @private
|
|
onHoverOut: =>
|
|
if @options.hideHover isnt false
|
|
@hover.hide()
|
|
|
|
# hover content for a point
|
|
#
|
|
# @private
|
|
hoverContentForRow: (index) ->
|
|
row = @data[index]
|
|
content = "<div class='morris-hover-row-label'>#{row.label}</div>"
|
|
for y, j in row.y
|
|
content += """
|
|
<div class='morris-hover-point' style='color: #{@colorFor(row, j, 'label')}'>
|
|
#{@options.labels[j]}:
|
|
#{@yLabelFormat(y)}
|
|
</div>
|
|
"""
|
|
if typeof @options.hoverCallback is 'function'
|
|
content = @options.hoverCallback(index, @options, content, row.src)
|
|
x = @left + (index + 0.5) * @width / @data.length
|
|
[content, x]
|
|
|
|
drawXAxisLabel: (xPos, yPos, text) ->
|
|
label = @raphael.text(xPos, yPos, text)
|
|
.attr('font-size', @options.gridTextSize)
|
|
.attr('font-family', @options.gridTextFamily)
|
|
.attr('font-weight', @options.gridTextWeight)
|
|
.attr('fill', @options.gridTextColor)
|
|
|
|
drawBar: (xPos, yPos, width, height, barColor, opacity, radiusArray) ->
|
|
maxRadius = Math.max(radiusArray...)
|
|
if maxRadius == 0 or maxRadius > height
|
|
path = @raphael.rect(xPos, yPos, width, height)
|
|
else
|
|
path = @raphael.path @roundedRect(xPos, yPos, width, height, radiusArray)
|
|
path
|
|
.attr('fill', barColor)
|
|
.attr('fill-opacity', opacity)
|
|
.attr('stroke', 'none')
|
|
|
|
roundedRect: (x, y, w, h, r = [0,0,0,0]) ->
|
|
[ "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" ]
|
|
|