| // |
| // Copyright 2014 Google Inc. All rights reserved. |
| // |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file or at |
| // https://developers.google.com/open-source/licenses/bsd |
| // |
| |
| part of charted.charts; |
| |
| /// Displays tooltip for the values as user moves the mouse pointer over |
| /// values in the chart. It displays all the active values in the data row |
| /// and use the value in the dimension as the title. |
| @Deprecated('Use Hovercard') |
| class ChartTooltip implements ChartBehavior { |
| static const _TOOLTIP_OFFSET = 10; |
| final String orientation; |
| final bool showDimensionValue; |
| final bool showMeasureTotal; |
| final bool showSelectedMeasure; |
| |
| ChartArea _area; |
| ChartState _state; |
| Selection _tooltipRoot; |
| SubscriptionsDisposer _disposer = new SubscriptionsDisposer(); |
| |
| /// Constructs the tooltip. |
| /// If [showDimensionValue] is set, displays the dimension value as title. |
| /// If [showMeasureTotal] is set, displays the total value. |
| ChartTooltip( |
| {this.showSelectedMeasure: false, |
| this.showDimensionValue: false, |
| this.showMeasureTotal: false, |
| this.orientation: ORIENTATION_RIGHT}); |
| |
| /// Sets up listeners for triggering tooltip. |
| void init(ChartArea area, Selection _, Selection __) { |
| _area = area; |
| _state = area.state; |
| _disposer.addAll([ |
| area.onValueMouseOver.listen(show), |
| area.onValueMouseOut.listen(hide) |
| ]); |
| |
| // Tooltip requires host to be position: relative. |
| area.host.style.position = 'relative'; |
| |
| var _scope = new SelectionScope.element(_area.host); |
| _scope.append('div')..classed('tooltip'); |
| _tooltipRoot = _scope.select('.tooltip'); |
| } |
| |
| void dispose() { |
| _disposer.dispose(); |
| if (_tooltipRoot != null) _tooltipRoot.remove(); |
| } |
| |
| /// Displays tooltip upon receiving a hover event on an element in chart. |
| show(ChartEvent e) { |
| _tooltipRoot.first |
| ..children.clear() |
| ..attributes['dir'] = _area.config.isRTL ? 'rtl' : ''; |
| _tooltipRoot.classed('rtl', _area.config.isRTL); |
| |
| // Display dimension value if set in config. |
| if (showDimensionValue) { |
| var column = _area.config.dimensions.elementAt(0), |
| value = _area.data.rows.elementAt(e.row).elementAt(column), |
| formatter = _getFormatterForColumn(column); |
| |
| _tooltipRoot.append('div') |
| ..classed('tooltip-title') |
| ..text((formatter != null) ? formatter(value) : value.toString()); |
| } |
| |
| // Display sum of the values in active row if set in config. |
| if (showMeasureTotal) { |
| var measures = e.series.measures, |
| formatter = _getFormatterForColumn(measures.elementAt(0)), |
| row = _area.data.rows.elementAt(e.row), |
| total = 0.0; |
| for (int i = 0, len = measures.length; i < len; i++) { |
| total += row.elementAt(measures.elementAt(i)) as num; |
| } |
| _tooltipRoot.append('div') |
| ..classed('tooltip-total') |
| ..text((formatter != null) ? formatter(total) : total.toString()); |
| } |
| |
| // Find the currently selectedMeasures and hoveredMeasures and show |
| // tooltip for them, if none is selected/hovered, show all. |
| var activeMeasures = []; |
| if (showSelectedMeasure) { |
| if (_state != null) { |
| activeMeasures.addAll(_state.selection); |
| activeMeasures.add( |
| _state.preview != null ? _state.preview : _state.hovered.first); |
| } else { |
| // If state is null, chart tooltip will not capture selection, but only |
| // display for the currently hovered measure column. |
| activeMeasures.add(e.column); |
| } |
| if (activeMeasures.isEmpty) { |
| for (var series in _area.config.series) { |
| activeMeasures.addAll(series.measures); |
| } |
| } |
| activeMeasures.sort(); |
| } |
| |
| Iterable data = (showSelectedMeasure) ? activeMeasures : e.series.measures; |
| |
| // Create the tooltip items base on the number of measures in the series. |
| var items = _tooltipRoot.selectAll('.tooltip-item').data(data); |
| items.enter.append('div') |
| ..classed('tooltip-item') |
| ..classedWithCallback( |
| 'active', (d, i, c) => !showSelectedMeasure && (d == e.column)); |
| |
| // Display the label for the currently active series. |
| var tooltipItems = _tooltipRoot.selectAll('.tooltip-item'); |
| tooltipItems.append('div') |
| ..classed('tooltip-item-label') |
| ..textWithCallback((d, i, c) => _area.data.columns |
| .elementAt( |
| (showSelectedMeasure) ? d as int : e.series.measures.elementAt(i)) |
| .label); |
| |
| // Display the value of the currently active series |
| tooltipItems.append('div') |
| ..classed('tooltip-item-value') |
| ..styleWithCallback('color', (d, i, c) => _area.theme.getColorForKey(d)) |
| ..textWithCallback((_d, i, c) { |
| int d = _d; |
| var formatter = _getFormatterForColumn(d), |
| value = _area.data.rows.elementAt(e.row).elementAt(d); |
| return (formatter != null) ? formatter(value) : value.toString(); |
| }); |
| |
| math.Point position = computeTooltipPosition( |
| new math.Point(e.chartX, e.chartY), |
| _tooltipRoot.first.getBoundingClientRect()); |
| |
| // Set position of the tooltip and display it. |
| _tooltipRoot |
| ..style('left', '${position.x}px') |
| ..style('top', '${position.y}px') |
| ..style('opacity', '1'); |
| } |
| |
| static String switchPositionDirection(String direction) => |
| direction == ORIENTATION_LEFT ? ORIENTATION_RIGHT : ORIENTATION_LEFT; |
| |
| /// Computes the ideal tooltip position based on orientation. |
| math.Point computeTooltipPosition(math.Point coord, math.Rectangle rect) { |
| var x, y, direction; |
| direction = _area.config.isRTL && _area.config.switchAxesForRTL |
| ? switchPositionDirection(orientation) |
| : orientation; |
| |
| if (direction == ORIENTATION_LEFT) { |
| x = coord.x - rect.width - _TOOLTIP_OFFSET; |
| y = coord.y + _TOOLTIP_OFFSET; |
| } else { |
| x = coord.x + _TOOLTIP_OFFSET; |
| y = coord.y + _TOOLTIP_OFFSET; |
| } |
| return boundTooltipPosition( |
| new math.Rectangle(x as num, y as num, rect.width, rect.height)); |
| } |
| |
| /// Positions the tooltip to be inside of the chart boundary. |
| math.Point boundTooltipPosition(math.Rectangle rect) { |
| var hostRect = _area.host.getBoundingClientRect(); |
| |
| var top = rect.top; |
| var left = rect.left; |
| |
| // Checks top and bottom. |
| if (rect.top < 0) { |
| top += (2 * _TOOLTIP_OFFSET); |
| } else if (rect.top + rect.height > hostRect.height) { |
| top -= (rect.height + 2 * _TOOLTIP_OFFSET); |
| } |
| |
| // Checks left and right. |
| if (rect.left < 0) { |
| left += (rect.width + 2 * _TOOLTIP_OFFSET); |
| } else if (rect.left + rect.width > hostRect.width) { |
| left -= (rect.width + 2 * _TOOLTIP_OFFSET); |
| } |
| |
| return new math.Point(left, top); |
| } |
| |
| FormatFunction _getFormatterForColumn(int column) => |
| _area.data.columns.elementAt(column).formatter; |
| |
| hide(ChartEvent e) { |
| if (_tooltipRoot == null) return; |
| _tooltipRoot.style('opacity', '0'); |
| } |
| } |