/** * @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) { if (tooltip) { var 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); break; case 'scatterSeries': return me.onDragScatter(e); break; } } }, 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('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, xAxis = item.series.getXAxis(), isEditableX = xAxis && xAxis.getLayout().isContinuous, renderer = me.getRenderer(), style, changes, params, positionX, positionY; 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; } style = { translationX: positionX, translationY: positionY, 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); } item.sprite.putMarker('items', 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.handleAfterShow(); } 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(); } });