Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ 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

Auto Save

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#root
    
#panel
  #control
    span #DataNodes
    input#seed-number(type='number' value='50' min='10' max='400' step='10')
    button#reset Seed Data
  #info
    #info-id
      span= 'ID'
      span#id -
    #info-predecessors
      span= 'Predecessors'
      span#predecessors -
    #info-progeny
      span= 'Progeny'
      span#progeny -
  #legend
    span Min
    span Max
#hints
  ul
    li
      span#k187 +
      span +1
    li
      span#k189 -
      span -1
    li
      span#kshift SHIFT
      span ×10
      
#loader
  .loader
  .progress
    span#progress 0
    span %
  .message
    span#message Seeding...
              
            
!

CSS

              
                $white: darken(white, 5%);
$black: lighten(black, 10%);
$theme: #597f92;

* {
  box-sizing: border-box;
  &:focus,
  &:active {
    outline: none;
  }
}

body {
  background: linear-gradient(
    to top,
    #2C5364,
    #203A43,
    #0F2027
  );
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: center;
  height: auto;
  width: 100vw;
  padding: 2em 0;
  overflow-x: hidden;
  padding-bottom: 10em;
  font-family: monospace;
}

svg {
  margin-top: 10em;
  overflow: visible;
}

.node-group {
  &:hover {
    .node {
      fill: $theme;
    }
    .text {
      fill: white;
    }
  }
}

.node {
  fill: $white;
  stroke: darken(white, 20%);
  stroke-width: 2px;
  cursor: pointer;
  transition: 150ms ease-out;
}

.text {
  font-size: 16px;
  font-family: monospace;
  fill: $theme;
  text-anchor: middle;
  dominant-baseline: middle;
  pointer-events: none;
  transition: 150ms ease-out;
}

.link {
  fill: none;
  stroke-width: 2px;
  stroke: rgba($white, 0.7);
}

#panel {
  position: fixed;
  top: 1rem;
  left: 1rem;
  background-color: rgba(lighten(black, 20%),0.9);
  font-family: monospace;
  padding: 1em;
}

#control {
  width: 100%;
  padding: 0.5em;
  display: flex;
  flex-direction: row;
  align-items: center;
  color: $white;
  & > * {
    display: inline-block;
  }
  input {
    font-family: monospace;
    padding: 0.5em 1em;
    outline: none;
    border: 1px solid lighten(black, 20%);
    background-color: rgba(white, 0.1);
    color: $white;
    margin: 0 0.5em;
    width: 8em;
    min-width: none;
  }
  button {
    font-family: monospace;
    cursor: pointer;
    color: $white;
    background: transparent;
    border: 1px solid $white;
    padding: 0.5em 1em;
    &:hover {
      background-color: $white;
      color: black;
    }
    &:active,
    &:focus {
      outline: none;
    }
  }
}

#info {
  font-family: monospace;
  padding: 0.5em;
  color: darken($white, 20%);
  display: flex;
  flex-direction: column;
  margin: 0;
  font-size: 1.2em;
  & > * {
    padding: 0.2em 0;
    width: 100%;
    display: flex;
    & > * {
      padding: 0 0.5em;
      &:first-child {
        flex: 0 0 55%;
        text-align: right;
        font-weight: bold;
      }
      &:last-child {
        flex: 0 0 45%;
      }
    }
  }
}

$f: 256 / 8;
#legend {
  margin: 0.5em 0;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  padding: 0 0.5em;
  color: lighten(black, 20%);
  background: linear-gradient(
    to right,
    hsl(0,80%,70%),
    hsl($f,80%,70%),
    hsl($f*2,80%,70%),
    hsl($f*3,80%,70%),
    hsl($f*4,80%,70%),
    hsl($f*5,80%,70%),
    hsl($f*6,80%,70%),
    hsl($f*7,80%,70%),
    hsl(256,80%,70%),
  );
}

#hints {
  position: fixed;
  color: darken(white, 40%);
  font-family: monospace;
  top: 16em;
  left: 0;
  ul {
    list-style: none;
    padding: 0 1rem;
    li {
      padding: 0 0 .5em;
      display: flex;
      flex-direction: row;
      width: 8em;
      span {
        flex: 0 0 50%;
        padding: 0 0.5em;
        &:first-child {
          background-color: darken(white, 40%);
          text-align: center;
          color: lighten(black, 10%);
        }
        &:last-child {
          padding: 0 1em;
        }
      }
    }
  }
}

#loader {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: rgba(lighten(black, 20%), 0.9);
  display: none;
  justify-content: center;
  align-items: center;
  color: $white;
  .progress {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 18px;
  }
  .message {
    position: absolute;
    top: calc(50% + 6em);
    left: 50%;
    transform: translate(-40%, -50%);
    font-size: 14px;
  }
}

