Chromium Code Reviews| Index: runtime/bin/vmstats/bargraph.dart |
| =================================================================== |
| --- runtime/bin/vmstats/bargraph.dart (revision 0) |
| +++ runtime/bin/vmstats/bargraph.dart (revision 0) |
| @@ -0,0 +1,231 @@ |
| +part of vmstats; |
|
siva
2013/03/05 23:12:44
Need some copyright info on top?
part of dart:vms
srdjan
2013/03/05 23:17:30
Missing copyright
Tom Ball
2013/03/06 00:34:43
Done.
Tom Ball
2013/03/06 00:34:43
Done.
|
| + |
| +class BarGraph { |
| + CanvasElement _canvas; |
| + GraphModel _model; |
| + List<Element> _elements; |
| + num scaleHeight = 0; |
|
srdjan
2013/03/05 23:17:30
s/num/double/
Tom Ball
2013/03/06 00:34:43
Done.
|
| + |
| + final int SAMPLE_WIDTH = 5; |
|
srdjan
2013/03/05 23:17:30
static const int (otherwise they get initialized w
Tom Ball
2013/03/06 00:34:43
Done.
|
| + final int LEFT_MARGIN = 50; |
| + final int RIGHT_MARGIN = 150; |
| + final int LEGEND_WIDTH = 130; |
| + final int LEGEND_Y = 20; |
| + final int INSIDE_MARGIN = 2; |
| + |
| + final int NUM_DIVIDERS = 5; |
| + final String FONT = "14px sans-serif"; |
| + |
| + BarGraph(CanvasElement canvas, List<Element> elements) { |
|
srdjan
2013/03/05 23:17:30
BarGraph(this._canvas, this._elements) {
....
Tom Ball
2013/03/06 00:34:43
Done.
|
| + _canvas = canvas; |
| + _elements = elements; |
| + int maxElements = |
|
siva
2013/03/05 23:12:44
var maxElements
Tom Ball
2013/03/06 00:34:43
Done.
|
| + (_canvas.width - LEFT_MARGIN - RIGHT_MARGIN) ~/ SAMPLE_WIDTH; |
| + _model = new GraphModel(maxElements); |
| + _model.addListener(drawGraph, null); |
| + drawBarGraph(); |
| + } |
| + |
| + void addSample(List<int> segments) { |
| + if (segments.length != _elements.length) { |
| + throw new RuntimeError('invalid sample size for graph'); |
| + } |
| + _model.addSample(segments); |
| + } |
| + |
| + void drawBarGraph() { |
| + // Draw chart's outer box. |
| + var context = _canvas.context2d; |
| + context.beginPath(); |
| + context.strokeStyle = 'black'; |
| + // The '2's are the width of the line, even though 1 is specified. |
| + context.strokeRect( |
| + LEFT_MARGIN - 2, 1, _canvas.width - LEFT_MARGIN - RIGHT_MARGIN + 2, |
| + _canvas.height - 2, 1); |
| + |
| + // Draw legend. |
| + int x = _canvas.width - LEGEND_WIDTH; |
| + int y = LEGEND_Y; |
|
siva
2013/03/05 23:12:44
var x ...
var y ...
Tom Ball
2013/03/06 00:34:43
Done.
|
| + context.font = FONT; |
| + for (int i = 0; i < _elements.length; i++) { |
|
siva
2013/03/05 23:12:44
for (var i = 0....
Tom Ball
2013/03/06 00:34:43
Done.
|
| + context.fillStyle = _elements[i].color; |
| + context.fillRect(x, y, 20, 20); |
| + context.fillStyle = 'black'; |
| + context.fillText(_elements[i]._name, x + 30, y + 15); |
| + y += 30; |
|
srdjan
2013/03/05 23:17:30
Optional: name constants.
|
| + } |
| + } |
| + |
| + void drawGraph(var unused) { |
|
siva
2013/03/05 23:12:44
Why do we have an unused argument here, can it be
Tom Ball
2013/03/06 00:34:43
It's standard in the Observable pattern to pass th
|
| + int graphHeight = _model.maxTotal; |
| + int width = _canvas.clientWidth; |
| + int height = _canvas.clientHeight; |
|
siva
2013/03/05 23:12:44
Ditto comment about use of types for local variabl
Tom Ball
2013/03/06 00:34:43
Done.
|
| + if (graphHeight >= scaleHeight) { |
| + // Make scale height a bit higher to allow for growth, and |
| + // round to nearest 100. |
| + scaleHeight = graphHeight * 1.2; |
| + scaleHeight = ((scaleHeight / 100).ceil() * 100).toInt(); |
|
srdjan
2013/03/05 23:17:30
Why to toInt? Do you want to truncate or round?
Tom Ball
2013/03/06 00:34:43
Neither, I want to display the number as an int (n
|
| + } |
| + num scale = height / scaleHeight; |
|
srdjan
2013/03/05 23:17:30
double scale =
|
| + drawValues(scaleHeight, scale); |
| + drawChart(scaleHeight, scale); |
| + } |
| + |
| + void drawChart(int maxHeight, num scale) { |
|
srdjan
2013/03/05 23:17:30
double scale
Tom Ball
2013/03/06 00:34:43
Done.
|
| + int dividerHeight = maxHeight ~/ NUM_DIVIDERS; |
| + var context = _canvas.context2d; |
| + context.beginPath(); |
| + int height = maxHeight; |
| + num scaledY = dividerHeight * scale; |
| + |
| + // Draw the vertical axis values and lines. |
| + for (int i = 1; i < NUM_DIVIDERS; i++) { |
| + height -= (dividerHeight ~/100) * 100; |
|
srdjan
2013/03/05 23:17:30
space between / and 100.
Tom Ball
2013/03/06 00:34:43
Done.
|
| + context.font = FONT; |
| + context.fillStyle = 'black'; |
| + context.textAlign = 'right'; |
| + context.textBaseline = 'middle'; |
| + context.fillText(height.toString(), LEFT_MARGIN - 10, scaledY); |
| + context.moveTo(LEFT_MARGIN - 2, scaledY); |
| + context.strokeStyle = 'grey'; |
| + context.lineWidth = 0.5; |
| + context.lineTo(_canvas.width - RIGHT_MARGIN, scaledY); |
| + context.stroke(); |
| + scaledY += dividerHeight * scale; |
| + } |
| + } |
| + |
| + void drawValues(int maxHeight, num scale) { |
| + Iterator<Sample> iterator = _model.iterator; |
| + int x = LEFT_MARGIN + INSIDE_MARGIN; |
| + |
| + while (iterator.moveNext()) { |
| + Sample s = iterator.current; |
| + int y = INSIDE_MARGIN; |
| + if (s != null) { |
| + num blankHeight = scaleHeight - s.total; |
| + drawVerticalSegment(x, y, SAMPLE_WIDTH, blankHeight, 'white', scale); |
| + y += blankHeight; |
| + for (int i = s.length - 1; i >= 0; i--) { |
| + int h = s[i]; |
| + drawVerticalSegment(x, y, SAMPLE_WIDTH, h, _elements[i].color, scale); |
| + y += s[i]; |
| + } |
| + } else { |
| + drawVerticalSegment(x, INSIDE_MARGIN, SAMPLE_WIDTH, |
| + maxHeight, 'white', scale); |
| + } |
| + x += SAMPLE_WIDTH ; |
| + } |
| + } |
| + |
| + void drawVerticalSegment(int x, int y, int w, int h, String color, |
| + num scale) { |
| + var context = _canvas.context2d; |
| + y = (y * scale).floor(); |
| + h = (h * scale).ceil(); |
| + context.beginPath(); |
| + context.lineWidth = w; |
| + context.fillStyle = color; |
| + context.strokeStyle = color; |
| + if (x < INSIDE_MARGIN) { |
| + x = INSIDE_MARGIN; |
| + } |
| + if (y < INSIDE_MARGIN) { |
| + y = INSIDE_MARGIN; |
| + } |
| + int max = _canvas.width - INSIDE_MARGIN; |
| + if ((x + w) > max) { |
| + w = max - x; |
| + } |
| + max = _canvas.height - INSIDE_MARGIN; |
| + if ((y + h) > max) { |
| + h = max - y; |
| + } |
| + context.moveTo(x, y); |
| + context.lineTo(x, y + h); |
| + context.stroke(); |
| + } |
| +} |
| + |
| +class GraphModel extends ObservableModel { |
| + List<Sample> _samples = new List<Sample>(); |
| + int _maxSize; |
| + |
| + final int _MAX_LENGTH = 999999999; |
|
srdjan
2013/03/05 23:17:30
static const. Is this MAX_LENGTH or just LARGE_LE
Tom Ball
2013/03/06 00:34:43
Changed to LARGE_LENGTH
|
| + |
| + GraphModel(int maxSize) { |
|
srdjan
2013/03/05 23:17:30
GraphModel (this._maxSize);
Tom Ball
2013/03/06 00:34:43
Done.
|
| + _maxSize = maxSize; |
| + } |
|
siva
2013/03/05 23:12:44
GraphModel(this._maxSize);
Tom Ball
2013/03/06 00:34:43
Done.
|
| + |
| + void addSample(List<int> segments) { |
| + int len = _samples.length; |
| + if (_samples.length >= _maxSize) { |
| + _samples.remove(_samples.first); |
| + } |
| + _samples.add(new Sample(segments)); |
| + notifySuccess(); |
| + } |
| + |
| + int get maxSize => _maxSize; |
| + |
| + Iterator<Sample> get iterator => _samples.iterator; |
| + |
| + Sample operator[](int i) => _samples[i]; |
| + |
| + /** |
| + * Returns the minimum total from all the samples. |
| + */ |
| + int get minTotal { |
| + int min = _MAX_LENGTH; |
| + _samples.forEach((Sample s) => min = (s.total < min ? s.total : min)); |
| + return min; |
| + } |
| + |
| + /** |
| + * Returns the maximum total from all the samples. |
| + */ |
| + int get maxTotal { |
| + int max = 0; |
| + _samples.forEach((Sample s) => max = (s.total > max ? s.total : max)); |
| + return max; |
| + } |
| +} |
| + |
| +/** |
| + * An element is a data type that gets charted. Each element has a name for |
| + * the legend, and a color for the bar graph. The number of elements in a |
| + * graph should match the number of segments in each sample. |
| + */ |
| +class Element { |
| + String _name; |
|
srdjan
2013/03/05 23:17:30
final String _name;
final String _ color_
Tom Ball
2013/03/06 00:34:43
Done.
|
| + String _color; // Any description the DOM will accept, like "red" or "#000". |
| + |
| + Element(String name, String color) { |
|
srdjan
2013/03/05 23:17:30
const Element(this._name, this._color);
Tom Ball
2013/03/06 00:34:43
Done.
|
| + _name = name; |
| + _color = color; |
| + } |
| + |
| + String get name => _name; |
| + String get color => _color; |
| +} |
|
siva
2013/03/05 23:12:44
I think this class can be written as:
class Elemen
Tom Ball
2013/03/06 00:34:43
Done.
|
| + |
| +/** |
| + * A sample is a list of segment lengths. |
| + */ |
| +class Sample { |
| + List<int> _segments; |
| + |
| + Sample(List<int> segments) { |
|
srdjan
2013/03/05 23:17:30
this._segments
Tom Ball
2013/03/06 00:34:43
Done.
|
| + _segments = segments; |
| + } |
|
siva
2013/03/05 23:12:44
Sample(this._segments);
Tom Ball
2013/03/06 00:34:43
Done.
|
| + |
| + int get length => _segments.length; |
| + int operator[](int i) => _segments[i]; |
| + |
| + Iterator<int> get iterator => _segments.iterator; |
| + |
| + int get total { |
|
srdjan
2013/03/05 23:17:30
Should this be a method instead of a getter? The b
Tom Ball
2013/03/06 00:34:43
Done.
Tom Ball
2013/03/06 00:34:43
Done.
|
| + return _segments.reduce(0, (int prev, int element) => prev + element); |
| + } |
| +} |