#StackBounty: #javascript #html #jquery #css #graph Beeswarm plot not collapsing

Bounty: 250

I’m trying to make my own beeswarm plot, which is a plot of points across one dimension where the points move up as needed in order to not overlap with other points.

Mine has labels for each point, and I’m trying to work out the javascript to move them up from the baseline only if they collide with a previous point. However, they’re moving up regardless of if they collide or not.

Desired behavior: In the first example (depending on your screen width) ● Juliet Francis should be down at the baseline instead of way up near the top, because it can exist at the baseline without colliding with any preceding points (ordered from right to left).

I’ve tested the collision function, and that seems to be working fine. The problem seems to be either in swarm() or collidesAll() functions.

I’ve been poking at this for a couple days and haven’t been able to get it working. A second pair of eyes would be appreciated.

$(function() {
  
  let $graphs = $('.graph')
  let $firstSetOfNames = $('.graph:first-child .graph-dd')
  
  const setup = () => {
    let longest = 0
    $firstSetOfNames.each(function() {
      longest = ($(this).width() > longest) ? $(this).width() : longest
    })
    $graphs.css('padding-right', longest + 'px')
  }
  
  const collides = ($e1, $e2) => {
    let e1x1 = $e1.offset().left
    let e1x2 = e1x1.x1 + $e1.outerWidth( true )
    let e2x1 = $e2.offset().left
    let e2x2 = e2x1.x1 + $e2.outerWidth( true )
    let x = ((e1x1 < e2x1) && (e2x1 < e1x2)) || ((e2x1 < e1x1) && (e1x1 < e2x2))
    let y = parseInt($e1.css('--y'), 10) === parseInt($e2.css('--y'), 10)
    return !!(x || y)
  }
  
  const collidesAll = ($people, $person, j) => {
    for (let i = 0; i < j; i++) {
      if (collides($person, $people.eq(i))) {
        return true
      }
    }
    return false
  }
  
  const swarm = () => {
    $graphs.each(function(i) {
      let $graph = $(this)
      let $people = $($graph.find('.graph-dd').get().reverse())
      $people.each(function(j) {
        let $person = $(this)
        let n = 1
        if (0 === j) {
          $person.css('--y', 1)
        } else {
          do {
            $person.css('--y', n++)
          } while (collidesAll($people, $person, j))
          $graph.css('--yMax', n)
        }
      })
    })
  }
  
  setup()
  swarm()
  
})
.graph {
  margin: 2rem;
  padding: calc(calc(var(--yMax, 0) * 1.1em) + 1rem) 1rem 1rem;
  border: 1px solid black;
  overflow: hidden;
}

.graph-dl {
  position: relative;
  display: flex;
  margin: 0;
  justify-content: space-between;
}
.graph-dl::before {
  content: "";
  position: absolute;
  display: block;
  height: 1px;
  top: calc(50% - 0.5px);
  left: 0;
  right: 0;
  background: gray;
  z-index: -1;
}

.graph-dt {
  background: black;
  width: 1px;
  height: 1em;
  border: 0.5em solid white;
  margin: 0 -0.5em;
}
.graph-dt span {
  display: none;
}

