| OLD | NEW |
| 1 /* | 1 // |
| 2 * Copyright 2014 Google Inc. All rights reserved. | 2 // Copyright 2014 Google Inc. All rights reserved. |
| 3 * | 3 // |
| 4 * Use of this source code is governed by a BSD-style | 4 // Use of this source code is governed by a BSD-style |
| 5 * license that can be found in the LICENSE file or at | 5 // license that can be found in the LICENSE file or at |
| 6 * https://developers.google.com/open-source/licenses/bsd | 6 // https://developers.google.com/open-source/licenses/bsd |
| 7 */ | 7 // |
| 8 | 8 |
| 9 part of charted.charts; | 9 part of charted.charts; |
| 10 | 10 |
| 11 class LineChartRenderer extends CartesianRendererBase { | 11 class LineChartRenderer extends CartesianRendererBase { |
| 12 final Iterable<int> dimensionsUsingBand = const[]; | 12 final Iterable<int> dimensionsUsingBand = const []; |
| 13 final SubscriptionsDisposer _disposer = new SubscriptionsDisposer(); | 13 final SubscriptionsDisposer _disposer = new SubscriptionsDisposer(); |
| 14 | 14 |
| 15 final bool alwaysAnimate; | 15 final bool alwaysAnimate; |
| 16 final bool trackDataPoints; | 16 final bool trackDataPoints; |
| 17 final bool trackOnDimensionAxis; | 17 final bool trackOnDimensionAxis; |
| 18 final int quantitativeScaleProximity; | 18 final int quantitativeScaleProximity; |
| 19 | 19 |
| 20 bool _trackingPointsCreated = false; | 20 bool _trackingPointsCreated = false; |
| 21 List _xPositions = []; | 21 List _xPositions = []; |
| 22 | 22 |
| 23 // Currently hovered row/column | 23 // Currently hovered row/column |
| 24 int _savedOverRow = 0; | 24 int _savedOverRow = 0; |
| 25 int _savedOverColumn = 0; | 25 int _savedOverColumn = 0; |
| 26 | 26 |
| 27 int currentDataIndex = -1; | 27 int currentDataIndex = -1; |
| 28 | 28 |
| 29 @override | 29 @override |
| 30 final String name = "line-rdr"; | 30 final String name = "line-rdr"; |
| 31 | 31 |
| 32 LineChartRenderer({ | 32 LineChartRenderer( |
| 33 this.alwaysAnimate: false, | 33 {this.alwaysAnimate: false, |
| 34 this.trackDataPoints: true, | 34 this.trackDataPoints: true, |
| 35 this.trackOnDimensionAxis: false, | 35 this.trackOnDimensionAxis: false, |
| 36 this.quantitativeScaleProximity: 5 }); | 36 this.quantitativeScaleProximity: 5}); |
| 37 | 37 |
| 38 /* | 38 // Returns false if the number of dimension axes on the area is 0. |
| 39 * Returns false if the number of dimension axes on the area is 0. | 39 // Otherwise, the first dimension scale is used to render the chart. |
| 40 * Otherwise, the first dimension scale is used to render the chart. | |
| 41 */ | |
| 42 @override | 40 @override |
| 43 bool prepare(ChartArea area, ChartSeries series) { | 41 bool prepare(ChartArea area, ChartSeries series) { |
| 44 _ensureAreaAndSeries(area, series); | 42 _ensureAreaAndSeries(area, series); |
| 45 if (trackDataPoints != false) { | 43 if (trackDataPoints != false) { |
| 46 _trackPointerInArea(); | 44 _trackPointerInArea(); |
| 47 } | 45 } |
| 48 return area is CartesianArea; | 46 return area is CartesianArea; |
| 49 } | 47 } |
| 50 | 48 |
| 51 @override | 49 @override |
| 52 void draw(Element element, {Future schedulePostRender}) { | 50 void draw(Element element, {Future schedulePostRender}) { |
| 53 _ensureReadyToDraw(element); | 51 _ensureReadyToDraw(element); |
| 54 | 52 |
| 55 var measureScale = area.measureScales(series).first, | 53 var measureScale = area.measureScales(series).first, |
| 56 dimensionScale = area.dimensionScales.first; | 54 dimensionScale = area.dimensionScales.first; |
| 57 | 55 |
| 58 // Create lists of values in measure columns. | 56 // Create lists of values in measure columns. |
| 59 var lines = series.measures.map((column) { | 57 var lines = series.measures.map((column) { |
| 60 return area.data.rows.map((values) => values[column]).toList(); | 58 return area.data.rows.map((values) => values[column]).toList(); |
| 61 }).toList(); | 59 }).toList(); |
| 62 | 60 |
| 63 // We only support one dimension axes, so we always use the | 61 // We only support one dimension axes, so we always use the |
| 64 // first dimension. | 62 // first dimension. |
| 65 var x = area.data.rows.map( | 63 var x = area.data.rows |
| 66 (row) => row.elementAt(area.config.dimensions.first)).toList(); | 64 .map((row) => row.elementAt(area.config.dimensions.first)) |
| 65 .toList(); |
| 67 | 66 |
| 68 var rangeBandOffset = | 67 var rangeBandOffset = |
| 69 dimensionScale is OrdinalScale ? dimensionScale.rangeBand / 2 : 0; | 68 dimensionScale is OrdinalScale ? dimensionScale.rangeBand / 2 : 0; |
| 70 | 69 |
| 71 // If tracking data points is enabled, cache location of points that | 70 // If tracking data points is enabled, cache location of points that |
| 72 // represent data. | 71 // represent data. |
| 73 if (trackDataPoints) { | 72 if (trackDataPoints) { |
| 74 _xPositions = | 73 _xPositions = |
| 75 x.map((val) => dimensionScale.scale(val) + rangeBandOffset).toList(); | 74 x.map((val) => dimensionScale.scale(val) + rangeBandOffset).toList(); |
| 76 } | 75 } |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 145 | 144 |
| 146 void _createTrackingCircles() { | 145 void _createTrackingCircles() { |
| 147 var linePoints = root.selectAll('.line-rdr-point').data(series.measures); | 146 var linePoints = root.selectAll('.line-rdr-point').data(series.measures); |
| 148 linePoints.enter.append('circle').each((d, i, e) { | 147 linePoints.enter.append('circle').each((d, i, e) { |
| 149 e.classes.add('line-rdr-point'); | 148 e.classes.add('line-rdr-point'); |
| 150 e.attributes['r'] = '4'; | 149 e.attributes['r'] = '4'; |
| 151 }); | 150 }); |
| 152 | 151 |
| 153 linePoints | 152 linePoints |
| 154 ..each((d, i, e) { | 153 ..each((d, i, e) { |
| 155 var color = colorForColumn(d); | 154 var color = colorForColumn(d); |
| 156 e.attributes | 155 e.attributes |
| 157 ..['r'] = '4' | 156 ..['r'] = '4' |
| 158 ..['stroke'] = color | 157 ..['stroke'] = color |
| 159 ..['fill'] = color | 158 ..['fill'] = color |
| 160 ..['data-column'] = '$d'; | 159 ..['data-column'] = '$d'; |
| 161 }) | 160 }) |
| 162 ..on('click', _mouseClickHandler) | 161 ..on('click', _mouseClickHandler) |
| 163 ..on('mousemove', _mouseOverHandler) // Ensure that we update values | 162 ..on('mousemove', _mouseOverHandler) // Ensure that we update values |
| 164 ..on('mouseover', _mouseOverHandler) | 163 ..on('mouseover', _mouseOverHandler) |
| 165 ..on('mouseout', _mouseOutHandler); | 164 ..on('mouseout', _mouseOutHandler); |
| 166 | 165 |
| 167 linePoints.exit.remove(); | 166 linePoints.exit.remove(); |
| 168 _trackingPointsCreated = true; | 167 _trackingPointsCreated = true; |
| 169 } | 168 } |
| 170 | 169 |
| 171 void _showTrackingCircles(int row) { | 170 void _showTrackingCircles(int row) { |
| 172 if (_trackingPointsCreated == false) { | 171 if (_trackingPointsCreated == false) { |
| 173 _createTrackingCircles(); | 172 _createTrackingCircles(); |
| 174 } | 173 } |
| 175 | 174 |
| 176 var yScale = area.measureScales(series).first; | 175 var yScale = area.measureScales(series).first; |
| 177 root.selectAll('.line-rdr-point').each((d, i, e) { | 176 root.selectAll('.line-rdr-point').each((d, i, e) { |
| 178 var x = _xPositions[row], | 177 var x = _xPositions[row], |
| 179 measureVal = area.data.rows.elementAt(row).elementAt(d); | 178 measureVal = area.data.rows.elementAt(row).elementAt(d); |
| 180 if (measureVal != null && measureVal.isFinite) { | 179 if (measureVal != null && measureVal.isFinite) { |
| 181 var color = colorForColumn(d), | 180 var color = colorForColumn(d), filter = filterForColumn(d); |
| 182 filter = filterForColumn(d); | |
| 183 e.attributes | 181 e.attributes |
| 184 ..['cx'] = '$x' | 182 ..['cx'] = '$x' |
| 185 ..['cy'] = '${yScale.scale(measureVal)}' | 183 ..['cy'] = '${yScale.scale(measureVal)}' |
| 186 ..['fill'] = color | 184 ..['fill'] = color |
| 187 ..['stroke'] = color | 185 ..['stroke'] = color |
| 188 ..['data-row'] = '$row'; | 186 ..['data-row'] = '$row'; |
| 189 e.style | 187 e.style |
| 190 ..setProperty('opacity', '1') | 188 ..setProperty('opacity', '1') |
| 191 ..setProperty('visibility', 'visible'); | 189 ..setProperty('visibility', 'visible'); |
| 192 if (isNullOrEmpty(filter)) { | 190 if (isNullOrEmpty(filter)) { |
| 193 e.attributes.remove('filter'); | 191 e.attributes.remove('filter'); |
| 194 } else { | 192 } else { |
| 195 e.attributes['filter'] = filter; | 193 e.attributes['filter'] = filter; |
| 196 } | 194 } |
| 197 } else { | 195 } else { |
| 198 e.style | 196 e.style |
| 199 ..setProperty('opacity', '$EPSILON') | 197 ..setProperty('opacity', '$EPSILON') |
| 200 ..setProperty('visibility', 'hidden'); | 198 ..setProperty('visibility', 'hidden'); |
| 201 } | 199 } |
| 202 }); | 200 }); |
| 203 } | 201 } |
| 204 | 202 |
| 205 void _hideTrackingCircles() { | 203 void _hideTrackingCircles() { |
| 206 root.selectAll('.line-rdr-point') | 204 root.selectAll('.line-rdr-point') |
| 207 ..style('opacity', '0.0') | 205 ..style('opacity', '0.0') |
| 208 ..style('visibility', 'hidden'); | 206 ..style('visibility', 'hidden'); |
| 209 } | 207 } |
| 210 | 208 |
| 211 int _getNearestRowIndex(double x) { | 209 int _getNearestRowIndex(num x) { |
| 212 var lastSmallerValue = 0; | 210 var lastSmallerValue = 0; |
| 213 var chartX = x - area.layout.renderArea.x; | 211 var chartX = x - area.layout.renderArea.x; |
| 214 for (var i = 0; i < _xPositions.length; i++) { | 212 for (var i = 0; i < _xPositions.length; i++) { |
| 215 var pos = _xPositions[i]; | 213 var pos = _xPositions[i]; |
| 216 if (pos < chartX) { | 214 if (pos < chartX) { |
| 217 lastSmallerValue = pos; | 215 lastSmallerValue = pos; |
| 218 } else { | 216 } else { |
| 219 return i == 0 ? 0 : | 217 return i == 0 |
| 220 (chartX - lastSmallerValue <= pos - chartX) ? i - 1 : i; | 218 ? 0 |
| 219 : (chartX - lastSmallerValue <= pos - chartX) ? i - 1 : i; |
| 221 } | 220 } |
| 222 } | 221 } |
| 223 return _xPositions.length - 1; | 222 return _xPositions.length - 1; |
| 224 } | 223 } |
| 225 | 224 |
| 226 void _trackPointerInArea() { | 225 void _trackPointerInArea() { |
| 227 _trackingPointsCreated = false; | 226 _trackingPointsCreated = false; |
| 228 _disposer.add(area.onMouseMove.listen((ChartEvent event) { | 227 _disposer.add(area.onMouseMove.listen((ChartEvent event) { |
| 229 if (area.layout.renderArea.contains(event.chartX, event.chartY)) { | 228 if (area.layout.renderArea.contains(event.chartX, event.chartY)) { |
| 230 var row = _getNearestRowIndex(event.chartX); | 229 var row = _getNearestRowIndex(event.chartX); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 266 if (area.state != null && | 265 if (area.state != null && |
| 267 area.state.preview == int.parse(e.dataset['column'])) { | 266 area.state.preview == int.parse(e.dataset['column'])) { |
| 268 area.state.preview = null; | 267 area.state.preview = null; |
| 269 } | 268 } |
| 270 if (mouseOutController != null && e.tagName == 'circle') { | 269 if (mouseOutController != null && e.tagName == 'circle') { |
| 271 mouseOutController.add(new DefaultChartEventImpl( | 270 mouseOutController.add(new DefaultChartEventImpl( |
| 272 scope.event, area, series, _savedOverRow, _savedOverColumn, d)); | 271 scope.event, area, series, _savedOverRow, _savedOverColumn, d)); |
| 273 } | 272 } |
| 274 } | 273 } |
| 275 } | 274 } |
| OLD | NEW |