import Ember from 'ember';
import { getMousePoint } from '../utils/nf/svg-dom';
import computed from 'ember-new-computed';
var { on, observer } = Ember;
export default Ember.Mixin.create({
/**
Gets or sets the tracking mode of the component.
Possible values are:
- 'none': no tracking behavior
- 'hover': only track while mouse hover
- 'snap-last': track while mouse hover, but snap to the last data element when not hovering
- 'snap-first': track while mouse hover, but snap to the first data element when not hovering
- 'selected-hover': The same as `'hover'` tracking mode, but only when the compononent is
{{#crossLink "mixins.graph-selectable-graphic/selected:property"}}{{/crossLink}}
- 'selected-snap-last': The same as `'snap-last'` tracking mode, but only when the compononent is
{{#crossLink "mixins.graph-selectable-graphic/selected:property"}}{{/crossLink}}
- 'selected-snap-first': The same as `'snap-first'` tracking mode, but only when the compononent is
{{#crossLink "mixins.graph-selectable-graphic/selected:property"}}{{/crossLink}}
@property trackingMode
@type String
@default 'none'
*/
trackingMode: 'none',
/**
The radius of the tracking dot in pixels
@property trackingDotRadius
@type {number}
@default 2.5
*/
trackingDotRadius: 2.5,
/**
The action to send on `didTrack`.
@property didTrack
@type String
@default null
*/
didTrack: null,
/**
The value of the data that is being tracked by the component.
@property trackedData
@type {Object} an object with the following values:
- point: an { x, y } pair for the exact px coordinates inside the graph-content
- graphX: domain x value at mouse position
- graphY: domain y value at mouse position
- x: nearest x data value
- y: nearest y data value
- data: nearest raw data
- renderX: domain x value to render a tracking dot at (stacked areas are offset)
- renderY: domain x value to render a tracking dot at (stacked areas are offset)
- mouseX: mouse x position in pixels
- mouseY: mouse y position in pixels
@default null
*/
trackedData: null,
/**
The value of the data that is being tracked by the component, ONLY if the
graph-content is currently being hovered.
@property hoverData
@type {Object} an object with the following values:
- point: an { x, y } pair for the exact px coordinates inside the graph-content
- graphX: domain x value at mouse position
- graphY: domain y value at mouse position
- x: nearest x data value
- y: nearest y data value
- data: nearest raw data
- renderX: domain x value to render a tracking dot at (stacked areas are offset)
- renderY: domain x value to render a tracking dot at (stacked areas are offset)
- mouseX: mouse x position in pixels
- mouseY: mouse y position in pixels
@default null
*/
hoverData: null,
_showTrackingDot: true,
/**
Gets or sets whether the tracking dot should be shown at all.
@property showTrackingDot
@type {boolean}
@default true
*/
showTrackingDot: computed('trackedData', {
get() {
return Boolean(this._showTrackingDot && this.get('trackedData'));
},
set(value) {
this._showTrackingDot = value;
}
}),
/**
Observes changes to tracked data and sends the
didTrack action.
@method _trackedDataChanged
@private
*/
_trackedDataChanged: Ember.observer('trackedData', function(){
var trackedData = this.get('trackedData');
this.set('hoverData', this._hovered ? trackedData : null);
if(this.get('didTrack')) {
this.sendAction('didTrack', {
x: trackedData.x,
y: trackedData.y,
data: trackedData.data,
source: this,
graph: this.get('graph'),
});
}
}),
_cleanup: function(){
if(this._onHoverCleanup) {
this._onHoverCleanup();
}
if(this._onEndCleanup) {
this._onEndCleanup();
}
},
_updateTrackingHandling() {
var { trackingMode, selected } = this.getProperties('trackingMode', 'selected');
this._cleanup();
switch(trackingMode) {
case 'hover':
this._onHoverTrack();
this._onEndUntrack();
break;
case 'snap-first':
this._onHoverTrack();
this._onEndSnapFirst();
break;
case 'snap-last':
this._onHoverTrack();
this._onEndSnapLast();
break;
case 'selected-hover':
if(selected) {
this._onHoverTrack();
this._onEndUntrack();
}
break;
case 'selected-snap-first':
if(selected) {
this._onHoverTrack();
this._onEndSnapFirst();
}
break;
case 'selected-snap-last':
if(selected) {
this._onHoverTrack();
this._onEndSnapLast();
}
break;
}
},
_onHoverTrack() {
var content = this._content;
var mousemoveHandler = e => {
this._hovered = true;
var evt = this._getEventObject(e);
this.set('trackedData', evt);
};
content.on('mousemove', mousemoveHandler);
this._onHoverCleanup = () => {
content.off('mousemove', mousemoveHandler);
};
},
_hovered: false,
_onEndUntrack() {
var content = this._content;
var mouseoutHandler = () => {
this.set('trackedData', null);
};
content.on('mouseout', mouseoutHandler);
this._onEndCleanup = () => {
content.off('mouseout', mouseoutHandler);
};
if(!this._hovered) {
this.set('trackedData', null);
}
},
_onEndSnapLast() {
var content = this._content;
var mouseoutHandler = () => {
this._hovered = false;
this.set('trackedData', this.get('lastVisibleData'));
};
var changeHandler = () => {
if(!this._hovered) {
this.set('trackedData', this.get('lastVisibleData'));
}
};
content.on('mouseout', mouseoutHandler);
this.addObserver('lastVisibleData', this, changeHandler);
this._onEndCleanup = () => {
content.off('mouseout', mouseoutHandler);
this.removeObserver('lastVisibleData', this, changeHandler);
};
changeHandler();
},
_onEndSnapFirst() {
var content = this._content;
var mouseoutHandler = () => {
this._hovered = false;
this.set('trackedData', this.get('firstVisibleData'));
};
var changeHandler = () => {
if(!this._hovered) {
this.set('trackedData', this.get('firstVisibleData'));
}
};
content.on('mouseout', mouseoutHandler);
this.addObserver('firstVisibleData', this, changeHandler);
this._onEndCleanup = () => {
content.off('mouseout', mouseoutHandler);
this.removeObserver('firstVisibleData', this, changeHandler);
};
changeHandler();
},
_trackingModeChanged: on('init', observer('trackingMode', 'selected', function() {
Ember.run.once(this, this._updateTrackingHandling);
})),
_getEventObject(e) {
var { xScale, yScale } = this.getProperties('xScale', 'yScale');
var content = this._content;
var point = getMousePoint(content[0], e);
var graphX = xScale.invert(point.x);
var graphY = yScale.invert(point.y);
var near = this.getDataNearXRange(point.x);
if(!near) {
return {
point,
graphX,
graphY,
mouseX: point.x,
mouseY: point.y,
};
}
var { x, y, data, renderX, renderY } = near;
return {
point,
graphX,
graphY,
x,
y,
data,
renderX,
renderY,
mouseX: point.x,
mouseY: point.y
};
},
didInsertElement() {
this._super.apply(arguments);
this._content = this.$().parents('.nf-graph-content');
},
willDestroyElement() {
this._super.apply(arguments);
this._cleanup();
}
});