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 |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
44 /// Constraints and known issues: | 44 /// Constraints and known issues: |
45 /// (0) The implementation isn't complete yet! Specifically for CartesianArea | 45 /// (0) The implementation isn't complete yet! Specifically for CartesianArea |
46 /// that uses two axes. | 46 /// that uses two axes. |
47 /// (1) Even with all the logic, single value mode does not work well | 47 /// (1) Even with all the logic, single value mode does not work well |
48 /// with StackedBarChartRenderer | 48 /// with StackedBarChartRenderer |
49 /// (2) Only mouse relative positioning is supported on LayoutArea | 49 /// (2) Only mouse relative positioning is supported on LayoutArea |
50 /// (3) Positioning only works for renderers that determine extent given a | 50 /// (3) Positioning only works for renderers that determine extent given a |
51 /// single row. Eg: Would not work with a water-fall chart. | 51 /// single row. Eg: Would not work with a water-fall chart. |
52 /// | 52 /// |
53 class Hovercard implements ChartBehavior { | 53 class Hovercard implements ChartBehavior { |
| 54 static const _HOVERCARD_OFFSET = 20; |
54 final HovercardBuilder builder; | 55 final HovercardBuilder builder; |
55 | 56 |
56 bool _isMouseTracking; | 57 bool _isMouseTracking; |
57 bool _isMultiValue; | 58 bool _isMultiValue; |
58 bool _showDimensionTitle; | 59 bool _showDimensionTitle; |
59 Iterable<int> _columnsToShow; | 60 Iterable<int> _columnsToShow; |
60 | 61 |
61 Iterable placementOrder = const [ | 62 Iterable placementOrder = const [ |
62 'orientation', | 63 'orientation', |
63 'top', | 64 'top', |
64 'right', | 65 'right', |
65 'bottom', | 66 'bottom', |
66 'left', | 67 'left', |
67 'orientation' | 68 'orientation' |
68 ]; | 69 ]; |
69 int offset = 20; | |
70 | 70 |
71 ChartArea _area; | 71 ChartArea _area; |
72 ChartState _state; | 72 ChartState _state; |
73 SubscriptionsDisposer _disposer = new SubscriptionsDisposer(); | 73 SubscriptionsDisposer _disposer = new SubscriptionsDisposer(); |
74 | 74 |
75 Element _hovercardRoot; | 75 Element _hovercardRoot; |
76 | 76 |
77 Hovercard( | 77 Hovercard( |
78 {bool isMouseTracking, | 78 {bool isMouseTracking, |
79 bool isMultiValue: false, | 79 bool isMultiValue: false, |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
174 _positionOnTwoDimensionCartesian(column, row); | 174 _positionOnTwoDimensionCartesian(column, row); |
175 } else { | 175 } else { |
176 _positionOnSingleDimensionCartesian(column, row); | 176 _positionOnSingleDimensionCartesian(column, row); |
177 } | 177 } |
178 } else { | 178 } else { |
179 _positionOnLayout(column, row); | 179 _positionOnLayout(column, row); |
180 } | 180 } |
181 } | 181 } |
182 | 182 |
183 void _positionAtMousePointer(ChartEvent e) => | 183 void _positionAtMousePointer(ChartEvent e) => |
184 _positionAtPoint(e.chartX, e.chartY, offset, offset, false, false); | 184 _positionAtPoint(e.chartX, e.chartY, _HOVERCARD_OFFSET, _HOVERCARD_OFFSET,
false, false); |
185 | 185 |
186 void _positionOnLayout(column, row) { | 186 void _positionOnLayout(column, row) { |
187 // Currently for layouts, when hovercard is triggered due to change | 187 // Currently for layouts, when hovercard is triggered due to change |
188 // in ChartState, we render hovercard in the middle of layout. | 188 // in ChartState, we render hovercard in the middle of layout. |
189 // TODO: Get bounding rect from LayoutRenderer and position relative to it. | 189 // TODO: Get bounding rect from LayoutRenderer and position relative to it. |
190 } | 190 } |
191 | 191 |
192 void _positionOnTwoDimensionCartesian(int column, int row) { | 192 void _positionOnTwoDimensionCartesian(int column, int row) { |
193 // TODO: Implement multi dimension positioning. | 193 // TODO: Implement multi dimension positioning. |
194 } | 194 } |
195 | 195 |
196 void _positionOnSingleDimensionCartesian(int column, int row) { | 196 void _positionOnSingleDimensionCartesian(int column, int row) { |
197 CartesianArea area = _area; | 197 CartesianArea area = _area; |
198 var dimensionCol = area.config.dimensions.first, | 198 var dimensionCol = area.config.dimensions.first, |
199 dimensionScale = area.dimensionScales.first, | 199 dimensionScale = area.dimensionScales.first, |
200 measureScale = _getScaleForColumn(column), | 200 measureScale = _getScaleForColumn(column), |
201 dimensionOffset = this.offset, | 201 dimensionOffset = _HOVERCARD_OFFSET, |
202 dimensionCenterOffset = 0; | 202 dimensionCenterOffset = 0; |
203 | 203 |
204 // If we are using bands on the one axis that is shown | 204 // If we are using bands on the one axis that is shown |
205 // update position and offset accordingly. | 205 // update position and offset accordingly. |
206 if (area.dimensionsUsingBands.contains(dimensionCol)) { | 206 if (area.dimensionsUsingBands.contains(dimensionCol)) { |
207 assert(dimensionScale is OrdinalScale); | 207 assert(dimensionScale is OrdinalScale); |
208 dimensionOffset = (dimensionScale as OrdinalScale).rangeBand ~/ 2; | 208 dimensionOffset = (dimensionScale as OrdinalScale).rangeBand ~/ 2; |
209 dimensionCenterOffset = dimensionOffset; | 209 dimensionCenterOffset = dimensionOffset; |
210 } | 210 } |
211 | 211 |
212 var rowData = area.data.rows.elementAt(row), | 212 var rowData = area.data.rows.elementAt(row), |
213 measurePosition = 0, | 213 measurePosition = 0, |
214 isNegative = false, | 214 isNegative = false, |
215 dimensionPosition = dimensionScale | 215 dimensionPosition = dimensionScale |
216 .scale(rowData.elementAt(dimensionCol)) + | 216 .scale(rowData.elementAt(dimensionCol)) + |
217 dimensionCenterOffset; | 217 dimensionCenterOffset; |
218 | 218 |
219 if (_isMultiValue) { | 219 if (_isMultiValue) { |
220 var max = SMALL_INT_MIN, min = SMALL_INT_MAX; | 220 var max = SMALL_INT_MIN, min = SMALL_INT_MAX; |
221 area.config.series.forEach((ChartSeries series) { | 221 area.config.series.forEach((ChartSeries series) { |
222 CartesianRenderer renderer = series.renderer; | 222 CartesianRenderer renderer = series.renderer; |
223 Extent extent = renderer.extentForRow(rowData); | 223 Extent extent = renderer.extentForRow(rowData); |
224 if (extent.min < min) min = extent.min; | 224 if (extent.min < min) min = extent.min; |
225 if (extent.max > max) max = extent.max; | 225 if (extent.max > max) max = extent.max; |
226 measurePosition = measureScale.scale(max); | 226 measurePosition = measureScale.scale(max); |
227 isNegative = max < 0; | 227 isNegative = max < 0; |
228 }); | 228 }); |
229 } else { | 229 } else { |
230 var value = rowData.elementAt(column); | 230 var value = rowData.elementAt(column); |
231 isNegative = value < 0; | 231 if (value != null) { |
232 measurePosition = measureScale.scale(rowData.elementAt(column)); | 232 isNegative = value < 0; |
| 233 measurePosition = measureScale.scale(value); |
| 234 } |
233 } | 235 } |
234 | 236 |
235 if (area.config.isLeftAxisPrimary) { | 237 if (area.config.isLeftAxisPrimary) { |
236 _positionAtPoint(measurePosition, dimensionPosition, 0, dimensionOffset, | 238 _positionAtPoint(measurePosition, dimensionPosition, _HOVERCARD_OFFSET, |
237 isNegative, true); | 239 dimensionOffset, isNegative, true); |
238 } else { | 240 } else { |
239 _positionAtPoint(dimensionPosition, measurePosition, dimensionOffset, 0, | 241 _positionAtPoint(dimensionPosition, measurePosition, dimensionOffset, |
240 isNegative, false); | 242 _HOVERCARD_OFFSET, isNegative, false); |
241 } | 243 } |
242 } | 244 } |
243 | 245 |
244 void _positionAtPoint(num x, num y, num xBand, num yBand, bool negative, | 246 void _positionAtPoint(num x, num y, num xBand, num yBand, bool negative, |
245 [bool isLeftPrimary = false]) { | 247 [bool isLeftPrimary = false]) { |
246 var rect = _hovercardRoot.getBoundingClientRect(), | 248 var rect = _hovercardRoot.getBoundingClientRect(), |
247 width = rect.width, | 249 width = rect.width, |
248 height = rect.height, | 250 height = rect.height, |
249 scaleToHostY = (_area.theme.padding != null | 251 scaleToHostY = (_area.theme.padding != null |
250 ? _area.theme.padding.top | 252 ? _area.theme.padding.top |
(...skipping 30 matching lines...) Expand all Loading... |
281 top = isLeftPrimary ? y - height / 2 : y; | 283 top = isLeftPrimary ? y - height / 2 : y; |
282 left = negative ? x + xBand : x - (width + xBand); | 284 left = negative ? x + xBand : x - (width + xBand); |
283 } | 285 } |
284 if (placement == 'bottom') { | 286 if (placement == 'bottom') { |
285 top = negative ? y - (height + yBand) : y + yBand; | 287 top = negative ? y - (height + yBand) : y + yBand; |
286 left = isLeftPrimary ? x - width : x - width / 2; | 288 left = isLeftPrimary ? x - width : x - width / 2; |
287 } | 289 } |
288 | 290 |
289 // Check if the popup is contained in the RenderArea. | 291 // Check if the popup is contained in the RenderArea. |
290 // If not, try other placements. | 292 // If not, try other placements. |
291 if (top > 0 && | 293 if (top >= 0 && |
292 left > 0 && | 294 left >= 0 && |
293 top + height < renderAreaHeight && | 295 top + height < renderAreaHeight && |
294 left + width < renderAreaWidth) { | 296 left + width < renderAreaWidth) { |
295 break; | 297 break; |
296 } | 298 } |
297 } | 299 } |
298 | 300 |
299 _hovercardRoot.style | 301 _hovercardRoot.style |
300 ..top = '${top + scaleToHostY}px' | 302 ..top = '${top + scaleToHostY}px' |
301 ..left = '${left + scaleToHostX}px'; | 303 ..left = '${left + scaleToHostX}px'; |
302 } | 304 } |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
406 } | 408 } |
407 } | 409 } |
408 if (formatter == null) { | 410 if (formatter == null) { |
409 // Formatter function must return String. Default to identity function | 411 // Formatter function must return String. Default to identity function |
410 // but return the toString() instead. | 412 // but return the toString() instead. |
411 formatter = (x) => x.toString(); | 413 formatter = (x) => x.toString(); |
412 } | 414 } |
413 return formatter; | 415 return formatter; |
414 } | 416 } |
415 } | 417 } |
OLD | NEW |