utils.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {ECharts} from 'echarts';
  2. import {Query} from 'history';
  3. import {HistogramData} from 'app/utils/performance/histogram/types';
  4. import {getBucketWidth} from 'app/utils/performance/histogram/utils';
  5. import {Point, Rectangle} from './types';
  6. export function generateVitalsRoute({orgSlug}: {orgSlug: String}): string {
  7. return `/organizations/${orgSlug}/performance/summary/vitals/`;
  8. }
  9. export function vitalsRouteWithQuery({
  10. orgSlug,
  11. transaction,
  12. projectID,
  13. query,
  14. }: {
  15. orgSlug: string;
  16. transaction: string;
  17. query: Query;
  18. projectID?: string | string[];
  19. }) {
  20. const pathname = generateVitalsRoute({
  21. orgSlug,
  22. });
  23. return {
  24. pathname,
  25. query: {
  26. transaction,
  27. project: projectID,
  28. environment: query.environment,
  29. statsPeriod: query.statsPeriod,
  30. start: query.start,
  31. end: query.end,
  32. query: query.query,
  33. },
  34. };
  35. }
  36. /**
  37. * Given a value on the x-axis, return the index of the nearest bucket or null
  38. * if it cannot be found.
  39. *
  40. * A bucket contains a range of values, and nearest is defined as the bucket the
  41. * value will fall in.
  42. */
  43. export function findNearestBucketIndex(
  44. chartData: HistogramData,
  45. xAxis: number
  46. ): number | null {
  47. const width = getBucketWidth(chartData);
  48. // it's possible that the data is not available yet or the x axis is out of range
  49. if (!chartData.length || xAxis >= chartData[chartData.length - 1].bin + width) {
  50. return null;
  51. } else if (xAxis < chartData[0].bin) {
  52. return -1;
  53. }
  54. return Math.floor((xAxis - chartData[0].bin) / width);
  55. }
  56. /**
  57. * To compute pixel coordinates, we need at least 2 unique points on the chart.
  58. * The two points must have different x axis and y axis values for it to work.
  59. *
  60. * If all bars have the same y value, pick the most naive reference rect. This
  61. * may result in floating point errors, but should be okay for our purposes.
  62. */
  63. export function getRefRect(chartData: HistogramData): Rectangle | null {
  64. // not enough points to construct 2 reference points
  65. if (chartData.length < 2) {
  66. return null;
  67. }
  68. for (let i = 0; i < chartData.length; i++) {
  69. const data1 = chartData[i];
  70. for (let j = i + 1; j < chartData.length; j++) {
  71. const data2 = chartData[j];
  72. if (data1.bin !== data2.bin && data1.count !== data2.count) {
  73. return {
  74. point1: {x: i, y: Math.min(data1.count, data2.count)},
  75. point2: {x: j, y: Math.max(data1.count, data2.count)},
  76. };
  77. }
  78. }
  79. }
  80. // all data points have the same count, just pick any 2 histogram bins
  81. // and use 0 and 1 as the count as we can rely on them being on the graph
  82. return {
  83. point1: {x: 0, y: 0},
  84. point2: {x: 1, y: 1},
  85. };
  86. }
  87. /**
  88. * Given an ECharts instance and a rectangle defined in terms of the x and y axis,
  89. * compute the corresponding pixel coordinates. If it cannot be done, return null.
  90. */
  91. export function asPixelRect(chartRef: ECharts, dataRect: Rectangle): Rectangle | null {
  92. const point1 = chartRef.convertToPixel({xAxisIndex: 0, yAxisIndex: 0}, [
  93. dataRect.point1.x,
  94. dataRect.point1.y,
  95. ]);
  96. if (isNaN(point1?.[0]) || isNaN(point1?.[1])) {
  97. return null;
  98. }
  99. const point2 = chartRef.convertToPixel({xAxisIndex: 0, yAxisIndex: 0}, [
  100. dataRect.point2.x,
  101. dataRect.point2.y,
  102. ]);
  103. if (isNaN(point2?.[0]) || isNaN(point2?.[1])) {
  104. return null;
  105. }
  106. return {
  107. point1: {x: point1[0], y: point1[1]},
  108. point2: {x: point2[0], y: point2[1]},
  109. };
  110. }
  111. /**
  112. * Given a point on a source rectangle, map it to the corresponding point on the
  113. * destination rectangle. Assumes that the two rectangles are related by a simple
  114. * transformation containing only translations and scaling.
  115. */
  116. export function mapPoint(
  117. point: Point,
  118. srcRect: Rectangle,
  119. destRect: Rectangle
  120. ): Point | null {
  121. if (
  122. srcRect.point1.x === srcRect.point2.x ||
  123. srcRect.point1.y === srcRect.point2.y ||
  124. destRect.point1.x === destRect.point2.x ||
  125. destRect.point1.y === destRect.point2.y
  126. ) {
  127. return null;
  128. }
  129. const xPercentage =
  130. (point.x - srcRect.point1.x) / (srcRect.point2.x - srcRect.point1.x);
  131. const yPercentage =
  132. (point.y - srcRect.point1.y) / (srcRect.point2.y - srcRect.point1.y);
  133. return {
  134. x: destRect.point1.x + (destRect.point2.x - destRect.point1.x) * xPercentage,
  135. y: destRect.point1.y + (destRect.point2.y - destRect.point1.y) * yPercentage,
  136. };
  137. }