WIP: connecting hover object to grid subclasses.

This commit is contained in:
Olly Smith 2012-12-11 22:00:22 +00:00
parent e0691b93d3
commit 77ee0468e6
7 changed files with 87 additions and 366 deletions

View File

@ -5,10 +5,6 @@ class Morris.Bar extends Morris.Grid
init: ->
@cumulative = @options.stacked
@hoverConfigure @options.hoverOptions
postInit: ->
@hoverInit()
# Default configuration
#
@ -30,7 +26,6 @@ class Morris.Bar extends Morris.Grid
# @private
calc: ->
@calcBars()
@hoverCalculateMargins()
# calculate series data bars coordinates and sizes
#
@ -114,7 +109,3 @@ class Morris.Bar extends Morris.Grid
@options.barColors.call(@, r, s, type)
else
@options.barColors[sidx % @options.barColors.length]
hoverGetPosition: (index) ->
[x, y] = super(index)
[x, (@top + @bottom)/2 - @hoverHeight/2]

View File

@ -36,6 +36,12 @@ class Morris.Grid extends Morris.EventEmitter
# load data
@setData @options.data
# hover
unless @options.hideHover is 'always'
@hover = new Morris.Hover
parent: @el
@initHover()
@postInit() if @postInit
# Default options
@ -46,6 +52,7 @@ class Morris.Grid extends Morris.EventEmitter
gridStrokeWidth: 0.5
gridTextColor: '#888'
gridTextSize: 12
hideHover: false
numLines: 5
padding: 25
parseTime: true
@ -261,119 +268,29 @@ class Morris.Grid extends Morris.EventEmitter
# Hover stuff
#
#
hoverConfigure: (options) ->
@hoverOptions = $.extend {}, @hoverDefaults, options ? {}
initHover: ->
if @hover?
@el.bind 'mousemove', (evt) =>
@updateHover evt.pageX, evt.pageY
hoverInit: ->
if @hoverOptions.enableHover
@hover = @hoverBuild()
@hoverBindEvents()
@hoverShow(if @hoverOptions.hideHover then null else @data.length - 1)
hoverDefaults:
enableHover: true
popupClass: "morris-popup"
hideHover: false
allowOverflow: false
pointMargin: 10
hoverFill: (index, row) -> @hoverFill(index, row)
hoverBindEvents: ->
@el.mousemove (evt) =>
@hoverUpdate evt.pageX
if @hoverOptions.hideHover
@el.mouseout (evt) =>
@hoverShow null
touchHandler = (evt) =>
touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0]
@hoverUpdate touch.pageX
return touch
@el.bind 'touchstart', touchHandler
@el.bind 'touchmove', touchHandler
@el.bind 'touchend', touchHandler
@hover.mousemove (evt) -> evt.stopPropagation()
@hover.mouseout (evt) -> evt.stopPropagation()
@hover.bind 'touchstart', (evt) -> evt.stopPropagation()
@hover.bind 'touchmove', (evt) -> evt.stopPropagation()
@hover.bind 'touchend', (evt) -> evt.stopPropagation()
hoverCalculateMargins: ->
@hoverMargins = for i in [1...@data.length]
@left + i * @width / @data.length
hoverBuild: ->
hover = $ "<div/>"
hover.addClass "#{@hoverOptions.popupClass} js-morris-popup"
hover.appendTo @el
hover.hide()
hover
hoverUpdate: (x) ->
x -= @el.offset().left
for hoverIndex in [0...@hoverMargins.length]
break if @hoverMargins[hoverIndex] > x
@hoverShow hoverIndex
hoverShow: (index) ->
if index isnt null
@hover.html("")
@hoverOptions.hoverFill.call(@, index, @data[index])
@hoverPosition(index)
@fire "hover.show", index
@hover.show()
if not index?
@hoverHide()
hoverHide: ->
if @options.hideHover
@el.bind 'mouseout', (evt) =>
@hover.hide()
colorFor: (row, i, type) -> "inherit"
yLabelFormat: (label) -> Morris.commas(label)
@el.bind 'touchstart touchmove touchend', (evt) =>
touch = evt.originalEvent.touches[0] or evt.originalEvent.changedTouches[0]
@updateHover touch.pageX, touch.pageY
return touch
hoverPosition: (index) ->
[x, y] = @hoverGetPosition index
@hover.css
top: "#{@el.offset().top + y}px"
left: "#{@el.offset().left + x}px"
hoverGetPosition: (index) ->
row = @data[index]
@hoverWidth = @hover.outerWidth(true)
@hoverHeight = @hover.outerHeight(true)
miny = y = Math.min.apply(null, (y for y in row._y when y isnt null).concat(@bottom))
x = row._x - @hoverWidth/2
y = miny
y = y - @hoverHeight - @hoverOptions.pointMargin
unless @hoverOptions.allowOverflow
if x < @left
x = row._x + @hoverOptions.pointMargin
else if x > @right - @hoverWidth
x = row._x - @hoverWidth - @hoverOptions.pointMargin
y = Math.max y, @top
y = Math.min y, (@bottom - @hoverHeight - @hoverOptions.pointMargin)
if y - miny < @hoverWidth + @hoverOptions.pointMargin
y = miny + @hoverOptions.pointMargin
[x, y]
hoverFill: (index, row) ->
xLabel = $ "<h4/>"
xLabel.text row.label
xLabel.appendTo @hover
for y, i in row.y
yLabel = $ "<p/>"
yLabel.css "color", @colorFor(row, i, "hover")
yLabel.text "#{@options.labels[i]}: #{@yLabelFormat(y)}"
yLabel.appendTo @hover
hitTest: (x, y) -> null
updateHover: (x, y) ->
offset = @el.offset()
x -= offset.left
y -= offset.top
hit = hitTest(x, y)
if hit?
@hover.update(hit...)
# Parse a date into a javascript timestamp
#

