Уменьшение количества запусков вспомогательной функции

avatar
Alex Motor
8 августа 2021 в 23:37
60
1
1

Я пытаюсь создать облако слов. Чтобы отобразить текст на экране, я генерирую случайную позицию для каждого слова. Это работает отлично, однако есть много перекрывающихся слов. Чтобы решить эту проблему, я сохраняю положение и размер элементов в массиве, а затем создал вспомогательную функцию, которая проверяет наличие столкновений, создает новую позицию для элемента, если он ее находит, а затем снова вызывает себя, чтобы проверить снова с начала массива. Когда я запускаю свой код, первые 2-3 слова отображаются нормально, но затем я получаю сообщение об ошибке, говорящее о том, что превышен максимальный размер стека вызовов. Я видел, что уже было сообщение об этой такой же проблеме с переполнением стека.

Я увидел, что другой человек использует функцию forEach, и я тоже, поэтому я преобразовал ее в цикл for, как было предложено в ответе, но это ничего не сделало. Я думаю, что проблема сводится к тому, что коллизий так много, но я не уверен, как лучше всего подойти к этой проблеме. Есть ли другой способ генерировать уникальные позиции для элементов, избегая при этом столкновений?

Код:

function calculatePosition(parent, child) {
    return Math.random() * parent - (child / 2)
}

// needed for rendering position of span elements
var ranges = []
var totalWidthOfWords = 0
var totalHeightOfWords = 0

// reposition element if there is a collision
function checkForCollisions(element, height, width, wordCloud, injectedSpan) {
 for(var i = 0; i < ranges.length; i++) {
        let current = ranges[i]
        if(element.left >= current.width[0] && element.left <= current.width[1]) {
          injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, width) + "px";
          checkForCollisions(element, height, width, wordCloud, injectedSpan)
        }
        if(element.top >= current.height[0] && element.top <= current.height[1]) {
      injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, height) + "px";
      checkForCollisions(element, height, width, wordCloud, injectedSpan)
      }
      }
}

// Create content in DOM
const injectedContent = data.map(line => {
const injectedSpan = document.createElement("span")
const injectedWord = document.createElement("p")
const wordCloud = document.querySelector(".word-cloud")

// mod weight value to get more managable inputs
let weightValue = (line.weight * 100).toFixed(2)

// sets values of words and renders them to the screen
injectedWord.innerText = line.word
injectedSpan.appendChild(injectedWord)
wordCloud.appendChild(injectedSpan)

// sets style attribute based on weight value
injectedWord.setAttribute("style", `--i: ${weightValue}`)

// flips words
if(Math.random() > 0.75) {
    injectedWord.style.writingMode = "vertical-rl";
  }


// Entrance animation
let left = innerWidth * Math.random()
let top = innerHeight * Math.random()
if(Math.random() < 0.5) {
  injectedWord.style.left = "-" + left + "px";
  injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
} else {
    injectedWord.style.left = left + "px";
  injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
}
if(Math.random() < 0.5) {
  injectedWord.style.top = "-" + top + "px";
  injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, injectedSpan.clientHeight) + "px";
} else {
    injectedWord.style.top = top + "px";
  injectedSpan.style.top = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
}


// Get position of span and change coordinites if there is a collision
let spanPosition = injectedSpan.getBoundingClientRect()
console.log(spanPosition)

if(spanPosition) {
  checkForCollisions(spanPosition, spanPosition.height, spanPosition.width, wordCloud, injectedSpan)
}



totalWidthOfWords += spanPosition.width
totalHeightOfWords += spanPosition.height

ranges.push({width: [spanPosition.left, spanPosition.right], height: [spanPosition.top, spanPosition.bottom]})
})

Ссылка: https://jsfiddle.net/amotor/mdg7rzL1/4/

Источник
Cobra_8
8 августа 2021 в 23:51
1

