Javascript вычисляет индексы данных по координатам пикселей

#javascript #algorithm #math

Вопрос:

Возникли проблемы с поиском способа вычисления индексов данных по координатам пикселей. То, что у меня есть, — это a NumberRange , в котором хранятся 0 (мин) и HTMLCanvasElement.width (макс).

 const EPSILON = 0.0001;

function IsRealNumber(number: number) {
    return !isNaN(number) amp;amp; isFinite(number) amp;amp; number !== Number.MAX_VALUE amp;amp; 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 amp;amp; 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 amp;amp; 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
 

Комментарии:

1. Вместо того, чтобы понижать голос, на самом деле дайте обратную связь о том, что не так с сообщением..

2. Почему пример 3 предполагает, что ответ f ( timestamps[5] ), когда другие просто возвращают индекс?

3. У вас также, похоже, есть некоторая избыточная информация. interval может быть рассчитан по длине массива и значениям минимального и максимального масштаба. Если нет, то что происходит, когда они не синхронизированы?

Ответ №1:

Кажется относительно простым получить индекс или фактическое значение массива с помощью такого кода:

 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 того, чтобы быть меньше max и curr_interval падать в этом диапазоне. Но если что-то из этого не получится, оно должно просто вернуться undefined , что кажется разумным.