View File

@ -9,17 +9,16 @@ class Morris.Hover
@el = $ "<div class='#{@options.class}'></div>"
@el.hide()
@options.parent.append(@el)
@el.bind 'mousemove mouseout touchstart touchmove touchend', (evt) ->
evt.stopPropagation()
update: (x, y, data) ->
@render(data)
update: (html, x, y) ->
@html(html)
@show()
@moveTo(x, y)
render: (data) ->
if typeof @options.content is 'function'
@el.html @options.content(data)
else
@el.html @options.content
html: (content) ->
@el.html(content)
moveTo: (x, y) ->
parentWidth = @options.parent.innerWidth()

View File

@ -10,8 +10,6 @@ class Morris.Line extends Morris.Grid
@pointGrow = Raphael.animation r: @options.pointSize + 3, 25, 'linear'
@pointShrink = Raphael.animation r: @options.pointSize, 25, 'linear'
@hoverConfigure @options.hoverOptions
# column hilight events
if @options.hilight
@prevHilight = null
@ -28,9 +26,6 @@ class Morris.Line extends Morris.Grid
@el.bind 'touchmove', touchHandler
@el.bind 'touchend', touchHandler
postInit: ->
@hoverInit()
# Default configuration
#
defaults:
@ -60,7 +55,6 @@ class Morris.Line extends Morris.Grid
# @private
calc: ->
@calcPoints()
@hoverCalculateMargins()
@generatePaths()
@calcHilightMargins()
@ -79,9 +73,6 @@ class Morris.Line extends Morris.Grid
calcHilightMargins: ->
@hilightMargins = ((r._x + @data[i]._x) / 2 for r, i in @data.slice(1))
hoverCalculateMargins: ->
@hoverMargins = ((r._x + @data[i]._x) / 2 for r, i in @data.slice(1))
# generate paths for series lines
#
# @private

241
morris.js
View File

