|
2 | 2 | * Copyright (C) Microsoft Corporation. All rights reserved.
|
3 | 3 | *--------------------------------------------------------*/
|
4 | 4 |
|
| 5 | +import { Metric } from './baseMetric'; |
5 | 6 | import { Canvas } from './canvas';
|
6 |
| -import { GraphCanvas } from './graphCanvas'; |
7 | 7 |
|
8 | 8 | export const enum Sizing {
|
9 | 9 | LabelHeight = 18,
|
10 | 10 | RulerWidth = 1,
|
11 | 11 | Easing = 200,
|
12 | 12 | }
|
13 | 13 |
|
| 14 | +const rulers = 4; |
| 15 | + |
14 | 16 | export class FrameCanvas extends Canvas {
|
| 17 | + private rulers = this.createRulers(); |
| 18 | + private paths: [Path2D, string][] = []; |
15 | 19 | private ease?: { raf: number; dx: number };
|
16 | 20 | private rmSettingListener = this.settings.onChange(() => this.redraw());
|
17 | 21 |
|
18 | 22 | /**
|
19 | 23 | * Redraws the max values
|
20 | 24 | */
|
21 |
| - public updateMetrics(graphCanvas: GraphCanvas, shouldEase = this.settings.value.easing) { |
22 |
| - const { width: w, height: h } = this; |
23 |
| - |
24 |
| - let easeLength = graphCanvas.stepWidth; |
| 25 | + public updateMetrics(shouldEase = this.settings.value.easing) { |
| 26 | + let easeLength = this.width / this.settings.steps; |
25 | 27 | if (this.ease) {
|
26 | 28 | cancelAnimationFrame(this.ease.raf);
|
27 | 29 | easeLength += this.ease.dx;
|
28 | 30 | }
|
29 | 31 |
|
| 32 | + this.paths = this.settings.enabledMetrics.map(metric => [ |
| 33 | + this.createMetricPath(metric), |
| 34 | + this.settings.metricColor(metric), |
| 35 | + ]); |
| 36 | + |
30 | 37 | if (!shouldEase) {
|
31 |
| - this.drawGraph(graphCanvas, w, h); |
| 38 | + this.drawGraph(); |
32 | 39 | return;
|
33 | 40 | }
|
34 | 41 |
|
35 | 42 | let start: number;
|
36 | 43 | const draw = (now: number) => {
|
37 | 44 | const progress = Math.min(1, (now - start) / Sizing.Easing);
|
38 | 45 | const dx = easeLength * (1 - progress);
|
39 |
| - this.drawGraph(graphCanvas, w, h, dx); |
| 46 | + this.drawGraph(dx); |
40 | 47 |
|
41 | 48 | if (progress === 1) {
|
42 | 49 | this.ease = undefined;
|
@@ -65,18 +72,78 @@ export class FrameCanvas extends Canvas {
|
65 | 72 | }
|
66 | 73 | }
|
67 | 74 |
|
68 |
| - protected drawGraph(graphCanvas: GraphCanvas, w: number, h: number, dx = 0) { |
69 |
| - this.ctx.clearRect(0, 0, w, h); |
70 |
| - this.ctx.drawImage( |
71 |
| - graphCanvas.ctx.canvas, |
72 |
| - graphCanvas.width - w - dx, |
73 |
| - 0, |
74 |
| - w * this.scale, |
75 |
| - h * this.scale, |
76 |
| - 0, |
77 |
| - 0, |
78 |
| - w, |
79 |
| - h, |
80 |
| - ); |
| 75 | + protected redraw() { |
| 76 | + this.rulers = this.createRulers(); |
| 77 | + this.updateMetrics(false); |
| 78 | + } |
| 79 | + |
| 80 | + protected drawGraph(dx = 0) { |
| 81 | + this.ctx.clearRect(0, 0, this.width, this.height); |
| 82 | + |
| 83 | + // draw the background rulers first |
| 84 | + this.ctx.globalAlpha = 1; |
| 85 | + this.ctx.strokeStyle = this.settings.colors.border; |
| 86 | + this.ctx.stroke(this.rulers); |
| 87 | + |
| 88 | + this.ctx.save(); |
| 89 | + this.ctx.translate(dx, 0); |
| 90 | + |
| 91 | + // then the chart fill first (so lines will always be in the foreground) |
| 92 | + this.ctx.globalAlpha = 0.1; |
| 93 | + for (const [path, color] of this.paths) { |
| 94 | + this.ctx.fillStyle = color; |
| 95 | + this.ctx.fill(path); |
| 96 | + } |
| 97 | + |
| 98 | + // then stroke the lines |
| 99 | + this.ctx.globalAlpha = 1; |
| 100 | + for (const [path, color] of this.paths) { |
| 101 | + this.ctx.strokeStyle = color; |
| 102 | + this.ctx.stroke(path); |
| 103 | + } |
| 104 | + |
| 105 | + this.ctx.restore(); |
| 106 | + } |
| 107 | + |
| 108 | + private createRulers() { |
| 109 | + const path = new Path2D(); |
| 110 | + const { width, height } = this; |
| 111 | + const step = (height - Sizing.RulerWidth) / rulers; |
| 112 | + |
| 113 | + let y = step; |
| 114 | + for (let i = 0; i < rulers; i++) { |
| 115 | + const targetY = Math.floor(y) + Sizing.RulerWidth / 2; |
| 116 | + path.moveTo(0, targetY); |
| 117 | + path.lineTo(width, targetY); |
| 118 | + y += step; |
| 119 | + } |
| 120 | + |
| 121 | + return path; |
| 122 | + } |
| 123 | + |
| 124 | + private createMetricPath({ max, metrics }: Metric) { |
| 125 | + const { width, height } = this; |
| 126 | + const stepSize = width / this.settings.steps; |
| 127 | + const path = new Path2D(); |
| 128 | + |
| 129 | + if (metrics.length === 0) { |
| 130 | + path.moveTo(0, height - 1); |
| 131 | + path.lineTo(width, height - 1); |
| 132 | + return path; |
| 133 | + } |
| 134 | + |
| 135 | + let x = width; |
| 136 | + path.moveTo(x, height * (1 - metrics[metrics.length - 1] / max)); |
| 137 | + |
| 138 | + for (let i = metrics.length - 2; i >= 0; i--) { |
| 139 | + x -= stepSize; |
| 140 | + path.lineTo(x, height * (1 - metrics[i] / max)); |
| 141 | + } |
| 142 | + |
| 143 | + path.lineTo(x - stepSize, height - 1); |
| 144 | + path.lineTo(-stepSize, height - 1); |
| 145 | + path.lineTo(-stepSize, height + 2); |
| 146 | + path.lineTo(width, height + 2); |
| 147 | + return path; |
81 | 148 | }
|
82 | 149 | }
|
0 commit comments