| OLD | NEW |
| (Empty) |
| 1 // | |
| 2 // Copyright 2014 Google Inc. All rights reserved. | |
| 3 // | |
| 4 // Use of this source code is governed by a BSD-style | |
| 5 // license that can be found in the LICENSE file or at | |
| 6 // https://developers.google.com/open-source/licenses/bsd | |
| 7 // | |
| 8 | |
| 9 part of charted.charts; | |
| 10 | |
| 11 class DefaultChartAxisImpl { | |
| 12 CartesianArea _area; | |
| 13 ChartAxisConfig _config; | |
| 14 ChartAxisTheme _theme; | |
| 15 SvgAxisTicks _axisTicksPlacement; | |
| 16 | |
| 17 int _column; | |
| 18 bool _isDimension; | |
| 19 ChartColumnSpec _columnSpec; | |
| 20 | |
| 21 bool _isVertical; | |
| 22 String _orientation; | |
| 23 Scale _scale; | |
| 24 SelectionScope _scope; | |
| 25 | |
| 26 MutableRect size; | |
| 27 | |
| 28 DefaultChartAxisImpl.withAxisConfig(this._area, this._config); | |
| 29 DefaultChartAxisImpl(this._area); | |
| 30 | |
| 31 void initAxisDomain(int column, bool isDimension, Iterable domain) { | |
| 32 _columnSpec = _area.data.columns.elementAt(column); | |
| 33 _column = column; | |
| 34 _isDimension = isDimension; | |
| 35 | |
| 36 // If we don't have a scale yet, create one. | |
| 37 if (scale == null) { | |
| 38 _scale = _columnSpec.createDefaultScale(); | |
| 39 } | |
| 40 | |
| 41 // We have the scale, get theme. | |
| 42 _theme = isDimension | |
| 43 ? _area.theme.getDimensionAxisTheme(scale) | |
| 44 : _area.theme.getMeasureAxisTheme(scale); | |
| 45 | |
| 46 // Sets the domain if not using a custom scale. | |
| 47 if (_config == null || (_config != null && _config.scale == null)) { | |
| 48 scale.domain = domain; | |
| 49 scale.nice = !_isDimension; | |
| 50 } | |
| 51 } | |
| 52 | |
| 53 void initAxisScale(Iterable range) { | |
| 54 assert(scale != null); | |
| 55 if (scale is OrdinalScale) { | |
| 56 var usingBands = _area.dimensionsUsingBands.contains(_column), | |
| 57 innerPadding = usingBands ? _theme.axisBandInnerPadding : 1.0, | |
| 58 outerPadding = usingBands ? | |
| 59 _theme.axisBandOuterPadding : _theme.axisOuterPadding; | |
| 60 | |
| 61 // This is because when left axis is primary the first data row should | |
| 62 // appear on top of the y-axis instead of on bottom. | |
| 63 if (_area.config.isLeftAxisPrimary) { | |
| 64 range = range.toList().reversed; | |
| 65 } | |
| 66 (scale as OrdinalScale). | |
| 67 rangeRoundBands(range, innerPadding, outerPadding); | |
| 68 } else { | |
| 69 scale.range = range; | |
| 70 scale.ticksCount = _theme.axisTickCount; | |
| 71 } | |
| 72 } | |
| 73 | |
| 74 void prepareToDraw(String orientation) { | |
| 75 if (orientation == null) orientation = ORIENTATION_BOTTOM; | |
| 76 _orientation = orientation; | |
| 77 _isVertical = | |
| 78 _orientation == ORIENTATION_LEFT || _orientation == ORIENTATION_RIGHT; | |
| 79 | |
| 80 var layout = _area.layout.chartArea; | |
| 81 size = _isVertical | |
| 82 ? new MutableRect.size(_theme.verticalAxisWidth, layout.width) | |
| 83 : new MutableRect.size(layout.height, _theme.horizontalAxisHeight); | |
| 84 | |
| 85 // Handle auto re-sizing of horizontal axis. | |
| 86 if (_isVertical) { | |
| 87 var ticks = (_config != null && !isNullOrEmpty(_config.tickValues)) | |
| 88 ? _config.tickValues | |
| 89 : scale.ticks, | |
| 90 formatter = _columnSpec.formatter == null | |
| 91 ? scale.createTickFormatter() | |
| 92 : _columnSpec.formatter, | |
| 93 textMetrics = new TextMetrics(fontStyle: _theme.ticksFont), | |
| 94 formattedTicks = ticks.map((x) => formatter(x)).toList(), | |
| 95 shortenedTicks = formattedTicks; | |
| 96 | |
| 97 var width = textMetrics.getLongestTextWidth(formattedTicks).ceil(); | |
| 98 if (width > _theme.verticalAxisWidth) { | |
| 99 width = _theme.verticalAxisWidth; | |
| 100 shortenedTicks = formattedTicks.map( | |
| 101 (x) => textMetrics.ellipsizeText(x, width)).toList(); | |
| 102 } | |
| 103 if (_theme.verticalAxisAutoResize) { | |
| 104 size.width = | |
| 105 width + _theme.axisTickPadding + math.max(_theme.axisTickSize, 0); | |
| 106 } | |
| 107 _axisTicksPlacement = | |
| 108 new PrecomputedAxisTicks(ticks, formattedTicks, shortenedTicks); | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 void draw(Element element, SelectionScope scope, {bool preRender: false}) { | |
| 113 assert(element != null && element is GElement); | |
| 114 assert(scale != null); | |
| 115 | |
| 116 var rect = _area.layout.axes[_orientation], | |
| 117 renderAreaRect = _area.layout.renderArea, | |
| 118 range = _isVertical ? [rect.height, 0] : [0, rect.width], | |
| 119 innerTickSize = _theme.axisTickSize <= ChartAxisTheme.FILL_RENDER_AREA | |
| 120 ? 0 - (_isVertical ? renderAreaRect.width : renderAreaRect.height) | |
| 121 : _theme.axisTickSize, | |
| 122 tickValues = _config != null && !isNullOrEmpty(_config.tickValues) | |
| 123 ? _config.tickValues | |
| 124 : null; | |
| 125 | |
| 126 element.attributes['transform'] = 'translate(${rect.x}, ${rect.y})'; | |
| 127 | |
| 128 if (!_isVertical) { | |
| 129 _axisTicksPlacement = | |
| 130 new RotateHorizontalAxisTicks(rect, | |
| 131 _theme.ticksFont, _theme.axisTickSize + _theme.axisTickPadding); | |
| 132 } | |
| 133 | |
| 134 initAxisScale(range); | |
| 135 var axis = new SvgAxis(orientation: _orientation, | |
| 136 innerTickSize: innerTickSize, outerTickSize: 0, | |
| 137 tickPadding: _theme.axisTickPadding, | |
| 138 tickFormat: _columnSpec.formatter, tickValues: tickValues, | |
| 139 scale: scale); | |
| 140 | |
| 141 axis.create(element, scope, | |
| 142 axisTicksBuilder: _axisTicksPlacement, isRTL: _area.config.isRTL); | |
| 143 } | |
| 144 | |
| 145 void clear() { | |
| 146 } | |
| 147 | |
| 148 // Scale passed through configuration takes precedence | |
| 149 Scale get scale => | |
| 150 (_config != null && _config.scale != null) ? _config.scale : _scale; | |
| 151 | |
| 152 set scale(Scale value) => _scale = value; | |
| 153 } | |
| 154 | |
| 155 class PrecomputedAxisTicks implements SvgAxisTicks { | |
| 156 final int rotation = 0; | |
| 157 final Iterable ticks; | |
| 158 final Iterable formattedTicks; | |
| 159 final Iterable shortenedTicks; | |
| 160 const PrecomputedAxisTicks( | |
| 161 this.ticks, this.formattedTicks, this.shortenedTicks); | |
| 162 void init(SvgAxis axis) {} | |
| 163 } | |
| 164 | |
| 165 class RotateHorizontalAxisTicks implements SvgAxisTicks { | |
| 166 final Rect rect; | |
| 167 final String ticksFont; | |
| 168 final int tickLineLength; | |
| 169 | |
| 170 int rotation = 0; | |
| 171 Iterable ticks; | |
| 172 Iterable formattedTicks; | |
| 173 Iterable shortenedTicks; | |
| 174 | |
| 175 RotateHorizontalAxisTicks(this.rect, this.ticksFont, this.tickLineLength); | |
| 176 | |
| 177 void init(SvgAxis axis) { | |
| 178 assert( | |
| 179 axis.orientation == ORIENTATION_BOTTOM || | |
| 180 axis.orientation == ORIENTATION_TOP); | |
| 181 assert(ticksFont != null); | |
| 182 ticks = axis.tickValues; | |
| 183 formattedTicks = ticks.map((x) => axis.tickFormat(x)).toList(); | |
| 184 shortenedTicks = formattedTicks; | |
| 185 | |
| 186 var range = axis.scale.rangeExtent, | |
| 187 textMetrics = new TextMetrics(fontStyle: ticksFont), | |
| 188 allowedWidth = (range.max - range.min) ~/ ticks.length, | |
| 189 maxLabelWidth = textMetrics.getLongestTextWidth(formattedTicks); | |
| 190 | |
| 191 // Check if we need rotation | |
| 192 if (0.90 * allowedWidth < maxLabelWidth) { | |
| 193 var rectHeight = tickLineLength > 0 | |
| 194 ? rect.height - tickLineLength | |
| 195 : rect.height; | |
| 196 rotation = 45; | |
| 197 | |
| 198 // Check if we have enough space to render full chart | |
| 199 allowedWidth = (1.4142 * (rectHeight)) - (textMetrics.fontSize / 1.4142); | |
| 200 if (maxLabelWidth > allowedWidth) { | |
| 201 shortenedTicks = formattedTicks.map( | |
| 202 (x) => textMetrics.ellipsizeText(x, allowedWidth)).toList(); | |
| 203 } | |
| 204 } | |
| 205 } | |
| 206 } | |
| OLD | NEW |