@ -91,6 +91,12 @@
this.init();
}
this.setData(this.options.data);
if (this.options.hideHover !== 'always') {
this.hover = new Morris.Hover({
parent: this.el
});
this.initHover();
}
if (this.postInit) {
this.postInit();
}
@ -102,6 +108,7 @@
gridStrokeWidth: 0.5,
gridTextColor: '#888',
gridTextSize: 12,
hideHover: false,
numLines: 5,
padding: 25,
parseTime: true,
@ -362,182 +369,39 @@
return "" + this.options.preUnits + (Morris.commas(label)) + this.options.postUnits;
};
Grid.prototype.hoverConfigure = function(options) {
return this.hoverOptions = $.extend({}, this.hoverDefaults, options != null ? options : {});
};
Grid.prototype.hoverInit = function() {
if (this.hoverOptions.enableHover) {
this.hover = this.hoverBuild();
this.hoverBindEvents();
return this.hoverShow(this.hoverOptions.hideHover ? null : this.data.length - 1);
}
};
Grid.prototype.hoverDefaults = {
enableHover: true,
popupClass: "morris-popup",
hideHover: false,
allowOverflow: false,
pointMargin: 10,
hoverFill: function(index, row) {
return this.hoverFill(index, row);
}
};
Grid.prototype.hoverBindEvents = function() {
var touchHandler,
_this = this;
this.el.mousemove(function(evt) {
return _this.hoverUpdate(evt.pageX);
Grid.prototype.initHover = function() {
var _this = this;
if (this.hover != null) {
this.el.bind('mousemove', function(evt) {
return _this.updateHover(evt.pageX, evt.pageY);
});
if (this.hoverOptions.hideHover) {
this.el.mouseout(function(evt) {
return _this.hoverShow(null);
if (this.options.hideHover) {
this.el.bind('mouseout', function(evt) {
return _this.hover.hide();
});
}
touchHandler = function(evt) {
return this.el.bind('touchstart touchmove touchend', function(evt) {
var touch;
touch = evt.originalEvent.touches[0] || evt.originalEvent.changedTouches[0];
_this.hoverUpdate(touch.pageX);
_this.updateHover(touch.pageX, touch.pageY);
return touch;
};
this.el.bind('touchstart', touchHandler);
this.el.bind('touchmove', touchHandler);
this.el.bind('touchend', touchHandler);
this.hover.mousemove(function(evt) {
return evt.stopPropagation();
});
this.hover.mouseout(function(evt) {
return evt.stopPropagation();
});
this.hover.bind('touchstart', function(evt) {
return evt.stopPropagation();
});
this.hover.bind('touchmove', function(evt) {
return evt.stopPropagation();
});
return this.hover.bind('touchend', function(evt) {
return evt.stopPropagation();
});
};
Grid.prototype.hoverCalculateMargins = function() {
var i;
return this.hoverMargins = (function() {
var _i, _ref, _results;
_results = [];
for (i = _i = 1, _ref = this.data.length; 1 <= _ref ? _i < _ref : _i > _ref; i = 1 <= _ref ? ++_i : --_i) {
_results.push(this.left + i * this.width / this.data.length);
}
return _results;
}).call(this);
};
Grid.prototype.hoverBuild = function() {
var hover;
hover = $("<div/>");
hover.addClass("" + this.hoverOptions.popupClass + " js-morris-popup");
hover.appendTo(this.el);
hover.hide();
return hover;
};
Grid.prototype.hoverUpdate = function(x) {
var hoverIndex, _i, _ref;
x -= this.el.offset().left;
for (hoverIndex = _i = 0, _ref = this.hoverMargins.length; 0 <= _ref ? _i < _ref : _i > _ref; hoverIndex = 0 <= _ref ? ++_i : --_i) {
if (this.hoverMargins[hoverIndex] > x) {
break;
}
}
return this.hoverShow(hoverIndex);
};
Grid.prototype.hoverShow = function(index) {
if (index !== null) {
this.hover.html("");
this.hoverOptions.hoverFill.call(this, index, this.data[index]);
this.hoverPosition(index);
this.fire("hover.show", index);
this.hover.show();
}
if (!(index != null)) {
return this.hoverHide();
}
};
Grid.prototype.hoverHide = function() {
return this.hover.hide();
Grid.prototype.hitTest = function(x, y) {
return null;
};
Grid.prototype.colorFor = function(row, i, type) {
return "inherit";
};
Grid.prototype.yLabelFormat = function(label) {
return Morris.commas(label);
};
Grid.prototype.hoverPosition = function(index) {
var x, y, _ref;
_ref = this.hoverGetPosition(index), x = _ref[0], y = _ref[1];
return this.hover.css({
top: "" + (this.el.offset().top + y) + "px",
left: "" + (this.el.offset().left + x) + "px"
});
};
Grid.prototype.hoverGetPosition = function(index) {
var miny, row, x, y;
row = this.data[index];
this.hoverWidth = this.hover.outerWidth(true);
this.hoverHeight = this.hover.outerHeight(true);
miny = y = Math.min.apply(null, ((function() {
var _i, _len, _ref, _results;
_ref = row._y;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
y = _ref[_i];
if (y !== null) {
_results.push(y);
Grid.prototype.updateHover = function(x, y) {
var hit, offset, _ref;
offset = this.el.offset();
x -= offset.left;
y -= offset.top;
hit = hitTest(x, y);
if (hit != null) {
return (_ref = this.hover).update.apply(_ref, hit);
}
}
return _results;
})()).concat(this.bottom));
x = row._x - this.hoverWidth / 2;
y = miny;
y = y - this.hoverHeight - this.hoverOptions.pointMargin;
if (!this.hoverOptions.allowOverflow) {
if (x < this.left) {
x = row._x + this.hoverOptions.pointMargin;
} else if (x > this.right - this.hoverWidth) {
x = row._x - this.hoverWidth - this.hoverOptions.pointMargin;
}
y = Math.max(y, this.top);
y = Math.min(y, this.bottom - this.hoverHeight - this.hoverOptions.pointMargin);
if (y - miny < this.hoverWidth + this.hoverOptions.pointMargin) {
y = miny + this.hoverOptions.pointMargin;
}
}
return [x, y];
};
Grid.prototype.hoverFill = function(index, row) {
var i, xLabel, y, yLabel, _i, _len, _ref, _results;
xLabel = $("<h4/>");
xLabel.text(row.label);
xLabel.appendTo(this.hover);
_ref = row.y;
_results = [];
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
y = _ref[i];
yLabel = $("<p/>");
yLabel.css("color", this.colorFor(row, i, "hover"));
yLabel.text("" + this.options.labels[i] + ": " + (this.yLabelFormat(y)));
_results.push(yLabel.appendTo(this.hover));
}
return _results;
};
return Grid;
@ -615,20 +479,19 @@
this.el = $("<div class='" + this.options["class"] + "'></div>");
this.el.hide();
this.options.parent.append(this.el);
this.el.bind('mousemove mouseout touchstart touchmove touchend', function(evt) {
return evt.stopPropagation();
});
}
Hover.prototype.update = function(x, y, data) {
this.render(data);
Hover.prototype.update = function(html, x, y) {
this.html(html);
this.show();
return this.moveTo(x, y);
};
Hover.prototype.render = function(data) {
if (typeof this.options.content === 'function') {
return this.el.html(this.options.content(data));
} else {
return this.el.html(this.options.content);
}
Hover.prototype.html = function(content) {
return this.el.html(content);
};
Hover.prototype.moveTo = function(x, y) {
@ -690,7 +553,6 @@
this.pointShrink = Raphael.animation({
r: this.options.pointSize
}, 25, 'linear');
this.hoverConfigure(this.options.hoverOptions);
if (this.options.hilight) {
this.prevHilight = null;
this.el.mousemove(function(evt) {
@ -713,10 +575,6 @@
}
};
Line.prototype.postInit = function() {
return this.hoverInit();
};
Line.prototype.defaults = {
lineWidth: 3,
pointSize: 4,
@ -734,7 +592,6 @@
Line.prototype.calc = function() {
this.calcPoints();
this.hoverCalculateMargins();
this.generatePaths();
return this.calcHilightMargins();
};
@ -778,20 +635,6 @@
}).call(this);
};
Line.prototype.hoverCalculateMargins = function() {
var i, r;
return this.hoverMargins = (function() {
var _i, _len, _ref, _results;
_ref = this.data.slice(1);
_results = [];
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
r = _ref[i];
_results.push((r._x + this.data[i]._x) / 2);
}
return _results;
}).call(this);
};
Line.prototype.generatePaths = function() {
var c, coords, i, r, smooth;
return this.paths = (function() {
@ -1244,12 +1087,7 @@
}
Bar.prototype.init = function() {
this.cumulative = this.options.stacked;
return this.hoverConfigure(this.options.hoverOptions);
};
Bar.prototype.postInit = function() {
return this.hoverInit();
return this.cumulative = this.options.stacked;
};
Bar.prototype.defaults = {
@ -1259,8 +1097,7 @@
};
Bar.prototype.calc = function() {
this.calcBars();
return this.hoverCalculateMargins();
return this.calcBars();
};
Bar.prototype.calcBars = function() {
@ -1380,12 +1217,6 @@
}
};
Bar.prototype.hoverGetPosition = function(index) {
var x, y, _ref;
_ref = Bar.__super__.hoverGetPosition.call(this, index), x = _ref[0], y = _ref[1];
return [x, (this.top + this.bottom) / 2 - this.hoverHeight / 2];
};
return Bar;
})(Morris.Grid);

2
morris.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -3,11 +3,9 @@ describe "Morris.Hover", ->
describe "with dummy content", ->
beforeEach ->
@parent = $('<div style="width:200px;height:180px"></div>')
parent = $('<div style="width:200px;height:180px"></div>')
.appendTo($('#test'))
@hover = new Morris.Hover(
parent: @parent,
content: '<div style="width:84px;height:84px"></div>')
@hover = new Morris.Hover(parent: parent)
@element = $('#test .morris-popup')
it "should initialise a hidden, empty popup", ->
@ -26,8 +24,14 @@ describe "Morris.Hover", ->
@hover.hide()
@element.should.be.hidden
describe "#html", ->
it "should replace the contents of the element", ->
@hover.html('<div>Foobarbaz</div>')
@element.should.have.html('<div>Foobarbaz</div>')
describe "#moveTo", ->
beforeEach -> @hover.render()
beforeEach ->
@hover.html('<div style="width:84px;height:84px"></div>')
it "should place the popup directly above the given point", ->
@hover.moveTo(100, 150)
@ -49,23 +53,11 @@ describe "Morris.Hover", ->
@element.should.have.css('left', '50px')
@element.should.have.css('top', '40px')
describe "#render", ->
it "should take content from a string", ->
hover = new Morris.Hover(parent: $('#test'), content: 'Hello, World!')
hover.render()
$('#test .morris-popup').html().should.equal 'Hello, World!'
it "should take content from a method", ->
hover = new Morris.Hover(parent: $('#test'), content: (x) -> "Hello, #{x}!")
hover.render('Tester')
$('#test .morris-popup').html().should.equal 'Hello, Tester!'
describe "#update", ->
it "should update content, show and reposition the popup", ->
hover = new Morris.Hover
parent: $('#test')
content: (x) -> "<div style='width:84px;height:84px'>Hello, #{x}!</div>"
hover.update(150, 200, 'Everyone')
hover = new Morris.Hover(parent: $('#test'))
html = "<div style='width:84px;height:84px'>Hello, Everyone!</div>"
hover.update(html, 150, 200)
el = $('#test .morris-popup')
el.should.have.css('left', '100px')
el.should.have.css('top', '90px')