From 5d33e3152db9db18452ae1c8fe759c4ed7fcf630 Mon Sep 17 00:00:00 2001 From: Manik Rana Date: Wed, 29 May 2024 14:41:56 +0530 Subject: [PATCH] feat: add proper zero bucket Signed-off-by: Manik Rana --- .../src/pages/graph/HistogramChart.tsx | 119 ++++++++++++------ 1 file changed, 79 insertions(+), 40 deletions(-) diff --git a/web/ui/react-app/src/pages/graph/HistogramChart.tsx b/web/ui/react-app/src/pages/graph/HistogramChart.tsx index 42c431ab275..12dba86fd30 100644 --- a/web/ui/react-app/src/pages/graph/HistogramChart.tsx +++ b/web/ui/react-app/src/pages/graph/HistogramChart.tsx @@ -12,19 +12,19 @@ type closestToZeroType = { const HistogramChart: FC<{ histogram: Histogram; index: number; scale: ScaleType }> = ({ index, histogram, scale }) => { const { buckets } = histogram; + if (!buckets) { + return
No data
; + } const formatter = Intl.NumberFormat('en', { notation: 'compact' }); - const rangeMax = buckets ? parseFloat(buckets[buckets.length - 1][2]) : 0; - const rangeMin = buckets ? parseFloat(buckets[0][1]) : 0; - - const expBucketWidth = buckets - ? Math.abs( - Math.log(Math.abs(parseFloat(buckets[buckets.length - 1][2]))) - - Math.log(Math.abs(parseFloat(buckets[buckets.length - 1][1]))) - ) - : 0; //bw + const rangeMax = parseFloat(buckets[buckets.length - 1][2]); + const rangeMin = parseFloat(buckets[0][1]); - const countMax = buckets ? buckets.map((b) => parseFloat(b[3])).reduce((a, b) => Math.max(a, b)) : 0; + const expBucketWidth = Math.abs( + Math.log(Math.abs(parseFloat(buckets[buckets.length - 1][2]))) - + Math.log(Math.abs(parseFloat(buckets[buckets.length - 1][1]))) + ); //bw + const countMax = buckets.map((b) => parseFloat(b[3])).reduce((a, b) => Math.max(a, b)); // The count of a histogram bucket is represented by its area rather than its height. This means it considers // both the count and the width (range) of the bucket. For this, we can set the height of the bucket proportional @@ -32,32 +32,49 @@ const HistogramChart: FC<{ histogram: Histogram; index: number; scale: ScaleType // Frequency density histograms are necessary when the bucekts are of unequal width. If the buckets are collected in // intervals of equal width, then there is no difference between frequency and frequency density histograms. - const fds = buckets - ? buckets.map((b) => { - return parseFloat(b[3]) / (parseFloat(b[2]) - parseFloat(b[1])); - }) - : []; + const fds = buckets.map((b) => { + return parseFloat(b[3]) / (parseFloat(b[2]) - parseFloat(b[1])); + }); const fdMax = fds.reduce((a, b) => Math.max(a, b)); - const closestToZero = buckets ? findClosestToZero(buckets.map((b) => parseFloat(b[1]))) : { closest: 0, closestIdx: 0 }; - - const zeroAxisLeft = - scale === 'linear' - ? ((0 - rangeMin) / (rangeMax - rangeMin)) * 100 + '%' - : (closestToZero.closestIdx / (buckets ? buckets.length : 1)) * 100 + '%'; + const closestToZero = findClosestToZero(buckets.map((b) => parseFloat(b[1]))); + const { zeroBucket, zeroBucketIdx } = findZeroBucket(buckets); + console.log('ZERO BUCKET IS', zeroBucket); - const maxPositive = buckets ? parseFloat(buckets[buckets.length - 1][2]) : 0; - const minPositive = buckets ? parseFloat(buckets[closestToZero.closestIdx + 1][1]) : 0; - const maxNegative = buckets ? parseFloat(buckets[closestToZero.closestIdx - 1][2]) : 0; - const minNegative = buckets ? parseFloat(buckets[0][1]) : 0; - const startNegative = buckets ? -Math.log(Math.abs(minNegative)) : 0; //start_neg - const endNegative = buckets ? -Math.log(Math.abs(maxNegative)) : 0; //end_neg - const startPositive = buckets ? Math.log(minPositive) : 0; //start_pos - const endPositive = buckets ? Math.log(maxPositive) : 0; //end_pos + const maxPositive = parseFloat(buckets[buckets.length - 1][2]); + const minPositive = + zeroBucketIdx !== -1 ? parseFloat(buckets[zeroBucketIdx + 1][1]) : parseFloat(buckets[closestToZero.closestIdx + 1][1]); + const maxNegative = + zeroBucketIdx !== -1 ? parseFloat(buckets[zeroBucketIdx - 1][2]) : parseFloat(buckets[closestToZero.closestIdx][2]); + console.log('MAX NEGATIVE', maxNegative, 'MIN POSITIVE', minPositive, 'MAX POSITIVE', maxPositive); + const minNegative = parseFloat(buckets[0][1]); + const startNegative = -Math.log(Math.abs(minNegative)); //start_neg + const endNegative = -Math.log(Math.abs(maxNegative)); //end_neg + const startPositive = Math.log(minPositive); //start_pos + const endPositive = Math.log(maxPositive); //end_pos const widthNegative = endNegative - startNegative; //width_neg const widthPositive = endPositive - startPositive; //width_pos const widthTotal = widthNegative + expBucketWidth + widthPositive; //width_total + const zeroAxisLeft = + scale === 'linear' + ? ((0 - rangeMin) / (rangeMax - rangeMin)) * 100 + '%' + : ((widthNegative + 0.5 * expBucketWidth) / widthTotal) * 100 + '%'; + + function findZeroBucket(buckets: [number, string, string, string][]): { + zeroBucket: [number, string, string, string]; + zeroBucketIdx: number; + } { + for (let i = 0; i < buckets.length; i++) { + const left = parseFloat(buckets[i][1]); + const right = parseFloat(buckets[i][2]); + if (left <= 0 && right >= 0) { + return { zeroBucket: buckets[i], zeroBucketIdx: i }; + } + } + return { zeroBucket: [-1, '', '', ''], zeroBucketIdx: -1 }; + } + function findClosestToZero(numbers: number[]): closestToZeroType { let closest = numbers[0]; let closestIdx = 0; @@ -182,20 +199,42 @@ const RenderHistogramBars: FC = ({ const left = parseFloat(b[1]); const right = parseFloat(b[2]); const count = parseFloat(b[3]); - console.log('new stuff'); - const bucketIdx = `bucket-${index}-${bIdx}-${Math.ceil(parseFloat(b[3]) * 100)}`; - const bucketWidth = - scale === 'linear' ? ((right - left) / (rangeMax - rangeMin)) * 100 + '%' : (bw / widthTotal) * 100 + '%'; - const bucketLeft = - scale === 'linear' - ? ((left - rangeMin) / (rangeMax - rangeMin)) * 100 + '%' - : left < 0 - ? (-(Math.log(Math.abs(left)) + startNegative) / widthTotal) * 100 + '%' // negative buckets boundary - : ((Math.log(left) - startPositive + bw + widthNegative) / widthTotal) * 100 + '%'; // positive buckets boundary - const bucketHeight = scale === 'linear' ? (fds[bIdx] / fdMax) * 100 + '%' : (count / countMax) * 100 + '%'; + + let bucketWidth = ''; + let bucketLeft = ''; + let bucketHeight = ''; + + switch (scale) { + case 'linear': + bucketWidth = ((right - left) / (rangeMax - rangeMin)) * 100 + '%'; + bucketLeft = ((left - rangeMin) / (rangeMax - rangeMin)) * 100 + '%'; + bucketHeight = (fds[bIdx] / fdMax) * 100 + '%'; + break; + case 'exponential': + bucketWidth = (bw / widthTotal) * 100 + '%'; + if (left < 0) { + // negative buckets boundary + bucketLeft = (-(Math.log(Math.abs(left)) + startNegative) / widthTotal) * 100 + '%'; + } else { + // positive buckets boundary + bucketLeft = ((Math.log(left) - startPositive + bw + widthNegative) / widthTotal) * 100 + '%'; + } + bucketHeight = (count / countMax) * 100 + '%'; + break; + default: + console.error('Invalid scale type'); + } + + // zero bucket + if (left < 0 && right > 0) { + bucketLeft = (widthNegative / widthTotal) * 100 + '%'; + } console.log( + 'ID', + bucketIdx, + '\n', 'left', left, '\n',