首页 > 解决方案 > Javascript从像素坐标计算数据索引

问题描述

无法从像素坐标计算数据索引。我所拥有的是NumberRange存储0(最小)和HTMLCanvasElement.width(最大)的。

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;
    }
}

应该发生的事情如下。

// 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

在此处输入图像描述

模拟变化

// 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

在此处输入图像描述

计算数据索引的函数

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;
}

其他示例

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

标签: javascriptalgorithmmath

解决方案


使用如下代码获取索引或实际数组值似乎相对简单:

const getIndex = (xs, min, max) => (x) =>
  Math.round((xs .length - 1) * (x - min) / (max - min))

const getValue = (xs, min, max) => (x) =>
  xs [Math.round((xs .length - 1) * (x - min) / (max - min))]

const tests = [
  {timestamps: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'], 
   scale_min: .5, scale_max: 1.5, curr_interval: 1.5},
  {timestamps: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'], 
   scale_min: .5, scale_max: 1.5, curr_interval: 1.5},
  {timestamps: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'], 
   scale_min: .5, scale_max: 1.5, curr_interval: 1},
  {timestamps: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'], 
   scale_min: .5, scale_max: 1.5, curr_interval: 1},
]

tests .forEach (({timestamps, scale_min, scale_max, curr_interval}) =>
  console .log (`${timestamps .join ('')} - [${scale_min} - ${scale_max}] - ${curr_interval} --> '${
    getValue(timestamps, scale_min, scale_max) (curr_interval)              
  }' (index ${getIndex(timestamps, scale_min, scale_max) (curr_interval)})`)               
)

但我担心这可能会丢失一些东西,因为你提供curr_interval一个字符串并包含完全可导出的interval(我忽略了)。这个解决方案缺少什么?

这确实取决于min小于maxcurr_interval落在该范围内。但如果其中任何一个失败,它应该简单地返回undefined,这似乎是合理的。


推荐阅读