Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <svg id='board'>
  <g id='lines'></g>
  <g id='circles'></g>
</svg>
<div id='buttons'>
  <button id='regenerate'>Regenerate Graph</button>
  <button id='bfs'>Start BFS</button>
</div>
              
            
!

CSS

              
                body {
  padding: 0;
  margin: 0;
  border: none;
  background-color: black;
}

circle {
  fill: white;
  cursor: pointer;
}

circle:hover {
  fill:pink;
}

#buttons {
  opacity: 0;
  position:absolute;
  left: 10px;
  bottom: 10px;
}
              
            
!

JS

              
                # VIEW SETUP STUFF
OUTER_MARGIN = 10.0
SVG_ELEMENT = d3.select '#board'
SVG_CIRCLES = SVG_ELEMENT.select('#circles')
SVG_LINES   = SVG_ELEMENT.select('#lines')

windowWidth = window.innerWidth
windowHeight = window.innerHeight

svgWidth = windowWidth - 2 * OUTER_MARGIN
svgHeight = windowHeight - 2 * OUTER_MARGIN

px = (x) -> String(x) + 'px'

SVG_ELEMENT.attr  'width',       px svgWidth
           .attr  'height',      px svgHeight
           .style 'margin-left', px OUTER_MARGIN
           .style 'margin-top',  px OUTER_MARGIN
      
# GOODIES
class Queue
  constructor: () ->
    @arr = []
    @p1 = 0
    @p2 = 0
    return
  
  push: (a) -> 
    @p2 += 1
    @arr.push(a)
    return
    
  squish: () ->
    @arr.splice(0, @p1)
    @p2 = @p2 - @p1
    @p1 = 0
    return
  
  front: () -> @arr[@p1]
  
  pop: () ->
    if @isEmpty() 
      return
    x = @arr[@p1]
    @arr[@p1] = 
    @p1 += 1
    if @isEmpty()
      @arr = []
      @p1 = 0
      @p2 = 0
    else if (@p2 - @p1) < @p2 / 2
      @squish()
    x
  
  isEmpty: () -> @p1 == @p2
    
diag  = (w, h) -> Math.sqrt(w * w + h * h)
ranbt = (a, b) -> a + Math.random() * (b - a)
ranIntbt = (a, b) -> parseInt(ranbt a, b)

colorOpen = (color, factor) ->
  rrr = 255 * factor
  res = 'black'
  if color == 'red'
    res = "rgb(255, #{rrr}, #{rrr})"
  else if color == 'green'
    res = "rgb(#{rrr}, 255, #{rrr})"
  else if color == 'blue' || color == 'green'
    res = "rgb(#{rrr}, #{rrr}, 255)"
  res

class Point
  constructor: (@x, @y) ->
    return
  
  distanceTo: (a) -> Math.sqrt((@x - a.x) * (@x - a.x) + (@y - a.y) * (@y - a.y))

  randomPointInAnnulus: (r1, r2) ->
    r = ranbt(r1, r2)
    a = ranbt(0, 2 * Math.PI)
    new Point(@x + r * Math.cos(a), @y + r * Math.sin(a))

randomPoint = (x1, y1, x2, y2) -> new Point(ranbt(x1, x2), ranbt(y1, y2))

# LOGIC STUFF
class PoissonDisk
  constructor: (@boardWidth, 
                @boardHeight, 
                @minDistance, 
                @candidatesSize, 
                @newPointCallback, 
                @newLineCallback, 
                @doneCallback) ->
    @cellSize = @minDistance / Math.SQRT2
    @cellsWide = Math.ceil @boardWidth / @cellSize
    @cellsHigh = Math.ceil @boardHeight / @cellSize
    @allPoints = []
    @grid = []
    for i in [0..@cellsHigh - 1]
      tarr = []
      for j in [0..@cellsWide - 1]
        tarr.push -1
      @grid.push tarr
    return
    
  sample: () ->
    fp = new Point(ranbt(50, @boardWidth - 50), ranbt(50, @boardHeight - 50))
    
    @grid[parseInt(fp.y / @cellSize)][parseInt(fp.x / @cellSize)] = 0
    
    @allPoints.push fp
    @newPointCallback fp.x, fp.y, 0
    q = new Queue()
    q.push(0)
    
    _candidatesSize  = @candidatesSize
    _newPointCallback = @newPointCallback
    _newLineCallback = @newLineCallback
    _doneCallback = @doneCallback
    _grid = @grid
    _cellSize = @cellSize
    _cellsWide = @cellsWide
    _cellsHigh = @cellsHigh
    _allPoints = @allPoints
    _minDistance = @minDistance
    _boardWidth = @boardWidth
    _boardHeight = @boardHeight
    
    testPoint = (point) ->
      row = parseInt point.y / _cellSize
      col = parseInt point.x / _cellSize
      res =
        goodClose: []
        goodCloseId: []
        isBad: false
      if point.x < 50 or point.x > _boardWidth - 50 or 
         point.y < 50 or point.y > _boardHeight - 50
        res.isBad = true
        return res
      for i in [-5..5]
        for j in [-5..5]
          prow = row + i
          pcol = col + j
          if prow < 0 or pcol < 0 or prow >= _cellsHigh or pcol >= _cellsWide
            continue
          if _grid[prow][pcol] > -1
            ppid = _grid[prow][pcol]
            pp = _allPoints[ppid]
            if point.distanceTo(pp) < _minDistance
              res.isBad = true
              return res
            if point.distanceTo(pp) < 2 * _minDistance
              res.goodClose.push pp
              res.goodCloseId.push ppid
      return res
    
    doWork = () ->
      found = false
      ppid = q.front()
      pp = _allPoints[ppid]
      for i in [1.._candidatesSize]
        gp = pp.randomPointInAnnulus(_minDistance, 2 * _minDistance)
        ttt = testPoint(gp)
        if !ttt.isBad
          row = parseInt gp.y / _cellSize
          col = parseInt gp.x / _cellSize
          _grid[row][col] = _allPoints.length
          gpid = _allPoints.length
          q.push gpid
          _allPoints.push gp
          _newPointCallback gp.x, gp.y, gpid
          found = true
          for ppwid in ttt.goodCloseId
            ppw = _allPoints[ppwid]
            _newLineCallback ppw.x, ppw.y, gp.x, gp.y, ppwid, gpid
      if found == false
        q.pop()
      if !q.isEmpty()
        setTimeout doWork, 0
      else
        setTimeout _doneCallback, 0
    
    setTimeout doWork, 0
    return

