/** * @class Ext.chart.interactions.ItemHighlight * @extends Ext.chart.interactions.Abstract * * The 'itemhighlight' interaction allows the user to highlight series items in the chart. */Ext.define('Ext.chart.interactions.ItemHighlight', { extend: 'Ext.chart.interactions.Abstract', type: 'itemhighlight', alias: 'interaction.itemhighlight', isItemHighlight: true, config: { gestures: { tap: 'onTapGesture', mousemove: 'onMouseMoveGesture', mousedown: 'onMouseDownGesture', mouseup: 'onMouseUpGesture', mouseleave: 'onMouseUpGesture' }, /** * @cfg {Boolean} [sticky=false] * Disables mouse tracking. * Series items will only be highlighted/unhighlighted on mouse click. * This config has no effect on touch devices. */ sticky: false, /** * @cfg {Boolean} [multiTooltips=false] * Enable displaying multiple tooltips for overlapping or adjacent series items within * {@link Ext.chart.series.Line#selectionTolerance} radius. * Default is to display a tooltip only for the last series item rendered. * When multiple tooltips are displayed, they may overlap partially or completely; * it is up to the developer to ensure tooltip positioning is satisfactory. * * @since 6.6.0 */ multiTooltips: false }, constructor: function(config) { this.callParent([config]); this.stickyHighlightItem = null; this.tooltipItems = []; }, destroy: function() { this.stickyHighlightItem = this.tooltipItems = null; this.callParent(); }, onMouseMoveGesture: function(e) { var me = this, tooltipItems = me.tooltipItems, isMousePointer = e.pointerType === 'mouse', tooltips = [], item, oldItem, items, tooltip, oldTooltip, i, len, j, jLen; if (me.getSticky()) { return true; } if (isMousePointer && me.stickyHighlightItem) { me.stickyHighlightItem = null; me.highlight(null); } if (me.isDragging) { if (tooltipItems.length && isMousePointer) { me.hideTooltips(tooltipItems); tooltipItems.length = 0; } } else if (!me.stickyHighlightItem) { if (me.getMultiTooltips()) { items = me.getItemsForEvent(e); } else { item = me.getItemForEvent(e); items = item ? [item] : []; } for (i = 0, len = items.length; i < len; i++) { item = items[i]; // Items are returned top to down, so first item is the top one. // Chart can only have one highlighted item. if (i === 0 && item !== me.getChart().getHighlightItem()) { me.highlight(item); me.sync(); } tooltip = item.series.getTooltip(); if (tooltip) { tooltips.push(tooltip); } } // sync the last item on mouseleave me.highlight(item); if (isMousePointer) { // If we detected a mouse hit, show/refresh the tooltip if (items.length) { for (i = 0, len = items.length; i < len; i++) { item = items[i]; tooltip = item.series.getTooltip(); if (tooltip) { // If there were different previously active items // that are not going to be included in current active items, // ask them to hide their tooltips. Unless those are // the same tooltip instances that we are about to show, // in which case we are just going to reposition them. for (j = 0, jLen = tooltipItems.length; j < jLen; j++) { oldItem = tooltipItems[j]; if (!Ext.Array.contains(items, oldItem)) { oldTooltip = oldItem.series.getTooltip(); if (!Ext.Array.contains(tooltips, oldTooltip)) { oldItem.series.hideTooltip(oldItem, true); } } } if (tooltip.getTrackMouse()) { item.series.showTooltip(item, e); } else { me.showUntracked(item); } } } me.tooltipItems = items; } // No mouse hit - schedule a hide for hideDelay ms. // If pointer enters another item within that time, // there will be no flickery reshow. else { me.hideTooltips(tooltipItems); tooltipItems.length = 0; } } return false; } }, highlight: function(item) { // This is its own function to make it easier for subclasses // to enhance the behavior. An alternative would be to listen // for the chart's 'itemhighlight' event. this.getChart().setHighlightItem(item); }, showTooltip: function(e, item) { item.series.showTooltip(item, e); Ext.Array.include(this.tooltipItems, item); }, showUntracked: function(item) { var marker = item.sprite.getMarker(item.category), surface, surfaceXY, isInverseY, itemBBox, matrix; if (marker) { surface = marker.getSurface(); isInverseY = surface.matrix.elements[3] < 0; surfaceXY = surface.element.getXY(); itemBBox = Ext.clone(marker.getBBoxFor(item.index)); if (isInverseY) { // The item.category for bar series will be 'items'. // The item.category for line series will be 'markers'. // 'items' are in the 'series' surface, which is flipped vertically // for cartesian series. // 'markers' are in the 'overlay' surface, which isn't flipped. // So for 'markers' we already have the bbox in a coordinate system // with the origin at the top-left of the surface, but for 'items' // we need to do a conversion. if (surface.getInherited().rtl) { matrix = surface.inverseMatrix.clone().flipX() .translate(item.sprite.attr.innerWidth, 0, true); } else { matrix = surface.inverseMatrix; } itemBBox = matrix.transformBBox(itemBBox); } itemBBox.x += surfaceXY[0]; itemBBox.y += surfaceXY[1]; item.series.showTooltipAt(item, itemBBox.x + itemBBox.width * 0.5, itemBBox.y + itemBBox.height * 0.5 ); } }, onMouseDownGesture: function() { this.isDragging = true; }, onMouseUpGesture: function() { this.isDragging = false; }, isSameItem: function(a, b) { return a && b && a.series === b.series && a.field === b.field && a.index === b.index; }, onTapGesture: function(e) { var me = this, item; // A click/tap on an item makes its highlight sticky. // It requires another click/tap to unhighlight. if (e.pointerType === 'mouse' && !me.getSticky()) { return; } item = me.getItemForEvent(e); if (me.isSameItem(me.stickyHighlightItem, item)) { item = null; // toggle } me.stickyHighlightItem = item; me.highlight(item); }, privates: { hideTooltips: function(items, force) { var item, i, len; items = Ext.isArray(items) ? items : [items]; for (i = 0, len = items.length; i < len; i++) { item = items[i]; if (item && item.series && !item.series.destroyed) { item.series.hideTooltip(item, force); } } } }});