каждый раз, когда у вас возникает коллизия, вы обновляете позицию элемента, а затем вызываете checkForCollisions, что, на мой взгляд, хорошо, но цикл for, из которого вы вызываете метод, просто продолжает работать! Попробуйте добавить «перерыв»; после каждого рекурсивного вызова checkForCollisions. Это уже должно уменьшить количество петель.

IllusiveBrian
9 августа 2021 в 00:02
2

checkForCollisions может просто вернуть значение независимо от того, есть ли коллизия, и вызывающая функция может обрабатывать вычисление новой и вызывать ее снова, это позволит избежать проблемы с глубиной стека. Однако ваш код, кажется, имеет коллизию, если элемент находится в той же вертикальной или горизонтальной плоскости, что и другой элемент, но не обязательно в обоих. Я не знаю, так задумано или нет.

Alex Motor
9 августа 2021 в 23:06
0

Спасибо, что указали на это @IllusiveBrian. Это было непреднамеренно. Я обновлю код.

Alex Motor
10 августа 2021 в 02:46
0

Я обновил код и изменил функцию checkForCollisions, чтобы она возвращала true, если высота и ширина находились в пределах диапазона существующего элемента, и возвращала false в противном случае. В функции карты, которая отображает каждый элемент, я установил цикл while, чтобы проверить, есть ли какие-либо перекрывающиеся элементы. Я думал, что это сработает, и на самом деле это уменьшило количество перекрывающихся слов, но все же есть несколько слов, которые перекрываются. Я думал, что это проблема с пространством, но в родительском div еще достаточно места для перемещения слов. Я обновил jsFiddle новым кодом.

Ответы (1)

avatar
Cobra_8
9 августа 2021 в 00:30
1

Предстоит еще много работы, чтобы убедиться, что он работает правильно, особенно чтобы код не вызывал ошибок!

Общая идея состоит в том, чтобы следовать комментарию IllsuiveBrian, чтобы убедиться, что checkForCollision выполняет только работу по проверке наличия коллизии, а другая функция заботится о пересчете позиции, если это необходимо, а затем повторно оценивает потенциальное столкновение.

function checkForCollisions(element, wordCloud, injectedSpan) {
   for(var i = 0; i < ranges.length; i++) {
     let current = ranges[i];
     // return true if there is a collision (you probably have to update the code you are using here to truly avoid collisions!)
     if (collision) { return true; }
   }
    return false; // return false otherwise
}

Наконец, эта часть позаботится о пересчете позиции и перепроверке столкновения:

ranges.forEach(function(injectedSpan) {

    // Get position of span and change coordinites if there is a collision
    let spanPosition = injectedSpan.getBoundingClientRect();
    if (spanPosition) {
        while (checkForCollisions(spanPosition, wordCloud, injectedSpan)) {
            injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, element.width) + "px";
            injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, element.height) + "px";
    }
 }

});

Вот краткая идея о том, как двигаться в этом направлении: https://jsfiddle.net/euvbax1r/4/

Alex Motor
10 августа 2021 в 02:29
0

Спасибо за это. Я обновил свой код и смог отобразить слова на экране, но облако слов по-прежнему отображается с перекрывающимися словами. Моя функция checkForCollisions теперь просто принимает один аргумент, которым является инъекцииSpan, перебирает массив диапазонов и сравнивает ширину и высоту. Если они оба находятся в диапазоне, он возвращает true. Если нет, то он возвращает false. Я также подумал, что могу просто отказаться от функции forEach и просто извлечь цикл while. Я ДУМАЛ, что это запустится, найдет любые коллизии, а затем снова проверит. Я обновил код на jsFiddle, указанном выше.

Cobra_8
12 августа 2021 в 11:39
0

Я рад, что это помогло, по крайней мере, с крахом! К сожалению, ссылка на jsFiddle в вашем сообщении выше не обновляется?

Alex Motor
12 августа 2021 в 22:18
0

Очевидно, когда я сохранил старый файл, он сгенерировал новую ссылку. Извините за это, но я обновил ссылку в своем исходном сообщении