| Index: charted/lib/charts/src/cartesian_area_impl.dart
|
| diff --git a/charted/lib/charts/src/cartesian_area_impl.dart b/charted/lib/charts/src/cartesian_area_impl.dart
|
| deleted file mode 100644
|
| index 2769f7ecb3a6dc464e1dacc2f5021e4aed215a87..0000000000000000000000000000000000000000
|
| --- a/charted/lib/charts/src/cartesian_area_impl.dart
|
| +++ /dev/null
|
| @@ -1,740 +0,0 @@
|
| -//
|
| -// 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 either one or two dimension axes and zero or more measure axis.
|
| -/// The number of measure axes displayed is zero in charts like bubble chart
|
| -/// which contain two dimension axes.
|
| -class DefaultCartesianAreaImpl implements CartesianArea {
|
| - /// Default identifiers used by the measure axes
|
| - static const MEASURE_AXIS_IDS = const['_default'];
|
| -
|
| - /// Orientations used by measure axes. First, when "x" axis is the primary
|
| - /// and the only dimension. Second, when "y" axis is the primary and the only
|
| - /// dimension.
|
| - static const MEASURE_AXIS_ORIENTATIONS = const[
|
| - const[ORIENTATION_LEFT, ORIENTATION_RIGHT],
|
| - const[ORIENTATION_BOTTOM, ORIENTATION_TOP]
|
| - ];
|
| -
|
| - /// Orientations used by the dimension axes. First, when "x" is the
|
| - /// primary dimension and the last one for cases where "y" axis is primary
|
| - /// dimension.
|
| - static const DIMENSION_AXIS_ORIENTATIONS = const[
|
| - const[ORIENTATION_BOTTOM, ORIENTATION_LEFT],
|
| - const[ORIENTATION_LEFT, ORIENTATION_BOTTOM]
|
| - ];
|
| -
|
| - /// Mapping of measure axis Id to it's axis.
|
| - final _measureAxes = new LinkedHashMap<String, DefaultChartAxisImpl>();
|
| -
|
| - /// Mapping of dimension column index to it's axis.
|
| - final _dimensionAxes = new LinkedHashMap<int, DefaultChartAxisImpl>();
|
| -
|
| - /// Disposer for all change stream subscriptions related to data.
|
| - final _dataEventsDisposer = new SubscriptionsDisposer();
|
| -
|
| - /// Disposer for all change stream subscriptions related to config.
|
| - final _configEventsDisposer = new SubscriptionsDisposer();
|
| -
|
| - @override
|
| - final Element host;
|
| -
|
| - @override
|
| - final bool useTwoDimensionAxes;
|
| -
|
| - @override
|
| - final bool useRowColoring;
|
| -
|
| - /// Indicates whether any renderers need bands on primary dimension
|
| - final List<int> dimensionsUsingBands = [];
|
| -
|
| - @override
|
| - final ChartState state;
|
| -
|
| - @override
|
| - _ChartAreaLayout layout = new _ChartAreaLayout();
|
| -
|
| - @override
|
| - Selection upperBehaviorPane;
|
| -
|
| - @override
|
| - Selection lowerBehaviorPane;
|
| -
|
| - @override
|
| - bool isReady = false;
|
| -
|
| - @override
|
| - ChartTheme theme;
|
| -
|
| - ChartData _data;
|
| - ChartConfig _config;
|
| - bool _autoUpdate = false;
|
| -
|
| - SelectionScope _scope;
|
| - Selection _svg;
|
| - Selection visualization;
|
| -
|
| - Iterable<ChartSeries> _series;
|
| -
|
| - bool _pendingLegendUpdate = false;
|
| - List<ChartBehavior> _behaviors = new List<ChartBehavior>();
|
| - Map<ChartSeries, _ChartSeriesInfo> _seriesInfoCache = new Map();
|
| -
|
| - StreamController<ChartEvent> _valueMouseOverController;
|
| - StreamController<ChartEvent> _valueMouseOutController;
|
| - StreamController<ChartEvent> _valueMouseClickController;
|
| - StreamController<ChartArea> _chartAxesUpdatedController;
|
| -
|
| - DefaultCartesianAreaImpl(
|
| - this.host,
|
| - ChartData data,
|
| - ChartConfig config,
|
| - bool autoUpdate,
|
| - this.useTwoDimensionAxes,
|
| - this.useRowColoring,
|
| - this.state) : _autoUpdate = autoUpdate {
|
| - assert(host != null);
|
| - assert(isNotInline(host));
|
| -
|
| - this.data = data;
|
| - this.config = config;
|
| - theme = new QuantumChartTheme();
|
| -
|
| - Transition.defaultEasingType = theme.transitionEasingType;
|
| - Transition.defaultEasingMode = theme.transitionEasingMode;
|
| - Transition.defaultDurationMilliseconds =
|
| - theme.transitionDurationMilliseconds;
|
| - }
|
| -
|
| - void dispose() {
|
| - _configEventsDisposer.dispose();
|
| - _dataEventsDisposer.dispose();
|
| - _config.legend.dispose();
|
| -
|
| - if (_valueMouseOverController != null) {
|
| - _valueMouseOverController.close();
|
| - _valueMouseOverController = null;
|
| - }
|
| - if (_valueMouseOutController != null) {
|
| - _valueMouseOutController.close();
|
| - _valueMouseOutController = null;
|
| - }
|
| - if (_valueMouseClickController != null) {
|
| - _valueMouseClickController.close();
|
| - _valueMouseClickController = null;
|
| - }
|
| - if (_chartAxesUpdatedController != null) {
|
| - _chartAxesUpdatedController.close();
|
| - _chartAxesUpdatedController = null;
|
| - }
|
| - }
|
| -
|
| - static bool isNotInline(Element e) =>
|
| - e != null && e.getComputedStyle().display != 'inline';
|
| -
|
| - /// Set new data for this chart. If [value] is [Observable], subscribes to
|
| - /// changes and updates the chart when data changes.
|
| - @override
|
| - set data(ChartData value) {
|
| - _data = value;
|
| - _dataEventsDisposer.dispose();
|
| - _pendingLegendUpdate = true;
|
| -
|
| - if (autoUpdate && _data != null && _data is Observable) {
|
| - _dataEventsDisposer.add((_data as Observable).changes.listen((_) {
|
| - _pendingLegendUpdate = true;
|
| - draw();
|
| - }));
|
| - }
|
| - }
|
| -
|
| - @override
|
| - ChartData get data => _data;
|
| -
|
| - /// Set new config for this chart. If [value] is [Observable], subscribes to
|
| - /// changes and updates the chart when series or dimensions change.
|
| - @override
|
| - set config(ChartConfig value) {
|
| - _config = value;
|
| - _configEventsDisposer.dispose();
|
| - _pendingLegendUpdate = true;
|
| -
|
| - if (_config != null && _config is Observable) {
|
| - _configEventsDisposer.add((_config as Observable).changes.listen((_) {
|
| - _pendingLegendUpdate = true;
|
| - draw();
|
| - }));
|
| - }
|
| - }
|
| -
|
| - @override
|
| - ChartConfig get config => _config;
|
| -
|
| - @override
|
| - set autoUpdate(bool value) {
|
| - if (_autoUpdate != value) {
|
| - _autoUpdate = value;
|
| - this.data = _data;
|
| - this.config = _config;
|
| - }
|
| - }
|
| -
|
| - @override
|
| - bool get autoUpdate => _autoUpdate;
|
| -
|
| - /// Gets measure axis from cache - creates a new instance of _ChartAxis
|
| - /// if one was not already created for the given [axisId].
|
| - DefaultChartAxisImpl _getMeasureAxis(String axisId) {
|
| - _measureAxes.putIfAbsent(axisId, () {
|
| - var axisConf = config.getMeasureAxis(axisId),
|
| - axis = axisConf != null ?
|
| - new DefaultChartAxisImpl.withAxisConfig(this, axisConf) :
|
| - new DefaultChartAxisImpl(this);
|
| - return axis;
|
| - });
|
| - return _measureAxes[axisId];
|
| - }
|
| -
|
| - /// Gets a dimension axis from cache - creates a new instance of _ChartAxis
|
| - /// if one was not already created for the given dimension [column].
|
| - DefaultChartAxisImpl _getDimensionAxis(int column) {
|
| - _dimensionAxes.putIfAbsent(column, () {
|
| - var axisConf = config.getDimensionAxis(column),
|
| - axis = axisConf != null ?
|
| - new DefaultChartAxisImpl.withAxisConfig(this, axisConf) :
|
| - new DefaultChartAxisImpl(this);
|
| - return axis;
|
| - });
|
| - return _dimensionAxes[column];
|
| - }
|
| -
|
| - /// All columns rendered by a series must be of the same type.
|
| - bool _isSeriesValid(ChartSeries s) {
|
| - var first = data.columns.elementAt(s.measures.first).type;
|
| - return s.measures.every((i) =>
|
| - (i < data.columns.length) && data.columns.elementAt(i).type == first);
|
| - }
|
| -
|
| - @override
|
| - Iterable<Scale> get dimensionScales =>
|
| - config.dimensions.map((int column) => _getDimensionAxis(column).scale);
|
| -
|
| - @override
|
| - Iterable<Scale> measureScales(ChartSeries series) {
|
| - var axisIds = isNullOrEmpty(series.measureAxisIds)
|
| - ? MEASURE_AXIS_IDS
|
| - : series.measureAxisIds;
|
| - return axisIds.map((String id) => _getMeasureAxis(id).scale);
|
| - }
|
| -
|
| - /// Computes the size of chart and if changed from the previous time
|
| - /// size was computed, sets attributes on svg element
|
| - Rect _computeChartSize() {
|
| - int width = host.clientWidth,
|
| - height = host.clientHeight;
|
| -
|
| - if (config.minimumSize != null) {
|
| - width = max([width, config.minimumSize.width]);
|
| - height = max([height, config.minimumSize.height]);
|
| - }
|
| -
|
| - AbsoluteRect padding = theme.padding;
|
| - num paddingLeft = config.isRTL ? padding.end : padding.start;
|
| - Rect current = new Rect(paddingLeft, padding.top,
|
| - width - (padding.start + padding.end),
|
| - height - (padding.top + padding.bottom));
|
| - if (layout.chartArea == null || layout.chartArea != current) {
|
| - _svg.attr('width', width.toString());
|
| - _svg.attr('height', height.toString());
|
| - layout.chartArea = current;
|
| -
|
| - var transform = 'translate(${paddingLeft},${padding.top})';
|
| - visualization.first.attributes['transform'] = transform;
|
| - lowerBehaviorPane.first.attributes['transform'] = transform;
|
| - upperBehaviorPane.first.attributes['transform'] = transform;
|
| - }
|
| - return layout.chartArea;
|
| - }
|
| -
|
| - @override
|
| - draw({bool preRender:false, Future schedulePostRender}) {
|
| - assert(data != null && config != null);
|
| - assert(config.series != null && config.series.isNotEmpty);
|
| -
|
| - // One time initialization.
|
| - // Each [ChartArea] has it's own [SelectionScope]
|
| - if (_scope == null) {
|
| - _scope = new SelectionScope.element(host);
|
| - _svg = _scope.append('svg:svg')..classed('chart-canvas');
|
| - if (!isNullOrEmpty(theme.filters)) {
|
| - var element = _svg.first,
|
| - defs = Namespace.createChildElement('defs', element)
|
| - ..append(new SvgElement.svg(
|
| - theme.filters, treeSanitizer: new NullTreeSanitizer()));
|
| - _svg.first.append(defs);
|
| - }
|
| -
|
| - lowerBehaviorPane = _svg.append('g')..classed('lower-render-pane');
|
| - visualization = _svg.append('g')..classed('chart-render-pane');
|
| - upperBehaviorPane = _svg.append('g')..classed('upper-render-pane');
|
| -
|
| - if (_behaviors.isNotEmpty) {
|
| - _behaviors.forEach(
|
| - (b) => b.init(this, upperBehaviorPane, lowerBehaviorPane));
|
| - }
|
| - }
|
| -
|
| - // Compute chart sizes and filter out unsupported series
|
| - _computeChartSize();
|
| - var series = config.series.where((s) =>
|
| - _isSeriesValid(s) && s.renderer.prepare(this, s)),
|
| - selection = visualization.selectAll('.series-group').
|
| - data(series, (x) => x.hashCode),
|
| - axesDomainCompleter = new Completer();
|
| -
|
| - // Wait till the axes are rendered before rendering series.
|
| - // In an SVG, z-index is based on the order of nodes in the DOM.
|
| - axesDomainCompleter.future.then((_) {
|
| - selection.enter.append('svg:g')..classed('series-group');
|
| - String transform =
|
| - 'translate(${layout.renderArea.x},${layout.renderArea.y})';
|
| -
|
| - selection.each((ChartSeries s, _, Element group) {
|
| - _ChartSeriesInfo info = _seriesInfoCache[s];
|
| - if (info == null) {
|
| - info = _seriesInfoCache[s] = new _ChartSeriesInfo(this, s);
|
| - }
|
| - info.check();
|
| - group.attributes['transform'] = transform;
|
| - (s.renderer as CartesianRenderer)
|
| - .draw(group, schedulePostRender:schedulePostRender);
|
| - });
|
| -
|
| - // A series that was rendered earlier isn't there anymore, remove it
|
| - selection.exit
|
| - ..each((ChartSeries s, _, __) {
|
| - var info = _seriesInfoCache.remove(s);
|
| - if (info != null) {
|
| - info.dispose();
|
| - }
|
| - })
|
| - ..remove();
|
| -
|
| - // Notify on the stream that the chart has been updated.
|
| - isReady = true;
|
| - if (_chartAxesUpdatedController != null) {
|
| - _chartAxesUpdatedController.add(this);
|
| - }
|
| - });
|
| -
|
| - // Save the list of valid series and initialize axes.
|
| - _series = series;
|
| - _initAxes(preRender: preRender);
|
| -
|
| - // Render the chart, now that the axes layer is already in DOM.
|
| - axesDomainCompleter.complete();
|
| -
|
| - // Updates the legend if required.
|
| - _updateLegend();
|
| - }
|
| -
|
| - String _orientRTL(String orientation) => orientation;
|
| - Scale _scaleRTL(Scale scale) => scale;
|
| -
|
| - /// Initialize the axes - required even if the axes are not being displayed.
|
| - _initAxes({bool preRender: false}) {
|
| - Map measureAxisUsers = <String,Iterable<ChartSeries>>{};
|
| -
|
| - // Create necessary measures axes.
|
| - // If measure axes were not configured on the series, default is used.
|
| - _series.forEach((ChartSeries s) {
|
| - var measureAxisIds = isNullOrEmpty(s.measureAxisIds)
|
| - ? MEASURE_AXIS_IDS
|
| - : s.measureAxisIds;
|
| - measureAxisIds.forEach((axisId) {
|
| - _getMeasureAxis(axisId); // Creates axis if required
|
| - var users = measureAxisUsers[axisId];
|
| - if (users == null) {
|
| - measureAxisUsers[axisId] = [s];
|
| - } else {
|
| - users.add(s);
|
| - }
|
| - });
|
| - });
|
| -
|
| - // Now that we know a list of series using each measure axis, configure
|
| - // the input domain of each axis.
|
| - measureAxisUsers.forEach((id, listOfSeries) {
|
| - var sampleCol = listOfSeries.first.measures.first,
|
| - sampleColSpec = data.columns.elementAt(sampleCol),
|
| - axis = _getMeasureAxis(id),
|
| - domain;
|
| -
|
| - if (sampleColSpec.useOrdinalScale) {
|
| - throw new UnsupportedError(
|
| - 'Ordinal measure axes are not currently supported.');
|
| - } else {
|
| - // Extent is available because [ChartRenderer.prepare] was already
|
| - // called (when checking for valid series in [draw].
|
| - Iterable extents = listOfSeries.map((s) => s.renderer.extent).toList();
|
| - var lowest = min(extents.map((e) => e.min)),
|
| - highest = max(extents.map((e) => e.max));
|
| -
|
| - // Use default domain if lowest and highest are the same, right now
|
| - // lowest is always 0 unless it is less than 0 - change to lowest when
|
| - // we make use of it.
|
| - domain = highest == lowest
|
| - ? (highest == 0
|
| - ? [0, 1]
|
| - : (highest < 0 ? [highest, 0] : [0, highest]))
|
| - : (lowest <= 0 ? [lowest, highest] : [0, highest]);
|
| - }
|
| - axis.initAxisDomain(sampleCol, false, domain);
|
| - });
|
| -
|
| - // Configure dimension axes.
|
| - int dimensionAxesCount = useTwoDimensionAxes ? 2 : 1;
|
| - config.dimensions.take(dimensionAxesCount).forEach((int column) {
|
| - var axis = _getDimensionAxis(column),
|
| - sampleColumnSpec = data.columns.elementAt(column),
|
| - values = data.rows.map((row) => row.elementAt(column)),
|
| - domain;
|
| -
|
| - if (sampleColumnSpec.useOrdinalScale) {
|
| - domain = values.map((e) => e.toString()).toList();
|
| - } else {
|
| - var extent = new Extent.items(values);
|
| - domain = [extent.min, extent.max];
|
| - }
|
| - axis.initAxisDomain(column, true, domain);
|
| - });
|
| -
|
| - // See if any dimensions need "band" on the axis.
|
| - dimensionsUsingBands.clear();
|
| - List<bool> usingBands = [false, false];
|
| - _series.forEach((ChartSeries s) =>
|
| - (s.renderer as CartesianRenderer).dimensionsUsingBand.forEach((x) {
|
| - if (x <= 1 && !(usingBands[x])) {
|
| - usingBands[x] = true;
|
| - dimensionsUsingBands.add(config.dimensions.elementAt(x));
|
| - }
|
| - }));
|
| -
|
| - // List of measure and dimension axes that are displayed
|
| - assert(
|
| - isNullOrEmpty(config.displayedMeasureAxes) ||
|
| - config.displayedMeasureAxes.length < 2);
|
| - var measureAxesCount = dimensionAxesCount == 1 ? 2 : 0,
|
| - displayedMeasureAxes = (isNullOrEmpty(config.displayedMeasureAxes)
|
| - ? _measureAxes.keys.take(measureAxesCount)
|
| - : config.displayedMeasureAxes.take(measureAxesCount)).
|
| - toList(growable: false),
|
| - displayedDimensionAxes =
|
| - config.dimensions.take(dimensionAxesCount).toList(growable: false);
|
| -
|
| - // Compute size of the dimension axes
|
| - if (config.renderDimensionAxes != false) {
|
| - var dimensionAxisOrientations = config.isLeftAxisPrimary
|
| - ? DIMENSION_AXIS_ORIENTATIONS.last
|
| - : DIMENSION_AXIS_ORIENTATIONS.first;
|
| - for (int i = 0, len = displayedDimensionAxes.length; i < len; ++i) {
|
| - var axis = _dimensionAxes[displayedDimensionAxes[i]],
|
| - orientation = _orientRTL(dimensionAxisOrientations[i]);
|
| - axis.prepareToDraw(orientation);
|
| - layout._axes[orientation] = axis.size;
|
| - }
|
| - }
|
| -
|
| - // Compute size of the measure axes
|
| - if (displayedMeasureAxes.isNotEmpty) {
|
| - var measureAxisOrientations = config.isLeftAxisPrimary
|
| - ? MEASURE_AXIS_ORIENTATIONS.last
|
| - : MEASURE_AXIS_ORIENTATIONS.first;
|
| - displayedMeasureAxes.asMap().forEach((int index, String key) {
|
| - var axis = _measureAxes[key],
|
| - orientation = _orientRTL(measureAxisOrientations[index]);
|
| - axis.prepareToDraw(orientation);
|
| - layout._axes[orientation] = axis.size;
|
| - });
|
| - }
|
| -
|
| - // Consolidate all the information that we collected into final layout
|
| - _computeLayout(
|
| - displayedMeasureAxes.isEmpty && config.renderDimensionAxes == false);
|
| -
|
| - // Domains for all axes have been taken care of and _ChartAxis ensures
|
| - // that the scale is initialized on visible axes. Initialize the scale on
|
| - // all invisible measure scales.
|
| - if (_measureAxes.length != displayedMeasureAxes.length) {
|
| - _measureAxes.keys.forEach((String axisId) {
|
| - if (displayedMeasureAxes.contains(axisId)) return;
|
| - _getMeasureAxis(axisId).initAxisScale([layout.renderArea.height, 0]);
|
| - });
|
| - }
|
| -
|
| - // Draw the visible measure axes, if any.
|
| - if (displayedMeasureAxes.isNotEmpty) {
|
| - var axisGroups = visualization.
|
| - selectAll('.measure-axis-group').data(displayedMeasureAxes);
|
| - // Update measure axis (add/remove/update)
|
| - axisGroups.enter.append('svg:g');
|
| - axisGroups.each((axisId, index, group) {
|
| - _getMeasureAxis(axisId).draw(group, _scope, preRender: preRender);
|
| - group.attributes['class'] = 'measure-axis-group measure-${index}';
|
| - });
|
| - axisGroups.exit.remove();
|
| - }
|
| -
|
| - // Draw the dimension axes, unless asked not to.
|
| - if (config.renderDimensionAxes != false) {
|
| - var dimAxisGroups = visualization.
|
| - selectAll('.dimension-axis-group').data(displayedDimensionAxes);
|
| - // Update dimension axes (add/remove/update)
|
| - dimAxisGroups.enter.append('svg:g');
|
| - dimAxisGroups.each((column, index, group) {
|
| - _getDimensionAxis(column).draw(group, _scope, preRender: preRender);
|
| - group.attributes['class'] = 'dimension-axis-group dim-${index}';
|
| - });
|
| - dimAxisGroups.exit.remove();
|
| - } else {
|
| - // Initialize scale on invisible axis
|
| - var dimensionAxisOrientations = config.isLeftAxisPrimary ?
|
| - DIMENSION_AXIS_ORIENTATIONS.last : DIMENSION_AXIS_ORIENTATIONS.first;
|
| - for (int i = 0; i < dimensionAxesCount; ++i) {
|
| - var column = config.dimensions.elementAt(i),
|
| - axis = _dimensionAxes[column],
|
| - orientation = dimensionAxisOrientations[i];
|
| - axis.initAxisScale(orientation == ORIENTATION_LEFT ?
|
| - [layout.renderArea.height, 0] : [0, layout.renderArea.width]);
|
| - };
|
| - }
|
| - }
|
| -
|
| - // Compute chart render area size and positions of all elements
|
| - _computeLayout(bool notRenderingAxes) {
|
| - if (notRenderingAxes) {
|
| - layout.renderArea =
|
| - new Rect(0, 0, layout.chartArea.height, layout.chartArea.width);
|
| - return;
|
| - }
|
| -
|
| - var top = layout.axes[ORIENTATION_TOP],
|
| - left = layout.axes[ORIENTATION_LEFT],
|
| - bottom = layout.axes[ORIENTATION_BOTTOM],
|
| - right = layout.axes[ORIENTATION_RIGHT];
|
| -
|
| - var renderAreaHeight = layout.chartArea.height -
|
| - (top.height + layout.axes[ORIENTATION_BOTTOM].height),
|
| - renderAreaWidth = layout.chartArea.width -
|
| - (left.width + layout.axes[ORIENTATION_RIGHT].width);
|
| -
|
| - layout.renderArea = new Rect(
|
| - left.width, top.height, renderAreaWidth, renderAreaHeight);
|
| -
|
| - layout._axes
|
| - ..[ORIENTATION_TOP] =
|
| - new Rect(left.width, 0, renderAreaWidth, top.height)
|
| - ..[ORIENTATION_RIGHT] =
|
| - new Rect(left.width + renderAreaWidth, top.y,
|
| - right.width, renderAreaHeight)
|
| - ..[ORIENTATION_BOTTOM] =
|
| - new Rect(left.width, top.height + renderAreaHeight,
|
| - renderAreaWidth, bottom.height)
|
| - ..[ORIENTATION_LEFT] =
|
| - new Rect(
|
| - left.width, top.height, left.width, renderAreaHeight);
|
| - }
|
| -
|
| - // Updates the legend, if configuration changed since the last
|
| - // time the legend was updated.
|
| - _updateLegend() {
|
| - if (!_pendingLegendUpdate) return;
|
| - if (_config == null || _config.legend == null || _series.isEmpty) return;
|
| -
|
| - var legend = <ChartLegendItem>[];
|
| - List seriesByColumn =
|
| - new List.generate(data.columns.length, (_) => new List());
|
| -
|
| - _series.forEach((s) =>
|
| - s.measures.forEach((m) => seriesByColumn[m].add(s)));
|
| -
|
| - seriesByColumn.asMap().forEach((int i, List s) {
|
| - if (s.length == 0) return;
|
| - legend.add(new ChartLegendItem(
|
| - index:i, label:data.columns.elementAt(i).label, series:s,
|
| - color:theme.getColorForKey(i)));
|
| - });
|
| -
|
| - _config.legend.update(legend, this);
|
| - _pendingLegendUpdate = false;
|
| - }
|
| -
|
| - @override
|
| - Stream<ChartEvent> get onMouseUp =>
|
| - host.onMouseUp
|
| - .map((MouseEvent e) => new DefaultChartEventImpl(e, this));
|
| -
|
| - @override
|
| - Stream<ChartEvent> get onMouseDown =>
|
| - host.onMouseDown
|
| - .map((MouseEvent e) => new DefaultChartEventImpl(e, this));
|
| -
|
| - @override
|
| - Stream<ChartEvent> get onMouseOver =>
|
| - host.onMouseOver
|
| - .map((MouseEvent e) => new DefaultChartEventImpl(e, this));
|
| -
|
| - @override
|
| - Stream<ChartEvent> get onMouseOut =>
|
| - host.onMouseOut
|
| - .map((MouseEvent e) => new DefaultChartEventImpl(e, this));
|
| -
|
| - @override
|
| - Stream<ChartEvent> get onMouseMove =>
|
| - host.onMouseMove
|
| - .map((MouseEvent e) => new DefaultChartEventImpl(e, this));
|
| -
|
| - @override
|
| - Stream<ChartEvent> get onValueClick {
|
| - if (_valueMouseClickController == null) {
|
| - _valueMouseClickController = new StreamController.broadcast(sync: true);
|
| - }
|
| - return _valueMouseClickController.stream;
|
| - }
|
| -
|
| - @override
|
| - Stream<ChartEvent> get onValueMouseOver {
|
| - if (_valueMouseOverController == null) {
|
| - _valueMouseOverController = new StreamController.broadcast(sync: true);
|
| - }
|
| - return _valueMouseOverController.stream;
|
| - }
|
| -
|
| - @override
|
| - Stream<ChartEvent> get onValueMouseOut {
|
| - if (_valueMouseOutController == null) {
|
| - _valueMouseOutController = new StreamController.broadcast(sync: true);
|
| - }
|
| - return _valueMouseOutController.stream;
|
| - }
|
| -
|
| - @override
|
| - Stream<ChartArea> get onChartAxesUpdated {
|
| - if (_chartAxesUpdatedController == null) {
|
| - _chartAxesUpdatedController = new StreamController.broadcast(sync: true);
|
| - }
|
| - return _chartAxesUpdatedController.stream;
|
| - }
|
| -
|
| - @override
|
| - void addChartBehavior(ChartBehavior behavior) {
|
| - if (behavior == null || _behaviors.contains(behavior)) return;
|
| - _behaviors.add(behavior);
|
| - if (upperBehaviorPane != null && lowerBehaviorPane != null) {
|
| - behavior.init(this, upperBehaviorPane, lowerBehaviorPane);
|
| - }
|
| - }
|
| -
|
| - @override
|
| - void removeChartBehavior(ChartBehavior behavior) {
|
| - if (behavior == null || !_behaviors.contains(behavior)) return;
|
| - if (upperBehaviorPane != null && lowerBehaviorPane != null) {
|
| - behavior.dispose();
|
| - }
|
| - _behaviors.remove(behavior);
|
| - }
|
| -}
|
| -
|
| -class _ChartAreaLayout implements ChartAreaLayout {
|
| - final _axes = <String, Rect>{
|
| - ORIENTATION_LEFT: const Rect(),
|
| - ORIENTATION_RIGHT: const Rect(),
|
| - ORIENTATION_TOP: const Rect(),
|
| - ORIENTATION_BOTTOM: const Rect()
|
| - };
|
| -
|
| - UnmodifiableMapView<String, Rect> _axesView;
|
| -
|
| - @override
|
| - get axes => _axesView;
|
| -
|
| - @override
|
| - Rect renderArea;
|
| -
|
| - @override
|
| - Rect chartArea;
|
| -
|
| - _ChartAreaLayout() {
|
| - _axesView = new UnmodifiableMapView(_axes);
|
| - }
|
| -}
|
| -
|
| -class _ChartSeriesInfo {
|
| - CartesianRenderer _renderer;
|
| - SubscriptionsDisposer _disposer = new SubscriptionsDisposer();
|
| -
|
| - DefaultChartSeriesImpl _series;
|
| - DefaultCartesianAreaImpl _area;
|
| - _ChartSeriesInfo(this._area, this._series);
|
| -
|
| - _click(ChartEvent e) {
|
| - var state = _area.state;
|
| - if (state != null) {
|
| - if (state.isHighlighted(e.column, e.row)) {
|
| - state.unhighlight(e.column, e.row);
|
| - } else {
|
| - state.highlight(e.column, e.row);
|
| - }
|
| - }
|
| - if (_area._valueMouseClickController != null) {
|
| - _area._valueMouseClickController.add(e);
|
| - }
|
| - }
|
| -
|
| - _mouseOver(ChartEvent e) {
|
| - var state = _area.state;
|
| - if (state != null) {
|
| - state.hovered = new Pair(e.column, e.row);
|
| - }
|
| - if (_area._valueMouseOverController != null) {
|
| - _area._valueMouseOverController.add(e);
|
| - }
|
| - }
|
| -
|
| - _mouseOut(ChartEvent e) {
|
| - var state = _area.state;
|
| - if (state != null) {
|
| - var current = state.hovered;
|
| - if (current != null &&
|
| - current.first == e.column && current.last == e.row) {
|
| - state.hovered = null;
|
| - }
|
| - }
|
| - if (_area._valueMouseOutController != null) {
|
| - _area._valueMouseOutController.add(e);
|
| - }
|
| - }
|
| -
|
| - check() {
|
| - if (_renderer != _series.renderer) {
|
| - dispose();
|
| - if (_series.renderer is ChartRendererBehaviorSource){
|
| - _disposer.addAll([
|
| - _series.renderer.onValueClick.listen(_click),
|
| - _series.renderer.onValueMouseOver.listen(_mouseOver),
|
| - _series.renderer.onValueMouseOut.listen(_mouseOut)
|
| - ]);
|
| - }
|
| - }
|
| - _renderer = _series.renderer;
|
| - }
|
| -
|
| - dispose() => _disposer.dispose();
|
| -}
|
|
|