mirror of
https://github.com/morrisjs/morris.js.git
synced 2024-09-21 10:41:32 +02:00
Merge pull request #455 from JelteF/feature-horizontal-bar
Add horizontal barchart option
This commit is contained in:
commit
62b4290315
@ -29,6 +29,7 @@ class Morris.Bar extends Morris.Grid
|
|||||||
barOpacity: 1.0
|
barOpacity: 1.0
|
||||||
barRadius: [0, 0, 0, 0]
|
barRadius: [0, 0, 0, 0]
|
||||||
xLabelMargin: 50
|
xLabelMargin: 50
|
||||||
|
horizontal: false
|
||||||
|
|
||||||
# Do any size-related calculations
|
# Do any size-related calculations
|
||||||
#
|
#
|
||||||
@ -43,7 +44,7 @@ class Morris.Bar extends Morris.Grid
|
|||||||
# @private
|
# @private
|
||||||
calcBars: ->
|
calcBars: ->
|
||||||
for row, idx in @data
|
for row, idx in @data
|
||||||
row._x = @left + @width * (idx + 0.5) / @data.length
|
row._x = @xStart + @xSize * (idx + 0.5) / @data.length
|
||||||
row._y = for y in row.y
|
row._y = for y in row.y
|
||||||
if y? then @transY(y) else null
|
if y? then @transY(y) else null
|
||||||
|
|
||||||
@ -58,39 +59,78 @@ class Morris.Bar extends Morris.Grid
|
|||||||
# @private
|
# @private
|
||||||
drawXAxis: ->
|
drawXAxis: ->
|
||||||
# draw x axis labels
|
# draw x axis labels
|
||||||
ypos = @bottom + (@options.xAxisLabelTopPadding || @options.padding / 2)
|
if not @options.horizontal
|
||||||
|
basePos = @getXAxisLabelY()
|
||||||
|
else
|
||||||
|
basePos = @getYAxisLabelX()
|
||||||
|
|
||||||
prevLabelMargin = null
|
prevLabelMargin = null
|
||||||
prevAngleMargin = null
|
prevAngleMargin = null
|
||||||
for i in [0...@data.length]
|
for i in [0...@data.length]
|
||||||
row = @data[@data.length - 1 - i]
|
row = @data[@data.length - 1 - i]
|
||||||
label = @drawXAxisLabel(row._x, ypos, row.label)
|
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
|
||||||
|
|
||||||
textBox = label.getBBox()
|
textBox = label.getBBox()
|
||||||
label.transform("r#{-@options.xLabelAngle}")
|
label.transform("r#{-angle}")
|
||||||
labelBox = label.getBBox()
|
labelBox = label.getBBox()
|
||||||
label.transform("t0,#{labelBox.height / 2}...")
|
label.transform("t0,#{labelBox.height / 2}...")
|
||||||
if @options.xLabelAngle != 0
|
|
||||||
|
|
||||||
|
if angle != 0
|
||||||
offset = -0.5 * textBox.width *
|
offset = -0.5 * textBox.width *
|
||||||
Math.cos(@options.xLabelAngle * Math.PI / 180.0)
|
Math.cos(angle * Math.PI / 180.0)
|
||||||
label.transform("t#{offset},0...")
|
label.transform("t#{offset},0...")
|
||||||
|
|
||||||
|
|
||||||
|
if not @options.horizontal
|
||||||
|
startPos = labelBox.x
|
||||||
|
size = labelBox.width
|
||||||
|
maxSize = @el.width()
|
||||||
|
else
|
||||||
|
startPos = labelBox.y
|
||||||
|
size = labelBox.height
|
||||||
|
maxSize = @el.height()
|
||||||
|
|
||||||
# try to avoid overlaps
|
# try to avoid overlaps
|
||||||
if (not prevLabelMargin? or
|
if (not prevLabelMargin? or
|
||||||
prevLabelMargin >= labelBox.x + labelBox.width or
|
prevLabelMargin >= startPos + size or
|
||||||
prevAngleMargin? and prevAngleMargin >= labelBox.x) and
|
prevAngleMargin? and prevAngleMargin >= startPos) and
|
||||||
labelBox.x >= 0 and (labelBox.x + labelBox.width) < @el.width()
|
startPos >= 0 and (startPos + size) < maxSize
|
||||||
if @options.xLabelAngle != 0
|
if angle != 0
|
||||||
margin = 1.25 * @options.gridTextSize /
|
margin = 1.25 * @options.gridTextSize /
|
||||||
Math.sin(@options.xLabelAngle * Math.PI / 180.0)
|
Math.sin(angle * Math.PI / 180.0)
|
||||||
prevAngleMargin = labelBox.x - margin
|
prevAngleMargin = startPos - margin
|
||||||
prevLabelMargin = labelBox.x - @options.xLabelMargin
|
if not @options.horizontal
|
||||||
|
prevLabelMargin = startPos - @options.xLabelMargin
|
||||||
|
else
|
||||||
|
prevLabelMargin = startPos
|
||||||
|
|
||||||
else
|
else
|
||||||
label.remove()
|
label.remove()
|
||||||
|
|
||||||
|
# get the Y position of a label on the X axis
|
||||||
|
#
|
||||||
|
# @private
|
||||||
|
getXAxisLabelY: ->
|
||||||
|
@bottom + (@options.xAxisLabelTopPadding || @options.padding / 2)
|
||||||
|
|
||||||
# draw the data series
|
# draw the data series
|
||||||
#
|
#
|
||||||
# @private
|
# @private
|
||||||
drawSeries: ->
|
drawSeries: ->
|
||||||
groupWidth = @width / @options.data.length
|
|
||||||
numBars = if @options.stacked then 1 else @options.ykeys.length
|
numBars = if @options.stacked then 1 else @options.ykeys.length
|
||||||
|
|
||||||
|
groupWidth = @xSize / @options.data.length
|
||||||
|
|
||||||
barWidth = (groupWidth * @options.barSizeRatio - @options.barGap * (numBars - 1)) / numBars
|
barWidth = (groupWidth * @options.barSizeRatio - @options.barGap * (numBars - 1)) / numBars
|
||||||
barWidth = Math.min(barWidth, @options.barSize) if @options.barSize
|
barWidth = Math.min(barWidth, @options.barSize) if @options.barSize
|
||||||
spaceLeft = groupWidth - barWidth * numBars - @options.barGap * (numBars - 1)
|
spaceLeft = groupWidth - barWidth * numBars - @options.barGap * (numBars - 1)
|
||||||
@ -107,7 +147,7 @@ class Morris.Bar extends Morris.Grid
|
|||||||
top = ypos
|
top = ypos
|
||||||
bottom = @bottom
|
bottom = @bottom
|
||||||
|
|
||||||
left = @left + idx * groupWidth + leftPadding
|
left = @xStart + idx * groupWidth + leftPadding
|
||||||
left += sidx * (barWidth + @options.barGap) unless @options.stacked
|
left += sidx * (barWidth + @options.barGap) unless @options.stacked
|
||||||
size = bottom - top
|
size = bottom - top
|
||||||
|
|
||||||
@ -115,10 +155,15 @@ class Morris.Bar extends Morris.Grid
|
|||||||
@drawBar(@left + idx * groupWidth, @top, groupWidth, Math.abs(@top - @bottom), @options.verticalGridColor, @options.verticalGridOpacity, @options.barRadius)
|
@drawBar(@left + idx * groupWidth, @top, groupWidth, Math.abs(@top - @bottom), @options.verticalGridColor, @options.verticalGridOpacity, @options.barRadius)
|
||||||
|
|
||||||
top -= lastTop if @options.stacked
|
top -= lastTop if @options.stacked
|
||||||
@drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar'),
|
if not @options.horizontal
|
||||||
@options.barOpacity, @options.barRadius)
|
@drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar'),
|
||||||
|
@options.barOpacity, @options.barRadius)
|
||||||
|
else
|
||||||
|
@drawBar(top, left, size, barWidth, @colorFor(row, sidx, 'bar'),
|
||||||
|
@options.barOpacity, @options.barRadius)
|
||||||
|
|
||||||
lastTop += size
|
lastTop += size
|
||||||
|
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
|
|
||||||
@ -137,11 +182,16 @@ class Morris.Bar extends Morris.Grid
|
|||||||
|
|
||||||
# hit test - returns the index of the row at the given x-coordinate
|
# hit test - returns the index of the row at the given x-coordinate
|
||||||
#
|
#
|
||||||
hitTest: (x) ->
|
hitTest: (x, y) ->
|
||||||
return null if @data.length == 0
|
return null if @data.length == 0
|
||||||
x = Math.max(Math.min(x, @right), @left)
|
if not @options.horizontal
|
||||||
|
pos = x
|
||||||
|
else
|
||||||
|
pos = y
|
||||||
|
|
||||||
|
pos = Math.max(Math.min(pos, @xEnd), @xStart)
|
||||||
Math.min(@data.length - 1,
|
Math.min(@data.length - 1,
|
||||||
Math.floor((x - @left) / (@width / @data.length)))
|
Math.floor((pos - @xStart) / (@xSize / @data.length)))
|
||||||
|
|
||||||
# click on grid event handler
|
# click on grid event handler
|
||||||
#
|
#
|
||||||
@ -154,7 +204,7 @@ class Morris.Bar extends Morris.Grid
|
|||||||
#
|
#
|
||||||
# @private
|
# @private
|
||||||
onHoverMove: (x, y) =>
|
onHoverMove: (x, y) =>
|
||||||
index = @hitTest(x)
|
index = @hitTest(x, y)
|
||||||
@hover.update(@hoverContentForRow(index)...)
|
@hover.update(@hoverContentForRow(index)...)
|
||||||
|
|
||||||
# hover out event handler
|
# hover out event handler
|
||||||
@ -179,8 +229,14 @@ class Morris.Bar extends Morris.Grid
|
|||||||
"""
|
"""
|
||||||
if typeof @options.hoverCallback is 'function'
|
if typeof @options.hoverCallback is 'function'
|
||||||
content = @options.hoverCallback(index, @options, content, row.src)
|
content = @options.hoverCallback(index, @options, content, row.src)
|
||||||
x = @left + (index + 0.5) * @width / @data.length
|
|
||||||
[content, x]
|
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]
|
||||||
|
|
||||||
drawXAxisLabel: (xPos, yPos, text) ->
|
drawXAxisLabel: (xPos, yPos, text) ->
|
||||||
label = @raphael.text(xPos, yPos, text)
|
label = @raphael.text(xPos, yPos, text)
|
||||||
|
@ -286,25 +286,74 @@ class Morris.Grid extends Morris.EventEmitter
|
|||||||
if @options.axes in [true, 'both', 'y']
|
if @options.axes in [true, 'both', 'y']
|
||||||
yLabelWidths = for gridLine in @grid
|
yLabelWidths = for gridLine in @grid
|
||||||
@measureText(@yAxisFormat(gridLine)).width
|
@measureText(@yAxisFormat(gridLine)).width
|
||||||
@left += Math.max(yLabelWidths...)
|
|
||||||
|
if not @options.horizontal
|
||||||
|
@left += Math.max(yLabelWidths...)
|
||||||
|
else
|
||||||
|
@bottom -= Math.max(yLabelWidths...)
|
||||||
|
|
||||||
if @options.axes in [true, 'both', 'x']
|
if @options.axes in [true, 'both', 'x']
|
||||||
|
if not @options.horizontal
|
||||||
|
angle = -@options.xLabelAngle
|
||||||
|
else
|
||||||
|
angle = -90
|
||||||
|
|
||||||
bottomOffsets = for i in [0...@data.length]
|
bottomOffsets = for i in [0...@data.length]
|
||||||
@measureText(@data[i].text, -@options.xLabelAngle).height
|
@measureText(@data[i].label, angle).height
|
||||||
@bottom -= Math.max(bottomOffsets...)
|
|
||||||
|
if not @options.horizontal
|
||||||
|
@bottom -= Math.max(bottomOffsets...)
|
||||||
|
else
|
||||||
|
@left += Math.max(bottomOffsets...)
|
||||||
|
|
||||||
@width = Math.max(1, @right - @left)
|
@width = Math.max(1, @right - @left)
|
||||||
@height = Math.max(1, @bottom - @top)
|
@height = Math.max(1, @bottom - @top)
|
||||||
@dx = @width / (@xmax - @xmin)
|
|
||||||
@dy = @height / (@ymax - @ymin)
|
if not @options.horizontal
|
||||||
|
@dx = @width / (@xmax - @xmin)
|
||||||
|
@dy = @height / (@ymax - @ymin)
|
||||||
|
|
||||||
|
@yStart = @bottom
|
||||||
|
@yEnd = @top
|
||||||
|
@xStart = @left
|
||||||
|
@xEnd = @right
|
||||||
|
|
||||||
|
@xSize = @width
|
||||||
|
@ySize = @height
|
||||||
|
else
|
||||||
|
@dx = @height / (@xmax - @xmin)
|
||||||
|
@dy = @width / (@ymax - @ymin)
|
||||||
|
|
||||||
|
@yStart = @left
|
||||||
|
@yEnd = @right
|
||||||
|
@xStart = @top
|
||||||
|
@xEnd = @bottom
|
||||||
|
|
||||||
|
@xSize = @height
|
||||||
|
@ySize = @width
|
||||||
|
|
||||||
@calc() if @calc
|
@calc() if @calc
|
||||||
|
|
||||||
# Quick translation helpers
|
# Quick translation helpers
|
||||||
#
|
#
|
||||||
transY: (y) -> @bottom - (y - @ymin) * @dy
|
transY: (y) ->
|
||||||
transX: (x) ->
|
if not @options.horizontal
|
||||||
if @data.length == 1
|
@bottom - (y - @ymin) * @dy
|
||||||
(@left + @right) / 2
|
|
||||||
else
|
else
|
||||||
@left + (x - @xmin) * @dx
|
@left + (y - @ymin) * @dy
|
||||||
|
transX: (x) ->
|
||||||
|
if @options.horizontal
|
||||||
|
start = @left
|
||||||
|
end = @right
|
||||||
|
else
|
||||||
|
start = @top
|
||||||
|
end = @bottom
|
||||||
|
|
||||||
|
if @data.length == 1
|
||||||
|
(start + end) / 2
|
||||||
|
else
|
||||||
|
start + (x - @xmin) * @dx
|
||||||
|
|
||||||
|
|
||||||
# Draw it!
|
# Draw it!
|
||||||
#
|
#
|
||||||
@ -342,16 +391,36 @@ class Morris.Grid extends Morris.EventEmitter
|
|||||||
else
|
else
|
||||||
"#{@options.preUnits}#{Morris.commas(label)}#{@options.postUnits}"
|
"#{@options.preUnits}#{Morris.commas(label)}#{@options.postUnits}"
|
||||||
|
|
||||||
|
# get the X position of a label on the Y axis
|
||||||
|
#
|
||||||
|
# @private
|
||||||
|
getYAxisLabelX: ->
|
||||||
|
@left - @options.padding / 2
|
||||||
|
|
||||||
|
|
||||||
# draw y axis labels, horizontal lines
|
# draw y axis labels, horizontal lines
|
||||||
#
|
#
|
||||||
drawGrid: ->
|
drawGrid: ->
|
||||||
return if @options.grid is false and @options.axes not in [true, 'both', 'y']
|
return if @options.grid is false and @options.axes not in [true, 'both', 'y']
|
||||||
|
|
||||||
|
if not @options.horizontal
|
||||||
|
basePos = @getYAxisLabelX()
|
||||||
|
else
|
||||||
|
basePos = @getXAxisLabelY()
|
||||||
|
|
||||||
for lineY in @grid
|
for lineY in @grid
|
||||||
y = @transY(lineY)
|
pos = @transY(lineY)
|
||||||
if @options.axes in [true, 'both', 'y']
|
if @options.axes in [true, 'both', 'y']
|
||||||
@drawYAxisLabel(@left - @options.padding / 2, y, @yAxisFormat(lineY))
|
if not @options.horizontal
|
||||||
|
@drawYAxisLabel(basePos, pos, @yAxisFormat(lineY))
|
||||||
|
else
|
||||||
|
@drawXAxisLabel(pos, basePos, @yAxisFormat(lineY))
|
||||||
|
|
||||||
if @options.grid
|
if @options.grid
|
||||||
@drawGridLine("M#{@left},#{y}H#{@left + @width}")
|
if not @options.horizontal
|
||||||
|
@drawGridLine("M#{@xStart},#{pos}H#{@xEnd}")
|
||||||
|
else
|
||||||
|
@drawGridLine("M#{pos},#{@xStart}V#{@xEnd}")
|
||||||
|
|
||||||
# draw goals horizontal lines
|
# draw goals horizontal lines
|
||||||
#
|
#
|
||||||
@ -367,12 +436,22 @@ class Morris.Grid extends Morris.EventEmitter
|
|||||||
@drawEvent(event, color)
|
@drawEvent(event, color)
|
||||||
|
|
||||||
drawGoal: (goal, color) ->
|
drawGoal: (goal, color) ->
|
||||||
@raphael.path("M#{@left},#{@transY(goal)}H#{@right}")
|
if not @options.horizontal
|
||||||
|
path = "M#{@xStart},#{@transY(goal)}H#{@xEnd}"
|
||||||
|
else
|
||||||
|
path = "M#{@transY(goal)},#{@xStart}V#{@xEnd}"
|
||||||
|
|
||||||
|
@raphael.path(path)
|
||||||
.attr('stroke', color)
|
.attr('stroke', color)
|
||||||
.attr('stroke-width', @options.goalStrokeWidth)
|
.attr('stroke-width', @options.goalStrokeWidth)
|
||||||
|
|
||||||
drawEvent: (event, color) ->
|
drawEvent: (event, color) ->
|
||||||
@raphael.path("M#{@transX(event)},#{@bottom}V#{@top}")
|
if not @options.horizontal
|
||||||
|
path = "M#{@transX(goal)},#{@yStart}V#{@yEnd}"
|
||||||
|
else
|
||||||
|
path = "M#{@yStart},#{@transX(goal)}H#{@yEnd}"
|
||||||
|
|
||||||
|
@raphael.path(path)
|
||||||
.attr('stroke', color)
|
.attr('stroke', color)
|
||||||
.attr('stroke-width', @options.eventStrokeWidth)
|
.attr('stroke-width', @options.eventStrokeWidth)
|
||||||
|
|
||||||
|
@ -10,29 +10,34 @@ class Morris.Hover
|
|||||||
@el.hide()
|
@el.hide()
|
||||||
@options.parent.append(@el)
|
@options.parent.append(@el)
|
||||||
|
|
||||||
update: (html, x, y) ->
|
update: (html, x, y, centre_y) ->
|
||||||
if not html
|
if not html
|
||||||
@hide()
|
@hide()
|
||||||
else
|
else
|
||||||
@html(html)
|
@html(html)
|
||||||
@show()
|
@show()
|
||||||
@moveTo(x, y)
|
@moveTo(x, y, centre_y)
|
||||||
|
|
||||||
html: (content) ->
|
html: (content) ->
|
||||||
@el.html(content)
|
@el.html(content)
|
||||||
|
|
||||||
moveTo: (x, y) ->
|
moveTo: (x, y, centre_y) ->
|
||||||
parentWidth = @options.parent.innerWidth()
|
parentWidth = @options.parent.innerWidth()
|
||||||
parentHeight = @options.parent.innerHeight()
|
parentHeight = @options.parent.innerHeight()
|
||||||
hoverWidth = @el.outerWidth()
|
hoverWidth = @el.outerWidth()
|
||||||
hoverHeight = @el.outerHeight()
|
hoverHeight = @el.outerHeight()
|
||||||
left = Math.min(Math.max(0, x - hoverWidth / 2), parentWidth - hoverWidth)
|
left = Math.min(Math.max(0, x - hoverWidth / 2), parentWidth - hoverWidth)
|
||||||
if y?
|
if y?
|
||||||
top = y - hoverHeight - 10
|
if centre_y is true
|
||||||
if top < 0
|
top = y - hoverHeight / 2
|
||||||
top = y + 10
|
if top < 0
|
||||||
if top + hoverHeight > parentHeight
|
top = 0
|
||||||
top = parentHeight / 2 - hoverHeight / 2
|
else
|
||||||
|
top = y - hoverHeight - 10
|
||||||
|
if top < 0
|
||||||
|
top = y + 10
|
||||||
|
if top + hoverHeight > parentHeight
|
||||||
|
top = parentHeight / 2 - hoverHeight / 2
|
||||||
else
|
else
|
||||||
top = parentHeight / 2 - hoverHeight / 2
|
top = parentHeight / 2 - hoverHeight / 2
|
||||||
@el.css(left: left + "px", top: parseInt(top) + "px")
|
@el.css(left: left + "px", top: parseInt(top) + "px")
|
||||||
|
Loading…
Reference in New Issue
Block a user