gridRenderer.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import {mat3} from 'gl-matrix';
  2. import {FlamegraphTheme} from '../flamegraph/flamegraphTheme';
  3. import {getContext, measureText, Rect} from '../gl/utils';
  4. export function getIntervalTimeAtX(logicalSpaceToConfigView: mat3, x: number): number {
  5. const vector = logicalSpaceToConfigView[0] * x + logicalSpaceToConfigView[6];
  6. if (vector > 1) {
  7. return Math.round(vector);
  8. }
  9. return Math.round(vector * 10) / 10;
  10. }
  11. export function computeInterval(
  12. configView: Rect,
  13. logicalSpaceToConfigView: mat3
  14. ): number[] {
  15. // We want to draw an interval every 200px, this is similar to how speedscope draws it and it works well
  16. // (both visually pleasing and precise enough). It is pretty much identical to what speedscope does with
  17. // the safeguards for the intervals being too small.
  18. const target = 200;
  19. // Compute x at 200 and subtract left, so we have the interval
  20. const targetInterval =
  21. getIntervalTimeAtX(logicalSpaceToConfigView, target) - configView.left;
  22. const minInterval = Math.pow(10, Math.floor(Math.log10(targetInterval)));
  23. let interval = minInterval;
  24. if (targetInterval / interval > 5) {
  25. interval *= 5;
  26. } else if (targetInterval / interval > 2) {
  27. interval *= 2;
  28. }
  29. let x = Math.ceil(configView.left / interval) * interval;
  30. const intervals: number[] = [];
  31. while (x <= configView.right) {
  32. intervals.push(x);
  33. x += interval;
  34. }
  35. return intervals;
  36. }
  37. class GridRenderer {
  38. canvas: HTMLCanvasElement;
  39. context: CanvasRenderingContext2D;
  40. theme: FlamegraphTheme;
  41. formatter: (value: number) => string;
  42. constructor(
  43. canvas: HTMLCanvasElement,
  44. theme: FlamegraphTheme,
  45. formatter: (value: number) => string
  46. ) {
  47. this.canvas = canvas;
  48. this.theme = theme;
  49. this.formatter = formatter;
  50. this.context = getContext(canvas, '2d');
  51. }
  52. draw(
  53. configViewSpace: Rect,
  54. physicalViewRect: Rect,
  55. configViewToPhysicalSpace: mat3,
  56. logicalSpaceToConfigView: mat3,
  57. context: CanvasRenderingContext2D = this.context
  58. ): void {
  59. context.font = `${this.theme.SIZES.LABEL_FONT_SIZE * window.devicePixelRatio}px ${
  60. this.theme.FONTS.FONT
  61. }`;
  62. context.textBaseline = 'top';
  63. context.lineWidth = this.theme.SIZES.GRID_LINE_WIDTH / 2;
  64. // Draw the background of the top timeline
  65. context.fillStyle = this.theme.COLORS.GRID_FRAME_BACKGROUND_COLOR;
  66. context.fillRect(
  67. 0,
  68. this.theme.SIZES.GRID_LINE_WIDTH,
  69. physicalViewRect.width,
  70. this.theme.SIZES.LABEL_FONT_SIZE * window.devicePixelRatio +
  71. this.theme.SIZES.LABEL_FONT_PADDING * window.devicePixelRatio * 2 -
  72. this.theme.SIZES.LABEL_FONT_PADDING
  73. );
  74. // Draw top timeline lines
  75. context.fillStyle = this.theme.COLORS.GRID_LINE_COLOR;
  76. context.fillRect(0, 0, physicalViewRect.width, this.theme.SIZES.GRID_LINE_WIDTH / 2);
  77. context.fillRect(
  78. 0,
  79. this.theme.SIZES.TIMELINE_HEIGHT * window.devicePixelRatio,
  80. physicalViewRect.width,
  81. this.theme.SIZES.GRID_LINE_WIDTH / 2
  82. );
  83. const intervals = computeInterval(configViewSpace, logicalSpaceToConfigView);
  84. for (let i = 0; i < intervals.length; i++) {
  85. // Compute the x position of our interval from config space to physical
  86. const physicalIntervalPosition = Math.round(
  87. intervals[i] * configViewToPhysicalSpace[0] + configViewToPhysicalSpace[6]
  88. );
  89. // Format the label text
  90. const labelText = this.formatter(intervals[i]);
  91. context.fillStyle = this.theme.COLORS.LABEL_FONT_COLOR;
  92. // Subtract width of the text and padding so that the text is align to the left of our interval
  93. context.fillText(
  94. labelText,
  95. physicalIntervalPosition -
  96. measureText(labelText, context).width -
  97. this.theme.SIZES.LABEL_FONT_PADDING * window.devicePixelRatio,
  98. this.theme.SIZES.LABEL_FONT_PADDING * window.devicePixelRatio
  99. );
  100. // Draw the vertical grid line
  101. context.strokeStyle = this.theme.COLORS.GRID_LINE_COLOR;
  102. context.strokeRect(
  103. physicalIntervalPosition - this.theme.SIZES.GRID_LINE_WIDTH / 2,
  104. physicalViewRect.y,
  105. this.theme.SIZES.GRID_LINE_WIDTH / 2,
  106. physicalViewRect.height
  107. );
  108. }
  109. }
  110. }
  111. export {GridRenderer};