| 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 BarChartRenderer extends CartesianRendererBase { | |
| 12 static const RADIUS = 2; | |
| 13 | |
| 14 final Iterable<int> dimensionsUsingBand = const[0]; | |
| 15 final bool alwaysAnimate; | |
| 16 | |
| 17 @override | |
| 18 final String name = "bar-rdr"; | |
| 19 | |
| 20 BarChartRenderer({this.alwaysAnimate: false}); | |
| 21 | |
| 22 /// Returns false if the number of dimension axes on the area is 0. | |
| 23 /// Otherwise, the first dimension scale is used to render the chart. | |
| 24 @override | |
| 25 bool prepare(ChartArea area, ChartSeries series) { | |
| 26 _ensureAreaAndSeries(area, series); | |
| 27 return area is CartesianArea; | |
| 28 } | |
| 29 | |
| 30 @override | |
| 31 void draw(Element element, {Future schedulePostRender}) { | |
| 32 _ensureReadyToDraw(element); | |
| 33 | |
| 34 var verticalBars = !area.config.isLeftAxisPrimary; | |
| 35 | |
| 36 var measuresCount = series.measures.length, | |
| 37 measureScale = area.measureScales(series).first, | |
| 38 dimensionScale = area.dimensionScales.first; | |
| 39 | |
| 40 var rows = new List() | |
| 41 ..addAll(area.data.rows.map((e) => | |
| 42 new List.generate( | |
| 43 measuresCount, (i) => e[series.measures.elementAt(i)]))); | |
| 44 | |
| 45 var dimensionVals = area.data.rows.map( | |
| 46 (row) => row.elementAt(area.config.dimensions.first)).toList(); | |
| 47 | |
| 48 var bars = new OrdinalScale() | |
| 49 ..domain = new Range(series.measures.length).toList() | |
| 50 ..rangeRoundBands([0, dimensionScale.rangeBand]); | |
| 51 | |
| 52 // Create and update the bar groups. | |
| 53 | |
| 54 var groups = root.selectAll('.bar-rdr-rowgroup').data(rows); | |
| 55 var animateBarGroups = alwaysAnimate || !groups.isEmpty; | |
| 56 | |
| 57 groups.enter.append('g') | |
| 58 ..classed('bar-rdr-rowgroup') | |
| 59 ..attrWithCallback('transform', (d, i, c) => verticalBars ? | |
| 60 'translate(${dimensionScale.scale(dimensionVals[i])}, 0)' : | |
| 61 'translate(0, ${dimensionScale.scale(dimensionVals[i])})'); | |
| 62 groups.attrWithCallback('data-row', (d, i, e) => i); | |
| 63 groups.exit.remove(); | |
| 64 | |
| 65 if (animateBarGroups) { | |
| 66 groups.transition() | |
| 67 ..attrWithCallback('transform', (d, i, c) => verticalBars ? | |
| 68 'translate(${dimensionScale.scale(dimensionVals[i])}, 0)' : | |
| 69 'translate(0, ${dimensionScale.scale(dimensionVals[i])})') | |
| 70 ..duration(theme.transitionDurationMilliseconds); | |
| 71 } | |
| 72 | |
| 73 // TODO: Test interactions between stroke width and bar width. | |
| 74 | |
| 75 var barWidth = bars.rangeBand.abs() - | |
| 76 theme.defaultSeparatorWidth - theme.defaultStrokeWidth, | |
| 77 strokeWidth = theme.defaultStrokeWidth, | |
| 78 strokeWidthOffset = strokeWidth ~/ 2; | |
| 79 | |
| 80 // Create and update the bars | |
| 81 // Avoids animation on first render unless alwaysAnimate is set to true. | |
| 82 | |
| 83 var bar = groups.selectAll('.bar-rdr-bar').dataWithCallback( | |
| 84 (d, i, c) => rows[i]), | |
| 85 scaled0 = measureScale.scale(0).round(); | |
| 86 | |
| 87 var getBarLength = (d) { | |
| 88 var scaledVal = measureScale.scale(d).round(), | |
| 89 ht = verticalBars | |
| 90 ? (d >= 0 ? scaled0 - scaledVal : scaledVal - scaled0) | |
| 91 : (d >= 0 ? scaledVal - scaled0 : scaled0 - scaledVal); | |
| 92 ht = ht - strokeWidth; | |
| 93 return (ht < 0) ? 0 : ht; | |
| 94 }; | |
| 95 var getBarPos = (d) { | |
| 96 var scaledVal = measureScale.scale(d).round(); | |
| 97 return verticalBars | |
| 98 ? (d >= 0 ? scaledVal : scaled0) + strokeWidthOffset | |
| 99 : (d >= 0 ? scaled0 : scaledVal) + strokeWidthOffset; | |
| 100 }; | |
| 101 var buildPath = (d, int i, bool animate) { | |
| 102 if (d == null || d == 0) return ''; | |
| 103 if (verticalBars) { | |
| 104 var fn = d > 0 ? topRoundedRect : bottomRoundedRect; | |
| 105 return fn( | |
| 106 bars.scale(i).toInt() + strokeWidthOffset, | |
| 107 animate ? rect.height : getBarPos(d), | |
| 108 barWidth, animate ? 0 : getBarLength(d), RADIUS); | |
| 109 } else { | |
| 110 var fn = d > 0 ? rightRoundedRect : leftRoundedRect; | |
| 111 return fn( | |
| 112 getBarPos(d), bars.scale(i).toInt() + strokeWidthOffset, | |
| 113 animate ? 0 : getBarLength(d), barWidth, RADIUS); | |
| 114 } | |
| 115 }; | |
| 116 | |
| 117 bar.enter.appendWithCallback((d, i, e) { | |
| 118 var rect = Namespace.createChildElement('path', e), | |
| 119 measure = series.measures.elementAt(i), | |
| 120 row = int.parse(e.dataset['row']), | |
| 121 color = colorForValue(measure, row), | |
| 122 filter = filterForValue(measure, row), | |
| 123 style = stylesForValue(measure, row); | |
| 124 | |
| 125 if (!isNullOrEmpty(style)) { | |
| 126 rect.classes.addAll(style); | |
| 127 } | |
| 128 rect.classes.add('bar-rdr-bar'); | |
| 129 | |
| 130 rect.attributes | |
| 131 ..['d'] = buildPath(d, i, animateBarGroups) | |
| 132 ..['stroke-width'] = '${strokeWidth}px' | |
| 133 ..['fill'] = color | |
| 134 ..['stroke'] = color; | |
| 135 | |
| 136 if (!isNullOrEmpty(filter)) { | |
| 137 rect.attributes['filter'] = filter; | |
| 138 } | |
| 139 if (!animateBarGroups) { | |
| 140 rect.attributes['data-column'] = '$measure'; | |
| 141 } | |
| 142 return rect; | |
| 143 }) | |
| 144 ..on('click', (d, i, e) => _event(mouseClickController, d, i, e)) | |
| 145 ..on('mouseover', (d, i, e) => _event(mouseOverController, d, i, e)) | |
| 146 ..on('mouseout', (d, i, e) => _event(mouseOutController, d, i, e)); | |
| 147 | |
| 148 if (animateBarGroups) { | |
| 149 bar.each((d, i, e) { | |
| 150 var measure = series.measures.elementAt(i), | |
| 151 row = int.parse(e.parent.dataset['row']), | |
| 152 color = colorForValue(measure, row), | |
| 153 filter = filterForValue(measure, row), | |
| 154 styles = stylesForValue(measure, row); | |
| 155 e.attributes | |
| 156 ..['data-column'] = '$measure' | |
| 157 ..['fill'] = color | |
| 158 ..['stroke'] = color; | |
| 159 e.classes | |
| 160 ..removeAll(ChartState.VALUE_CLASS_NAMES) | |
| 161 ..addAll(styles); | |
| 162 if (isNullOrEmpty(filter)) { | |
| 163 e.attributes.remove('filter'); | |
| 164 } else { | |
| 165 e.attributes['filter'] = filter; | |
| 166 } | |
| 167 }); | |
| 168 | |
| 169 bar.transition() | |
| 170 ..attrWithCallback('d', | |
| 171 (d, i, e) => buildPath(d, i, false)); | |
| 172 } | |
| 173 | |
| 174 bar.exit.remove(); | |
| 175 } | |
| 176 | |
| 177 @override | |
| 178 void dispose() { | |
| 179 if (root == null) return; | |
| 180 root.selectAll('.bar-rdr-rowgroup').remove(); | |
| 181 } | |
| 182 | |
| 183 @override | |
| 184 double get bandInnerPadding { | |
| 185 assert(series != null && area != null); | |
| 186 var measuresCount = series.measures.length; | |
| 187 return measuresCount > 2 ? 1 - (measuresCount / (measuresCount + 1)) : | |
| 188 area.theme.getDimensionAxisTheme().axisBandInnerPadding; | |
| 189 } | |
| 190 | |
| 191 @override | |
| 192 double get bandOuterPadding { | |
| 193 assert(series != null && area != null); | |
| 194 return area.theme.getDimensionAxisTheme().axisBandOuterPadding; | |
| 195 } | |
| 196 | |
| 197 @override | |
| 198 void handleStateChanges(List<ChangeRecord> changes) { | |
| 199 var groups = host.querySelectorAll('.bar-rdr-rowgroup'); | |
| 200 if (groups == null || groups.isEmpty) return; | |
| 201 | |
| 202 for(int i = 0, len = groups.length; i < len; ++i) { | |
| 203 var group = groups.elementAt(i), | |
| 204 bars = group.querySelectorAll('.bar-rdr-bar'), | |
| 205 row = int.parse(group.dataset['row']); | |
| 206 | |
| 207 for(int j = 0, barsCount = bars.length; j < barsCount; ++j) { | |
| 208 var bar = bars.elementAt(j), | |
| 209 column = int.parse(bar.dataset['column']), | |
| 210 color = colorForValue(column, row), | |
| 211 filter = filterForValue(column, row); | |
| 212 | |
| 213 bar.classes.removeAll(ChartState.VALUE_CLASS_NAMES); | |
| 214 bar.classes.addAll(stylesForValue(column, row)); | |
| 215 bar.attributes | |
| 216 ..['fill'] = color | |
| 217 ..['stroke'] = color; | |
| 218 if (isNullOrEmpty(filter)) { | |
| 219 bar.attributes.remove('filter'); | |
| 220 } else { | |
| 221 bar.attributes['filter'] = filter; | |
| 222 } | |
| 223 } | |
| 224 } | |
| 225 } | |
| 226 | |
| 227 void _event(StreamController controller, data, int index, Element e) { | |
| 228 if (controller == null) return; | |
| 229 var rowStr = e.parent.dataset['row']; | |
| 230 var row = rowStr != null ? int.parse(rowStr) : null; | |
| 231 controller.add( | |
| 232 new DefaultChartEventImpl(scope.event, area, | |
| 233 series, row, series.measures.elementAt(index), data)); | |
| 234 } | |
| 235 } | |
| OLD | NEW |