# CALL STUFF

graph = []

myNewPointCallback = (x, y, _id) ->
  domref = SVG_CIRCLES.append 'circle'
  domref.attr   'cx', x
        .attr   'cy', y
        .attr   'r',  diag(svgWidth, svgHeight) / 180 * 5
        .style  'opacity', 0.0
        .transition()
        .duration 250
        .style  'opacity', 1.0
        .attr   'r',  diag(svgWidth, svgHeight) / 180
  node =
    ref   : domref
    id    : _id
    edges : []
  graph.push node
  alll = () ->
    setTimeout () ->
      doBFS(_id)
      return
    , 0
    return
  domref.on('click', alll)
  return
        
myNewLineCallback = (x1, y1, x2, y2, id1, id2) ->
  edge = SVG_LINES.append 'line'
  edge.attr   'x1', x1
      .attr   'y1', y1
      .attr   'x2', x1
      .attr   'y2', y1
      .attr   'stroke-width', 1
      .attr   'stroke', 'white'
      .transition()
      .duration 500
      .attr   'x2', x2
      .attr   'y2', y2
  n1ton2 =
    ref : edge
    end : id2
  n2ton1 =
    ref : edge
    end : id1
  graph[id1].edges.push n1ton2
  graph[id2].edges.push n2ton1
            
active = true
myDoneCallback = () ->
  active = true
  d3.select('#regenerate').attr('disabled', null)
  d3.select('#bfs').attr('disabled', null)

doWork = () -> 
  graph = []
  if !active
    return
  active = false
  d3.select('#regenerate').attr('disabled', 'disabled')
  d3.select('#bfs').attr('disabled', 'disabled')
  SVG_CIRCLES.selectAll('*').remove()
  SVG_LINES  .selectAll('*').remove()
  a = new PoissonDisk svgWidth,
                      svgHeight,
                      # minDistance, candidateSize
                      diag(svgWidth, svgHeight) / 30, 30,
                      myNewPointCallback, 
                      myNewLineCallback, 
                      myDoneCallback
  a.sample()

d3.select('#regenerate').on('click', doWork)
doWork()













cl = 0
cls = ['red', 'blue', 'green']

doBFS = (startNodeId) ->
  
  d3.select('#regenerate').attr('disabled', 'disabled')
  d3.select('#bfs').attr('disabled', 'disabled')
  
  cl = (cl + 1) % 3
  startNode   = graph[startNodeId]
  d3.selectAll('line').transition().style('stroke-width', 1)
                                   .style('stroke', 'lightgrey')
  
  visited = []
  deep    = []
  for x in graph
    visited.push false
    deep.push -1
  
  visited[startNodeId] = true
  deep[startNodeId] = 0
  
  q = new Queue()
  q.push(startNodeId)
  startNode.ref.style('fill', colorOpen(cls[cl], 0))
  startNode.ref.attr('r', diag(svgWidth, svgHeight) / 180 * 3)
  
  bfsssDone = () ->
    d3.select('#regenerate').attr('disabled', null)
    d3.select('#bfs').attr('disabled', null)
    return
  
  bfsss = () ->
    
    if q.isEmpty()
      setTimeout bfsssDone, 0
      return
    
    xId = q.pop()
    xNode = graph[xId]
    
    for edge in xNode.edges
      yId = edge.end
      yNode = graph[yId]
      if !visited[yId]
        q.push yId
        visited[yId] = true
        deep[yId] = deep[xId] + 1
        aug = Math.max(3 - deep[yId] / 5, 1)
        cll = Math.min((3 - aug) / 3, 1)
        fill = colorOpen(cls[cl], cll)
        console.log fill
        yNode.ref.transition().style('fill', fill)
                              .attr('r', diag(svgWidth, svgHeight) / 180 * aug)
        edge.ref.transition().style('stroke-width', aug * diag(svgWidth, svgHeight) / 500)
                             .style('stroke', fill)
    setTimeout bfsss, 0
  
  setTimeout bfsss, 0
  
  return

d3.select('#bfs').on('click', () ->
  startNodeId = ranIntbt(0, graph.length - 1)
  doBFS(startNodeId)
)
              
            
!
999px

Console