Saturday, 16 October 2021

Javascript calculate data indexes from pixel coordinates

Having trouble coming of with a way to calculate data indexes from pixel coordinates. What I have is a NumberRange that stores the 0 (min) and HTMLCanvasElement.width(max).

const EPSILON = 0.0001;

function IsRealNumber(number: number) {
    return !isNaN(number) && isFinite(number) && number !== Number.MAX_VALUE && number !== Number.MIN_VALUE;
}

/**
 * Defines a number range with numeric min, max
 */
class NumberRange {
    readonly min: number;
    readonly max: number;
    constructor(min?: number, max?: number) {
        this.min = min ?? 0;
        this.max = max ?? 10;
    }
    /**
     * Grows a range by a min and max factor
     * @remarks
     * If the current range is [5,10] and the input range is [0.1, 0.1] the current range will be
     * grown by 10%, so [4.5, 10.5]
     * @param range The grow factor
     * @param isLogarithmic When true, treats the growth factor as logarithmic not linear
     */
    growBy(range: NumberRange, isLogarithmic?: boolean): NumberRange {
        if (isLogarithmic === void 0) { isLogarithmic = false; }
        var diff = this.max - this.min;
        // If min == max, expand around the mid line
        var min = this.min - range.min * (this.isZero() ? this.min : diff);
        var max = this.max + range.max * (this.isZero() ? this.max : diff);
        // Swap if min > max (occurs when mid line is negative)
        if (min > max) {
            var temp = min;
            min = max;
            max = temp;
        }
        // If still zero, then expand around the zero line
        if (Math.abs(max - min) <= EPSILON && Math.abs(min) <= EPSILON) {
            min = -1.0;
            max = 1.0;
        }
        return new NumberRange(min, max);
    }
    /**
     * Returns true if the range min === range max
     */
    isZero(): boolean {
        return this.min === this.max;
    }
}

What should happen is below.

// Simulate current canvas range (0, canvas.width)
const canvasPixelRange = new NumberRange(0, 1920)
// Simulate total data range
const totalDataRange = new NumberRange(0, 100)
// Default visible data range
let visibleDataRange; 

visibleDataRange = totalDataRange.growBy(new NumberRange(0, 0))
console.log(visibleDataRange)
/**
 * NumberRange: {
  "min": 0,
  "max": 100
} 
 */
console.log(PixelCoordinateXToDataIndex(canvasPixelRange, visibleDataRange, 960)) // Should be 50

enter image description here

Simulated change

// Simualte mouse drag x event that will determine growByRange
visibleDataRange = totalDataRange.growBy(new NumberRange(-.15, -.25))
console.log(visibleDataRange)
/**
 * NumberRange: {
  "min": 15,
  "max": 75
} 
 */
console.log(PixelCoordinateXToDataIndex(canvasPixelRange, visibleDataRange, 960)) // Should be 45

enter image description here

The function that calculates the data index

function PixelCoordinateXToDataIndex(canvasRange: NumberRange, dataRange: NumberRange, atCoord: number) {
    // [0, 100], 100
    const dataRangeValue = dataRange.max - dataRange.min;
    // ceil(canvas.width: 1920 / 100), 20
    const dataToPixelInterval = Math.ceil(canvasRange.max / dataRangeValue);
    // (canvas.width: 1920 / 20), 96
    const pixelBaseInterval = canvasRange.max / dataToPixelInterval;
    
    const dataOptimalIntervals = 8;

    // Traverse the canvas pixel width by the pixelBaseInterval (1920 / 96), 20 times
    for (let currentPixelCoord = 0; currentPixelCoord < canvasRange.max; currentPixelCoord += pixelBaseInterval) {
        if (currentPixelCoord !== 0) {
            let previousPixelXCoord = currentPixelCoord - pixelBaseInterval;
            if (atCoord > previousPixelXCoord && atCoord <= currentPixelCoord) {
                let scaledInterval = pixelBaseInterval / dataOptimalIntervals;
                let intervals: number[][] = [[0, scaledInterval]]
                for (let a = 2; a < dataRange.max + 1; a += scaledInterval) {
                    intervals.push([Math.ceil(intervals[intervals.length - 1][1]), Math.ceil(a)])
                }
                return intervals[intervals.length - 5][1]
            }
        }
    }

    return 0;
}

Other examples

visibleDataRange = totalDataRange.growBy(new NumberRange(0, 0))
console.log(PixelCoordinateXToDataIndex(canvasPixelRange, visibleDataRange, 1920)) // Should be 99
console.log(PixelCoordinateXToDataIndex(canvasPixelRange, visibleDataRange, 0)) // Should be 0


from Javascript calculate data indexes from pixel coordinates

No comments:

Post a Comment