| 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;
|
| +
|
| +class BarGraph {
|
| + CanvasElement _canvas;
|
| + GraphModel _model;
|
| + List<Element> _elements;
|
| + num scaleHeight = 0;
|
| +
|
| + final int SAMPLE_WIDTH = 5;
|
| + 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) {
|
| + _canvas = canvas;
|
| + _elements = elements;
|
| + int maxElements =
|
| + (_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;
|
| + context.font = FONT;
|
| + for (int i = 0; i < _elements.length; i++) {
|
| + 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;
|
| + }
|
| + }
|
| +
|
| + void drawGraph(var unused) {
|
| + int graphHeight = _model.maxTotal;
|
| + int width = _canvas.clientWidth;
|
| + int height = _canvas.clientHeight;
|
| + 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();
|
| + }
|
| + num scale = height / scaleHeight;
|
| + drawValues(scaleHeight, scale);
|
| + drawChart(scaleHeight, scale);
|
| + }
|
| +
|
| + void drawChart(int maxHeight, num scale) {
|
| + 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;
|
| + 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;
|
| +
|
| + GraphModel(int maxSize) {
|
| + _maxSize = maxSize;
|
| + }
|
| +
|
| + 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;
|
| + String _color; // Any description the DOM will accept, like "red" or "#000".
|
| +
|
| + Element(String name, String color) {
|
| + _name = name;
|
| + _color = color;
|
| + }
|
| +
|
| + String get name => _name;
|
| + String get color => _color;
|
| +}
|
| +
|
| +/**
|
| + * A sample is a list of segment lengths.
|
| + */
|
| +class Sample {
|
| + List<int> _segments;
|
| +
|
| + Sample(List<int> segments) {
|
| + _segments = segments;
|
| + }
|
| +
|
| + int get length => _segments.length;
|
| + int operator[](int i) => _segments[i];
|
| +
|
| + Iterator<int> get iterator => _segments.iterator;
|
| +
|
| + int get total {
|
| + return _segments.reduce(0, (int prev, int element) => prev + element);
|
| + }
|
| +}
|
|
|