662c2a8262
The RuntimeError has been used to report cyclic initialization errors in
dart2js.
It has also been used for other, unrelated, uses, where it makes
little to no sense.
The RuntimeError type has been removed, but added in js_helper.dart,
where dart2js uses it.
Uses of new RuntimeError("some message") have been converted to
other errors (Argument/State/Unsupported). In some cases, just
throwing a String might have been just as good.
BUG=http://dartbug.com/11040
R=johnniwinther@google.com
Review URL: https://codereview.chromium.org//16154017
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@23584 260f80e4-7a28-3924-810f-c04153c831b5
226 lines
6.4 KiB
Dart
226 lines
6.4 KiB
Dart
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
part of dart.vmstats;
|
|
|
|
class BarGraph {
|
|
CanvasElement _canvas;
|
|
GraphModel _model;
|
|
List<Element> _elements;
|
|
double scaleHeight = 0;
|
|
|
|
static const int SAMPLE_WIDTH = 5;
|
|
static const int LEFT_MARGIN = 50;
|
|
static const int RIGHT_MARGIN = 150;
|
|
static const int LEGEND_WIDTH = 130;
|
|
static const int LEGEND_Y = 20;
|
|
static const int INSIDE_MARGIN = 2;
|
|
static const int LINE_WIDTH = 2;
|
|
|
|
static const int NUM_DIVIDERS = 5;
|
|
static const String FONT = "14px sans-serif";
|
|
|
|
BarGraph(this._canvas, this._elements) {
|
|
var 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 ArgumentError('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.
|
|
var x = _canvas.width - LEGEND_WIDTH;
|
|
var y = LEGEND_Y;
|
|
context.font = FONT;
|
|
for (var i = _elements.length - 1; i >= 0; 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(GraphModel model) {
|
|
var graphHeight = model.maxTotal;
|
|
var width = _canvas.clientWidth;
|
|
var 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);
|
|
}
|
|
var scale = height / scaleHeight;
|
|
drawValues(scaleHeight, scale);
|
|
drawChart(scaleHeight, scale);
|
|
}
|
|
|
|
void drawChart(int maxHeight, double scale) {
|
|
var dividerHeight = maxHeight ~/ NUM_DIVIDERS;
|
|
var context = _canvas.context2D;
|
|
context.beginPath();
|
|
var height = maxHeight.toInt();
|
|
var scaledY = dividerHeight * scale;
|
|
|
|
// Draw the vertical axis values and lines.
|
|
context.clearRect(0, 0, LEFT_MARGIN - INSIDE_MARGIN, maxHeight);
|
|
for (var 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 - INSIDE_MARGIN, 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;
|
|
var x = LEFT_MARGIN + INSIDE_MARGIN;
|
|
var y = INSIDE_MARGIN;
|
|
var w = _canvas.width - LEFT_MARGIN - RIGHT_MARGIN - INSIDE_MARGIN;
|
|
var h = (maxHeight * scale).ceil() - (2 * INSIDE_MARGIN);
|
|
_canvas.context2D.clearRect(x, y, w, h);
|
|
|
|
while (iterator.moveNext()) {
|
|
Sample s = iterator.current;
|
|
var y = INSIDE_MARGIN;
|
|
if (s != null) {
|
|
var blankHeight = scaleHeight - s.total();
|
|
drawVerticalSegment(x, y, SAMPLE_WIDTH, blankHeight, 'white', scale);
|
|
y += blankHeight;
|
|
for (int i = s.length - 1; i >= 0; i--) {
|
|
var 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;
|
|
}
|
|
var 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;
|
|
|
|
static const int _LARGE_LENGTH = 999999999;
|
|
|
|
GraphModel(this._maxSize) {}
|
|
|
|
void addSample(List<int> segments) {
|
|
var 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 {
|
|
var min = _LARGE_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 {
|
|
var max = 1; // Must be non-zero.
|
|
_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 {
|
|
final String name;
|
|
final String color; // Any description the DOM will accept, like "red".
|
|
|
|
Element(this.name, this.color) {}
|
|
}
|
|
|
|
/**
|
|
* A sample is a list of segment lengths.
|
|
*/
|
|
class Sample {
|
|
List<int> _segments;
|
|
|
|
Sample(this._segments) {}
|
|
|
|
int get length => _segments.length;
|
|
int operator[](int i) => _segments[i];
|
|
|
|
Iterator<int> get iterator => _segments.iterator;
|
|
|
|
int total() {
|
|
return _segments.fold(0, (int prev, int element) => prev + element);
|
|
}
|
|
}
|