/**
* @class Ext.chart.interactions.ItemEdit
* @extends Ext.chart.interactions.ItemHighlight
*
* The 'itemedit' interaction allows the user to edit store data
* by dragging series items in the chart.
*
* The 'itemedit' interaction extends the
* {@link Ext.chart.interactions.ItemHighlight 'itemhighlight'} interaction,
* so it also acts like one. If you need both interactions in a single chart,
* 'itemedit' should be sufficient. Hovering/tapping will result in highlighting,
* and dragging will result in editing.
*/
Ext.define('Ext.chart.interactions.ItemEdit', {
extend: 'Ext.chart.interactions.ItemHighlight',
requires: [
'Ext.tip.ToolTip'
],
type: 'itemedit',
alias: 'interaction.itemedit',
isItemEdit: true,
config: {
/**
* @cfg {Object} [style=null]
* The style that will be applied to the series item on dragging.
* By default, series item will have no fill,
* and will have a dashed stroke of the same color.
*/
style: null,
/**
* @cfg {Function/String} [renderer=null]
* A function that returns style attributes for the item that's being dragged.
* This is useful if you want to give a visual feedback to the user when
* they dragged to a certain point.
*
* @param {Object} [data] The following properties are available:
*
* @param {Object} data.target The object containing the xField/xValue or/and
* yField/yValue properties, where the xField/yField specify the store records
* being edited and the xValue/yValue the target values to be set when
* the interaction ends. The object also contains the 'index' of the record
* being edited.
* @param {Object} data.style The style that is going to be used for the dragged item.
* The attributes returned by the renderer will be applied on top of this style.
* @param {Object} data.item The series item being dragged.
* This is actually the {@link Ext.chart.AbstractChart#highlightItem}.
*
* @return {Object} The style attributes to be set on the dragged item.
*/
renderer: null,
/**
* @cfg {Object/Boolean} [tooltip=true]
*/
tooltip: true,
gestures: {
dragstart: 'onDragStart',
drag: 'onDrag',
dragend: 'onDragEnd'
},
cursors: {
ewResize: 'ew-resize',
nsResize: 'ns-resize',
move: 'move'
}
/**
* @private
* @cfg {Boolean} [sticky=false]
*/
},
/**
* @event beginitemedit
* Fires when item edit operation (dragging) begins.
* @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
* @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
* @param {Object} item The item that is about to be edited.
*/
/**
* @event enditemedit
* Fires when item edit operation (dragging) ends.
* @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
* @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
* @param {Object} item The item that was edited.
* @param {Object} target The object containing target values the were used.
*/
item: null, // Item being edited.
applyTooltip: function(tooltip) {
var config;
if (tooltip) {
config = Ext.apply({}, tooltip, {
renderer: this.defaultTooltipRenderer,
constrainPosition: true,
shrinkWrapDock: true,
autoHide: true,
trackMouse: true,
mouseOffset: [20, 20]
});
tooltip = new Ext.tip.ToolTip(config);
}
return tooltip;
},
defaultTooltipRenderer: function(tooltip, item, target, e) {
var parts = [];
if (target.xField) {
parts.push(target.xField + ': ' + target.xValue);
}
if (target.yField) {
parts.push(target.yField + ': ' + target.yValue);
}
tooltip.setHtml(parts.join('<br>'));
},
onDragStart: function(e) {
var me = this,
chart = me.getChart(),
item = chart.getHighlightItem();
e.claimGesture();
if (item) {
chart.fireEvent('beginitemedit', chart, me, me.item = item);
// If ItemEdit interaction comes before other interactions
// in the chart's 'interactions' config, this will
// prevent other interactions hijacking the 'dragstart'
// event. We only stop event propagation is there's
// an item to edit under cursor/finger, otherwise we
// let other interactions (e.g. 'panzoom') handle the event.
return false;
}
},
onDrag: function(e) {
var me = this,
chart = me.getChart(),
item = chart.getHighlightItem(),
type = item && item.sprite.type;
if (item) {
switch (type) {
case 'barSeries':
return me.onDragBar(e);
case 'scatterSeries':
return me.onDragScatter(e);
}
}
},
highlight: function(item) {
var me = this,
chart = me.getChart(),
flipXY = chart.getFlipXY(),
cursors = me.getCursors(),
type = item && item.sprite.type,
style = chart.el.dom.style;
me.callParent([item]);
if (item) {
switch (type) {
case 'barSeries':
if (flipXY) {
style.cursor = cursors.ewResize;
}
else {
style.cursor = cursors.nsResize;
}
break;
case 'scatterSeries':
style.cursor = cursors.move;
break;
}
}
else {
chart.el.dom.style.cursor = 'default';
}
},
onDragBar: function(e) {
var me = this,
chart = me.getChart(),
isRtl = chart.getInherited().rtl,
flipXY = chart.isCartesian && chart.getFlipXY(),
item = chart.getHighlightItem(),
marker = item.sprite.getMarker('items'),
instance = marker.getMarkerFor(item.sprite.getId(), item.index),
surface = item.sprite.getSurface(),
surfaceRect = surface.getRect(),
xy = surface.getEventXY(e),
matrix = item.sprite.attr.matrix,
renderer = me.getRenderer(),
style, changes, params, positionY;
if (flipXY) {
positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
}
else {
positionY = surfaceRect[3] - xy[1];
}
style = {
x: instance.x,
y: positionY,
width: instance.width,
height: instance.height + (instance.y - positionY),
radius: instance.radius,
fillStyle: 'none',
lineDash: [4, 4],
zIndex: 100
};
Ext.apply(style, me.getStyle());
if (Ext.isArray(item.series.getYField())) { // stacked bars
positionY = positionY - instance.y - instance.height;
}
me.target = {
index: item.index,
yField: item.field,
yValue: (positionY - matrix.getDY()) / matrix.getYY()
};
params = [chart, {
target: me.target,
style: style,
item: item
}];
changes = Ext.callback(renderer, null, params, 0, chart);
if (changes) {
Ext.apply(style, changes);
}
// The interaction works by putting another series item instance
// under 'itemedit' ID with a slightly different style (default) or
// whatever style the user provided.
item.sprite.putMarker('items', style, 'itemedit');
me.showTooltip(e, me.target, item);
surface.renderFrame();
},
onDragScatter: function(e) {
var me = this,
chart = me.getChart(),
isRtl = chart.getInherited().rtl,
flipXY = chart.isCartesian && chart.getFlipXY(),
item = chart.getHighlightItem(),
marker = item.sprite.getMarker('markers'),
instance = marker.getMarkerFor(item.sprite.getId(), item.index),
surface = item.sprite.getSurface(),
surfaceRect = surface.getRect(),
xy = surface.getEventXY(e),
matrix = item.sprite.attr.matrix,
xAxis = item.series.getXAxis(),
isEditableX = xAxis && xAxis.getLayout().isContinuous,
renderer = me.getRenderer(),
style, changes, params,
positionX, positionY,
hintX, hintY;
if (flipXY) {
positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
}
else {
positionY = surfaceRect[3] - xy[1];
}
if (isEditableX) {
if (flipXY) {
positionX = surfaceRect[3] - xy[1];
}
else {
positionX = xy[0];
}
}
else {
positionX = instance.translationX;
}
if (isEditableX) {
hintX = xy[0];
hintY = xy[1];
}
else {
if (flipXY) {
hintX = xy[0];
hintY = instance.translationY; // no change
}
else {
hintX = instance.translationX;
hintY = xy[1]; // no change
}
}
style = {
translationX: hintX,
translationY: hintY,
scalingX: instance.scalingX,
scalingY: instance.scalingY,
r: instance.r,
fillStyle: 'none',
lineDash: [4, 4],
zIndex: 100
};
Ext.apply(style, me.getStyle());
me.target = {
index: item.index,
yField: item.field,
yValue: (positionY - matrix.getDY()) / matrix.getYY()
};
if (isEditableX) {
Ext.apply(me.target, {
xField: item.series.getXField(),
xValue: (positionX - matrix.getDX()) / matrix.getXX()
});
}
params = [chart, {
target: me.target,
style: style,
item: item
}];
changes = Ext.callback(renderer, null, params, 0, chart);
if (changes) {
Ext.apply(style, changes);
}
// This marker acts as a visual hint while dragging.
item.sprite.putMarker('markers', style, 'itemedit');
me.showTooltip(e, me.target, item);
surface.renderFrame();
},
showTooltip: function(e, target, item) {
var tooltip = this.getTooltip(),
config, chart;
if (tooltip && Ext.toolkit !== 'modern') {
config = tooltip.config;
chart = this.getChart();
Ext.callback(config.renderer, null, [tooltip, item, target, e], 0, chart);
// If trackMouse is set, a ToolTip shows by its pointerEvent
tooltip.pointerEvent = e;
if (tooltip.isVisible()) {
// After show handling repositions according
// to configuration. trackMouse uses the pointerEvent
// If aligning to an element, it uses a currentTarget
// flyweight which may be attached to any DOM element.
tooltip.realignToTarget();
}
else {
tooltip.show();
}
}
},
hideTooltip: function() {
var tooltip = this.getTooltip();
if (tooltip && Ext.toolkit !== 'modern') {
tooltip.hide();
}
},
onDragEnd: function(e) {
var me = this,
target = me.target,
chart = me.getChart(),
store = chart.getStore(),
record;
if (target) {
record = store.getAt(target.index);
if (target.yField) {
record.set(target.yField, target.yValue, {
convert: false
});
}
if (target.xField) {
record.set(target.xField, target.xValue, {
convert: false
});
}
if (target.yField || target.xField) {
me.getChart().onDataChanged();
}
me.target = null;
}
me.hideTooltip();
if (me.item) {
chart.fireEvent('enditemedit', chart, me, me.item, target);
}
me.highlight(me.item = null);
},
destroy: function() {
// Peek at the config, so we don't create one just to destroy it,
// if a user has set 'tooltip' config to 'false'.
var tooltip = this.getConfig('tooltip', true);
Ext.destroy(tooltip);
this.callParent();
}
});