.loader,
.loader:after {
  border-radius: 50%;
  width: 10em;
  height: 10em;
}
.loader {
  margin: 60px auto;
  font-size: 10px;
  position: relative;
  text-indent: -9999em;
  border-top: 1.1em solid rgba(255, 255, 255, 0.2);
  border-right: 1.1em solid rgba(255, 255, 255, 0.2);
  border-bottom: 1.1em solid rgba(255, 255, 255, 0.2);
  border-left: 1.1em solid #ffffff;
  -webkit-transform: translateZ(0);
  -ms-transform: translateZ(0);
  transform: translateZ(0);
  -webkit-animation: load8 1.1s infinite linear;
  animation: load8 1.1s infinite linear;
}
@-webkit-keyframes load8 {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
@keyframes load8 {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
              
            
!

JS

              
                /* 
  The seed() function creates a number
  of DataNode instances that simulate
  an SQL data structure. The DataNode
  class also provides methods to
  determine each node's number of
  direct predecessors and entire progeny.
*/

class DataNode {
  constructor(args){
    this.id = DataNode.count();
    this.parentId = args.parentId;
    DataNode.instances.push(this);
  }
  get children(){
    return DataNode.instances.filter(node => node.parentId === this.id);
  }
  get siblings(){
    return DataNode.instances.filter(node => node.parentId === this.parentId);
  }
  get parent(){
    return DataNode.instances.find(node => node.id === this.parentId);
  }
  // count predecessors by recursively backtracking to the root ancestor
  get predecessors(){
    function countAncestors(node, count){
      return node.parent ? countAncestors(node.parent, count+1) : count;
    }
    return countAncestors(this, 0);
  }
  // recursively find the number of children to get entire progeny
  get progeny(){
    function countChildren(node){
      if (node.children.length < 0){
        return 0;
      } else {
        return node.children.reduce((a,b) => {
          return a + countChildren(b);
        }, node.children.length);
      }
    }
    return countChildren(this);
  }

}
DataNode.maxChildren = 4;
DataNode.maxGenerationSize = 8;
DataNode.reset = function(){
  DataNode.instances = [];
}
DataNode.count = function() { return DataNode.instances.length; }
DataNode.find = function(id) {
  return DataNode.instances.find(node => id === node.id);
}
DataNode.format = function() {
  const output = { id: DataNode.instances[0].id }
  fill(output);
  return output;
  function fill(parent){
    parent.children = DataNode.find(parent.id).children.map(node => ({ id: node.id }))
    parent.children.forEach(child => fill(child));
  }
}

const state = {
  loading: false
}

function seed(n){
  // reset DataNodes
  DataNode.reset();
  DataNode.maxPredecessors = 0;
  // get DOM elements
  const progress = document.getElementById('progress');
  // create root node
  new DataNode({id: 0, parentId: null});
  // instantiate working variables
  const whitelist = [0];
  const generations = [1];
  const cd = 90,
        roundTime = 10;
  var progressTimer = cd,
      i = 1;
  const interval = setInterval(seedRound, roundTime);
  // seed round interval function
  function seedRound(){
    generateSeed(i);
    // update progress;
    if (progressTimer <= 0) {
      progressTimer = cd;
      tickProgress(i,n);
    }
    // increment iterator and timer
    i++;
    progressTimer -= roundTime;
    if (i > n) {
      clearInterval(interval);
      generateComplete();
    }
  }
  // each round of seeding
  function generateSeed(i){
    // choose parent from all instances filtered by blacklist
    const parentId = chooseParent();
    const newNode = new DataNode({ parentId });
    // add new node to whitelist for faster seeding computation
    whitelist.push(newNode.id);
    // update maxPredecessors in class object
    if (newNode.predecessors > DataNode.maxPredecessors) {
      DataNode.maxPredecessors = newNode.predecessors;
    }
  }
  // choose random parent with requirements:
  // 1) next generation after parent cannot exceed maxGenerationSize
  // 2) parent cannot have more than maxChildren
  // bad parents are added to blacklist during seed
  function chooseParent(){
    const rand = whitelist.length > 1 ? Math.floor(Math.random()*whitelist.length) : 0;
    const parentId = whitelist[rand];
    const parent = DataNode.find(parentId);
    const genLvl = parent.predecessors + 1;
    if (
      generations[genLvl] >= DataNode.maxGenerationSize
      || parent.children.length >= DataNode.maxChildren
    ) {
      whitelist.splice(rand, 1);
      return chooseParent();
    } else {
      generations[genLvl] = generations[genLvl] ? generations[genLvl] + 1 : 1;
      return parentId;
    }
  }
  function tickProgress(i,m){
    progress.innerHTML = Math.round((i/m)*100);
  }
}

function renderGraphic(data){
  const width = window.innerWidth*0.8;
  const height = DataNode.maxPredecessors * 120;
  const nodeSize = 24;
  
  const root = d3.hierarchy(data);
  const treeLayout = d3.tree().size([width, height]);
  treeLayout(root);
  
  d3.selectAll('svg > *').remove();
  const svg = d3.select('#root')
    .attr('width', width)
    .attr('height', height)
  
  const links = svg.append('g')
    .attr('id', 'links')
    .selectAll('line')
    .data(root.links())
    .enter()
  
  links.append('path')
    .classed('link', true)
    .attr('d', d => makeBezierPath(d));
  
  const nodes = svg.append('g')
    .attr('id', 'nodes')
    .selectAll('.node-group')
    .data(root.descendants())
    .enter()
    .append('g')
    .classed('node-group', true);
  nodes.append('circle')
    .classed('node', true)
    .attr('cx', d => d.x)
    .attr('cy', d => d.y)
    .attr('r', nodeSize)
    .on('mouseover click', function(d) {
      console.log('mouse/click')
      // get DataNode
      const node = DataNode.find(d.data.id);
      growNode(d3.select(this));
      // id
      document.getElementById('id').innerHTML = d.data.id;
      // predecessors
      const maxPred = DataNode.maxPredecessors;
      const currentPred = node.predecessors;
      const predecessors = document.getElementById('predecessors');
      predecessors.innerHTML = currentPred;
      predecessors.style.color = percentColor(currentPred, maxPred);
      // progeny
      const currentProg = node.progeny;
      const maxProg = DataNode.find(0).progeny;
      const progeny = document.getElementById('progeny');
      progeny.innerHTML = currentProg;
      progeny.style.color = percentColor(currentProg, maxProg);
    })
    .on('mouseleave blur', function(d) {
      console.log('leave/blur')
      const node = d3.select(this);
      if (node.attr('r') !== nodeSize) revertNode(node);
    })
  nodes.append('text')
    .classed('text', true)
    .attr('x', d => d.x)
    .attr('y', d => d.y)
    .text(d => d.data.id === 0 ? 'root' : d.data.id);
  
  //render helpers
  function growNode(el){
    el.transition()
      .attr('r', nodeSize*1.2)
      .duration(100)
      .delay(0)
      .ease(d3.easeBounce);
  }
  function revertNode(el){
    el.attr('r', nodeSize)
  }
}

function percentColor(val, max){
  return `hsl(${256*(val/max)},80%,70%)`;
}

function makeBezierPath(d){
  const dy = (d.target.y - d.source.y) * 0.8;
  return `M ${d.source.x} ${d.source.y} ` + 
         `C ${d.source.x} ${d.source.y+dy}, ` +
         `${d.target.x} ${d.target.y-dy}, ` +
         `${d.target.x} ${d.target.y}`
}

function generate(){
  document.getElementById('id').innerHTML = '-';
  document.getElementById('predecessors').innerHTML = '-';
  document.getElementById('progeny').innerHTML = '-';
  loading(true);
  const input = document.getElementById('seed-number');
  const value = input.value < 10 ? 10 : input.value > 400 ? 400 : input.value;
  input.value = value;
  seed(value);
}
function generateComplete(){
  loading(false);
  renderGraphic(DataNode.format());
}

function loading(status){
  state.loading = status;
  document.getElementById('loader').style.display = status ? 'flex' : 'none';
}

document.getElementById('reset').onclick = generate;
window.addEventListener('keydown', keydownHandler);
window.addEventListener('keyup', keyupHandler)

function keydownHandler(e){
  if (!state.loading){
    const input = document.getElementById('seed-number');
    if (e.shiftKey) document.getElementById('kshift').setAttribute('style', 'background-color:#f0f0f0;');
    switch(e.keyCode){
      case 13:
        state.keypressEnter = true;
        Object.assign(document.getElementById('reset').style, {
          color: 'black',
          backgroundColor: 'white',
        });
        break;
      case 187:
      case 189:
        document.getElementById('k' + e.keyCode).setAttribute('style', 'background-color:#f0f0f0;');
        var x = e.shiftKey ? -10 : -1;
        input.value = bound(parseInt(input.value, 10) + (e.keyCode-188)*x);
      default:
    }
    function bound(n){
      return n < 10 ? 10 : n > 400 ? 400 : n;
    }
  }
}
function keyupHandler(e){
  if (!e.shiftKey) document.getElementById('kshift').setAttribute('style', '');
  switch(e.keyCode){
    case 13:
      Object.assign(document.getElementById('reset').style, {
        color: 'white',
        backgroundColor: 'transparent',
      });
      if (state.keypressEnter) {
        state.keypressEnter = false;
        generate();
      }
      break;
    case 187:
    case 189:
      document.getElementById('k' + e.keyCode).setAttribute('style', '');
    default:
  }
}

generate();

              
            
!
999px

Console