.graph-dd {
  position: absolute;
  display: block;
  top: 0.5em;
  margin: 0 0 0 calc(-0.5ex - 1px);
  left: calc(var(--percent) * 1%);
  white-space: nowrap;
  transform: translateY(calc(var(--y, 0) * -1.1em));
  transition: transform 0.3s;
}
.graph-dd::before {
  content: "";
  display: inline-block;
  background: blue;
  width: 1ex;
  height: 1ex;
  vertical-align: baseline;
  border-radius: 100%;
  margin-right: 0.2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

<figure class="graph">
  <dl class="graph-dl">
    <dt class="graph-dt"><span>0</span></dt>
    <dd class="graph-dd" data-value="9" style="--percent: 9; --y: 1;">Zaccaria Osmant</dd>
    <dt class="graph-dt"><span>10</span></dt>
    <dd class="graph-dd" data-value="11" style="--percent: 11; --y: 1;">Nelli Dunge</dd>
    <dd class="graph-dd" data-value="12" style="--percent: 12; --y: 1;">Ethelind Evers</dd>
    <dd class="graph-dd" data-value="16" style="--percent: 16; --y: 1;">Juliet Francis</dd>
    <dt class="graph-dt"><span>20</span></dt>
    <dt class="graph-dt"><span>30</span></dt>
    <dt class="graph-dt"><span>40</span></dt>
    <dd class="graph-dd" data-value="48" style="--percent: 48; --y: 1;">Myles Burdoun</dd>
    <dt class="graph-dt"><span>50</span></dt>
    <dd class="graph-dd" data-value="55" style="--percent: 55; --y: 1;">Gregory Beade</dd>
    <dt class="graph-dt"><span>60</span></dt>
    <dd class="graph-dd" data-value="60" style="--percent: 60; --y: 1;">Trenna Vigne</dd>
    <dd class="graph-dd" data-value="61" style="--percent: 61; --y: 1;">Dulcia Koubu</dd>
    <dt class="graph-dt"><span>70</span></dt>
    <dd class="graph-dd" data-value="70" style="--percent: 70; --y: 1;">Amberly Wrightham</dd>
    <dd class="graph-dd" data-value="73" style="--percent: 73; --y: 1;">Barney Rawstorn</dd>
    <dt class="graph-dt"><span>80</span></dt>
    <dt class="graph-dt"><span>90</span></dt>
    <dd class="graph-dd" data-value="91" style="--percent: 91; --y: 1;">Nealson Helstrip</dd>
    <dd class="graph-dd" data-value="92" style="--percent: 92; --y: 1;">Asa Langwade</dd>
    <dd class="graph-dd" data-value="93" style="--percent: 93; --y: 1;">Malvin Imlaw</dd>
    <dd class="graph-dd" data-value="96" style="--percent: 96; --y: 1;">Joanie Clooney</dd>
    <dt class="graph-dt"><span>100</span></dt>
    <dd class="graph-dd" data-value="100" style="--percent: 100; --y: 1;">Kristo Biskupski</dd>
  </dl>
</figure>
<figure class="graph">
  <dl class="graph-dl">
    <dt class="graph-dt"><span>0</span></dt>
    <dt class="graph-dt"><span>10</span></dt>
    <dt class="graph-dt"><span>20</span></dt>
    <dt class="graph-dt"><span>30</span></dt>
    <dt class="graph-dt"><span>40</span></dt>
    <dd class="graph-dd" data-value="44" style="--percent: 44; --y: 1;">Nelli Dunge</dd>
    <dd class="graph-dd" data-value="48" style="--percent: 48; --y: 1;">Myles Burdoun</dd>
    <dt class="graph-dt"><span>50</span></dt>
    <dd class="graph-dd" data-value="51" style="--percent: 51; --y: 1;">Zaccaria Osmant</dd>
    <dt class="graph-dt"><span>60</span></dt>
    <dd class="graph-dd" data-value="61" style="--percent: 61; --y: 1;">Trenna Vigne</dd>
    <dd class="graph-dd" data-value="61" style="--percent: 61; --y: 1;">Dulcia Koubu</dd>
    <dd class="graph-dd" data-value="65" style="--percent: 65; --y: 1;">Ethelind Evers</dd>
    <dt class="graph-dt"><span>70</span></dt>
    <dd class="graph-dd" data-value="70" style="--percent: 70; --y: 1;">Amberly Wrightham</dd>
    <dd class="graph-dd" data-value="73" style="--percent: 73; --y: 1;">Barney Rawstorn</dd>
    <dd class="graph-dd" data-value="74" style="--percent: 74; --y: 1;">Kristo Biskupski</dd>
    <dd class="graph-dd" data-value="75" style="--percent: 75; --y: 1;">Joanie Clooney</dd>
    <dd class="graph-dd" data-value="77" style="--percent: 77; --y: 1;">Juliet Francis</dd>
    <dd class="graph-dd" data-value="77" style="--percent: 77; --y: 1;">Gregory Beade</dd>
    <dd class="graph-dd" data-value="79" style="--percent: 79; --y: 1;">Malvin Imlaw</dd>
    <dt class="graph-dt"><span>80</span></dt>
    <dd class="graph-dd" data-value="85" style="--percent: 85; --y: 1;">Asa Langwade</dd>
    <dt class="graph-dt"><span>90</span></dt>
    <dd class="graph-dd" data-value="91" style="--percent: 91; --y: 1;">Nealson Helstrip</dd>
    <dt class="graph-dt"><span>100</span></dt>
  </dl>
</figure>

Or view in CodePen with Pug and Sass instead.


Get this bounty!!!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.