diff --git a/grunt.js b/grunt.js index 494e69a..eb615cf 100644 --- a/grunt.js +++ b/grunt.js @@ -15,6 +15,7 @@ module.exports = function (grunt) { concat: { 'build/morris.coffee': [ 'lib/morris.coffee', + 'lib/morris.svg.coffee', 'lib/morris.grid.coffee', 'lib/morris.hover.coffee', 'lib/morris.line.coffee', diff --git a/lib/morris.area.coffee b/lib/morris.area.coffee index a654ac3..4d107ba 100644 --- a/lib/morris.area.coffee +++ b/lib/morris.area.coffee @@ -26,9 +26,7 @@ class Morris.Area extends Morris.Line path = @paths[i] if path isnt null path = path + "L#{@transX(@xmax)},#{@bottom}L#{@transX(@xmin)},#{@bottom}Z" - @r.path(path) - .attr('fill', @fillForSeries(i)) - .attr('stroke-width', 0) + @morrisSVG.drawFilledPath(path, @fillForSeries(i)) super() fillForSeries: (i) -> diff --git a/lib/morris.bar.coffee b/lib/morris.bar.coffee index d45daec..9898343 100644 --- a/lib/morris.bar.coffee +++ b/lib/morris.bar.coffee @@ -59,9 +59,7 @@ class Morris.Bar extends Morris.Grid prevLabelMargin = null for i in [0...@data.length] row = @data[@data.length - 1 - i] - label = @r.text(row._x, ypos, row.label) - .attr('font-size', @options.gridTextSize) - .attr('fill', @options.gridTextColor) + label = @morrisSVG.drawXAxisLabel(row._x, ypos, row.label) labelBox = label.getBBox() # ensure a minimum of `xLabelMargin` pixels between labels, and ensure # labels don't overflow the container @@ -96,9 +94,7 @@ class Morris.Bar extends Morris.Grid size = bottom - top top -= lastTop if @options.stacked - @r.rect(left, top, barWidth, size) - .attr('fill', @colorFor(row, sidx, 'bar')) - .attr('stroke-width', 0) + @morrisSVG.drawBar(left, top, barWidth, size, @colorFor(row, sidx, 'bar')) lastTop += size else diff --git a/lib/morris.donut.coffee b/lib/morris.donut.coffee index a87a31c..4d1ff7e 100644 --- a/lib/morris.donut.coffee +++ b/lib/morris.donut.coffee @@ -54,7 +54,7 @@ class Morris.Donut redraw: -> @el.empty() - @r = new Raphael(@el[0]) + @morrisSVG = new Morris.SVG(@el[0], @options) cx = @el.width() / 2 cy = @el.height() / 2 @@ -72,13 +72,13 @@ class Morris.Donut for d in @data next = last + min + C * (d.value / total) seg = new Morris.DonutSegment(cx, cy, w*2, w, last, next, @options.colors[idx % @options.colors.length], d) - seg.render @r + seg.render @morrisSVG @segments.push seg seg.on 'hover', @select last = next idx += 1 - @text1 = @r.text(cx, cy - 10, '').attr('font-size': 15, 'font-weight': 800) - @text2 = @r.text(cx, cy + 10, '').attr('font-size': 14) + @text1 = @morrisSVG.drawEmptyDonutLabel(cx, cy - 10, 15, 800) + @text2 = @morrisSVG.drawEmptyDonutLabel(cx, cy + 10, 14) max_value = Math.max.apply(null, d.value for d in @data) idx = 0 for d in @data @@ -147,11 +147,9 @@ class Morris.DonutSegment extends Morris.EventEmitter "M#{ix0},#{iy0}" + "A#{r},#{r},0,#{@long},0,#{ix1},#{iy1}") - render: (raphael) -> - @arc = raphael.path(@hilight).attr(stroke: @color, 'stroke-width': 2, opacity: 0) - @seg = raphael.path(@path) - .attr(fill: @color, stroke: 'white', 'stroke-width': 3) - .hover(=> @fire('hover', @)) + render: (morrisSVG) -> + @arc = morrisSVG.drawDonutArc(@hilight, @color) + @seg = morrisSVG.drawDonutSegment(@path, @color, => @fire('hover', @)) select: => unless @selected diff --git a/lib/morris.grid.coffee b/lib/morris.grid.coffee index b7b46e4..ef9ca26 100644 --- a/lib/morris.grid.coffee +++ b/lib/morris.grid.coffee @@ -27,6 +27,7 @@ class Morris.Grid extends Morris.EventEmitter # the raphael drawing instance @r = new Raphael(@el[0]) + @morrisSVG = new Morris.SVG(@el[0], @options) # some redraw stuff @elementWidth = null @@ -227,7 +228,7 @@ class Morris.Grid extends Morris.EventEmitter # If you need to re-size your charts, call this method after changing the # size of the container element. redraw: -> - @r.clear() + @morrisSVG.clear() @_calc() @drawGrid() @drawGoals() @@ -238,16 +239,12 @@ class Morris.Grid extends Morris.EventEmitter # drawGoals: -> for goal, i in @options.goals - @r.path("M#{@left},#{@transY(goal)}H#{@left + @width}") - .attr('stroke', @options.goalLineColors[i % @options.goalLineColors.length]) - .attr('stroke-width', @options.goalStrokeWidth) + @morrisSVG.drawGoal("M#{@left},#{@transY(goal)}H#{@left + @width}") # draw events vertical lines drawEvents: -> for event, i in @events - @r.path("M#{@transX(event)},#{@bottom}V#{@top}") - .attr('stroke', @options.eventLineColors[i % @options.eventLineColors.length]) - .attr('stroke-width', @options.eventStrokeWidth) + @morrisSVG.drawEvent("M#{@transX(event)},#{@bottom}V#{@top}") # draw y axis labels, horizontal lines # @@ -259,22 +256,14 @@ class Morris.Grid extends Morris.EventEmitter v = parseFloat(lineY.toFixed(@precision)) y = @transY(v) if @options.axes - @r.text(@left - @options.padding / 2, y, @yAxisFormat(v)) - .attr('font-size', @options.gridTextSize) - .attr('fill', @options.gridTextColor) - .attr('text-anchor', 'end') + @morrisSVG.drawYAxisLabel(@left - @options.padding / 2, y, @yAxisFormat(v)) if @options.grid - @r.path("M#{@left},#{y}H#{@left + @width}") - .attr('stroke', @options.gridLineColor) - .attr('stroke-width', @options.gridStrokeWidth) + @morrisSVG.drawGridLine("M#{@left},#{y}H#{@left + @width}") # @private # measureText: (text, fontSize = 12) -> - tt = @r.text(100, 100, text).attr('font-size', fontSize) - ret = tt.getBBox() - tt.remove() - ret + @morrisSVG.measureText(text, fontSize) # @private # diff --git a/lib/morris.line.coffee b/lib/morris.line.coffee index 55061a1..9ec69a8 100644 --- a/lib/morris.line.coffee +++ b/lib/morris.line.coffee @@ -138,9 +138,7 @@ class Morris.Line extends Morris.Grid ypos = @bottom + @options.gridTextSize * 1.25 prevLabelMargin = null drawLabel = (labelText, xpos) => - label = @r.text(@transX(xpos), ypos, labelText) - .attr('font-size', @options.gridTextSize) - .attr('fill', @options.gridTextColor) + label = @morrisSVG.drawXAxisLabel(@transX(xpos), ypos, labelText) labelBox = label.getBBox() # ensure a minimum of `xLabelMargin` pixels between labels, and ensure # labels don't overflow the container @@ -170,17 +168,12 @@ class Morris.Line extends Morris.Grid for i in [@options.ykeys.length-1..0] path = @paths[i] if path isnt null - @r.path(path) - .attr('stroke', @colorFor(row, i, 'line')) - .attr('stroke-width', @options.lineWidth) + @morrisSVG.drawLinePath(path, @colorFor(row, i, 'line')) #row isn't available here? @seriesPoints = ([] for i in [0...@options.ykeys.length]) for i in [@options.ykeys.length-1..0] for row in @data if row._y[i]? - circle = @r.circle(row._x, row._y[i], @options.pointSize) - .attr('fill', @colorFor(row, i, 'point')) - .attr('stroke-width', @strokeWidthForSeries(i)) - .attr('stroke', @strokeForSeries(i)) + circle = @morrisSVG.drawLinePoint(row._x, row._y[i], @options.pointSize, @colorFor(row, i, 'point'), i) else circle = null @seriesPoints[i].push(circle) @@ -245,14 +238,6 @@ class Morris.Line extends Morris.Grid @seriesPoints[i][index].animate @pointGrow @prevHilight = index - # @private - strokeWidthForSeries: (index) -> - @options.pointWidths[index % @options.pointWidths.length] - - # @private - strokeForSeries: (index) -> - @options.pointStrokeColors[index % @options.pointStrokeColors.length] - colorFor: (row, sidx, type) -> if typeof @options.lineColors is 'function' @options.lineColors.call(@, row, sidx, type) diff --git a/lib/morris.svg.coffee b/lib/morris.svg.coffee new file mode 100644 index 0000000..4c89f65 --- /dev/null +++ b/lib/morris.svg.coffee @@ -0,0 +1,82 @@ +class Morris.SVG + + constructor: (element, options) -> + @raphael = new Raphael(element) + @options = options + + clear: -> + @raphael.clear() + + drawGoal: (path) -> + @raphael.path(path) + .attr('stroke', @options.goalLineColors[i % @options.goalLineColors.length]) + .attr('stroke-width', @options.goalStrokeWidth) + + drawEvent: (path) -> + @raphael.path(path) + .attr('stroke', @options.eventLineColors[i % @options.eventLineColors.length]) + .attr('stroke-width', @options.eventStrokeWidth) + + drawYAxisLabel: (xPos, yPos, text) -> + @raphael.text(xPos, yPos, text) + .attr('font-size', @options.gridTextSize) + .attr('fill', @options.gridTextColor) + .attr('text-anchor', 'end') + + drawXAxisLabel: (xPos, yPos, text) -> + label = @raphael.text(xPos, yPos, text) + .attr('font-size', @options.gridTextSize) + .attr('fill', @options.gridTextColor) + + drawGridLine: (path) -> + @raphael.path(path) + .attr('stroke', @options.gridLineColor) + .attr('stroke-width', @options.gridStrokeWidth) + + measureText: (text, fontSize = 12) -> + tt = @raphael.text(100, 100, text).attr('font-size', fontSize) + ret = tt.getBBox() + tt.remove() + ret + + drawEmptyDonutLabel: (xPos, yPos, fontSize, fontWeight) -> + text = @raphael.text(xPos, yPos, '').attr('font-size', fontSize) + text.attr('font-weight', fontWeight) if fontWeight? + return text + + drawDonutArc: (path, color) -> + @raphael.path(path).attr(stroke: color, 'stroke-width': 2, opacity: 0) + + drawDonutSegment: (path, color, hoverFunction) -> + @raphael.path(path) + .attr(fill: color, stroke: 'white', 'stroke-width': 3) + .hover(hoverFunction) + + drawFilledPath: (path, fill) -> + @raphael.path(path) + .attr('fill', fill) + .attr('stroke-width', 0) + + drawLinePath: (path, lineColor) -> + @raphael.path(path) + .attr('stroke', lineColor) + .attr('stroke-width', @options.lineWidth) + + drawLinePoint: (xPos, yPos, size, pointColor, lineIndex) -> + circle = @raphael.circle(xPos, yPos, size) + .attr('fill', pointColor) + .attr('stroke-width', @strokeWidthForSeries(lineIndex)) + .attr('stroke', @strokeForSeries(lineIndex)) + + drawBar: (xPos, yPos, width, height, barColor) -> + @raphael.rect(xPos, yPos, width, height) + .attr('fill', barColor) + .attr('stroke-width', 0) + + # @private + strokeWidthForSeries: (index) -> + @options.pointWidths[index % @options.pointWidths.length] + + # @private + strokeForSeries: (index) -> + @options.pointStrokeColors[index % @options.pointStrokeColors.length] diff --git a/spec/lib/line/line_spec.coffee b/spec/lib/line/line_spec.coffee index f0accfe..48bdb35 100644 --- a/spec/lib/line/line_spec.coffee +++ b/spec/lib/line/line_spec.coffee @@ -25,10 +25,10 @@ describe 'Morris.Line', -> pointStrokeColors: [red, blue] pointWidths: [1, 2] pointFillColors: [null, red] - chart.strokeWidthForSeries(0).should.equal 1 - chart.strokeForSeries(0).should.equal red - chart.strokeWidthForSeries(1).should.equal 2 - chart.strokeForSeries(1).should.equal blue + chart.morrisSVG.strokeWidthForSeries(0).should.equal 1 + chart.morrisSVG.strokeForSeries(0).should.equal red + chart.morrisSVG.strokeWidthForSeries(1).should.equal 2 + chart.morrisSVG.strokeForSeries(1).should.equal blue chart.colorFor(chart.data[0], 0, 'point').should.equal chart.colorFor(chart.data[0], 0, 'line') chart.colorFor(chart.data[1], 1, 'point').should.equal red