blob: 78779d11e267ff9b5af6597dffba929ab119194b [file] [log] [blame]
//
// 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;
/// A behavior that draws marking lines on the chart.
class LineMarker implements ChartBehavior {
/// Position of the line markers.
final Map<int, dynamic> positions;
/// If true, the markers are drawn above the series
final bool drawAboveSeries;
/// If true, animates (grows from the axis into the chart)
final bool animate;
CartesianArea _area;
bool _isLeftAxisPrimary = false;
Rect _rect;
bool _showing;
Selection _parent;
DataSelection _markers;
StreamSubscription _axesChangeSubscription;
LineMarker(this.positions,
{this.drawAboveSeries: false, this.animate: false});
void init(ChartArea area, Selection upper, Selection lower) {
if (area is! CartesianArea) return;
_area = area as CartesianArea;
_parent = drawAboveSeries ? upper : lower;
_isLeftAxisPrimary = _area.config.isLeftAxisPrimary;
_axesChangeSubscription = _area.onChartAxesUpdated.listen((_) => _update());
_update();
}
void dispose() {
if (_axesChangeSubscription != null) _axesChangeSubscription.cancel();
if (_markers != null) _markers.remove();
}
bool _isDimension(int column) => _area.config.dimensions.contains(column);
String _pathForDimension(int column, bool initial) {
assert(_isDimension(column));
int index;
for (index = 0;
_area.config.dimensions.elementAt(index) != column;
++index);
assert(index == 0 || index == 1 && _area.useTwoDimensionAxes);
var dimensionAtBottom = index == 1 && _isLeftAxisPrimary ||
index == 0 && !_isLeftAxisPrimary,
scale = _area.dimensionScales.elementAt(index),
scaled = scale.scale(positions[column]) as num,
theme = _area.theme.getDimensionAxisTheme(),
renderAreaRect = _area.layout.renderArea,
left = renderAreaRect.x,
right = initial ? left : (left + renderAreaRect.width),
bottom = renderAreaRect.y + renderAreaRect.height,
top = initial ? bottom : renderAreaRect.y;
if (scale is OrdinalScale) {
var band = scale.rangeBand, bandPadding = theme.axisBandInnerPadding;
scaled = scaled - band * bandPadding + _area.theme.defaultStrokeWidth;
band = band + 2 * (band * bandPadding - _area.theme.defaultStrokeWidth);
return dimensionAtBottom
? 'M ${left + scaled} ${bottom} V ${top} H ${left + scaled + band} V ${bottom} Z'
: 'M ${left} ${scaled + band} H ${right} V ${scaled - band} H ${left} Z';
} else {
return dimensionAtBottom
? 'M ${left + scaled} ${bottom} V ${top}'
: 'M ${left} ${scaled} H ${right}';
}
}
String _pathForMeasure(int column, bool initial) {
throw new UnimplementedError('Measure axis markers');
}
String _getMarkerPath(int column, bool initial) => _isDimension(column)
? _pathForDimension(column, initial)
: _pathForMeasure(column, initial);
void _update() {
if (!_area.isReady) return;
_markers = _parent.selectAll('.line-marker').data(positions.keys);
_markers.enter.append('path').each((_d, i, e) {
int d = _d;
e.classes.add('line-marker');
e.attributes['d'] = _getMarkerPath(d, animate);
});
if (animate) {
_markers
.transition()
.attrWithCallback('d', (d, i, e) => _getMarkerPath(d as int, false));
}
_markers.exit.remove();
}
}