| 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 |