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 is required to process package imports. If you need a different preprocessor remove all packages first.

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

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

              
                <!-- markup structure
.app, wrapping container
    header, introducing the visualization
    .input, showing the array behind the visualization
    .visualization, showing one circle for each data point
    .controls, allowing to add or remove data points in the array
    .selection, highlighting the selections after each operation
-->
<div class="app">
    <header>
        <h1>D3 Data Binding</h1>
    </header>

    <div class="input">
        <p>
            <strong>Input array:</strong>
            <!-- include here the array describing the data points -->
            <span></span>
        </p>
    </div>

    <!-- include here one sphere for each data point -->
    <main class="visualization"></main>

    <div class="controls">
        <button class="controls--add">Add data point</button>
        <button class="controls--remove">Remove data point</button>
    </div>

    <!-- include in each paragraph the data points described in the enter/update/exit selection -->
    <div class="selection">
        <div class="selection--enter">
            <h3>Enter Selection</h3>
            <p></p>
        </div>
        <div class="selection--update">
            <h3>Update Selection</h3>
            <p></p>

        </div>
        <div class="selection--exit">
            <h3>Exit Selection</h3>
            <p></p>
        </div>
    </div>
</div>
              
            
!

CSS

              
                @import url("https://fonts.googleapis.com/css?family=Lato:400,900&display=swap");

* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}
body {
  min-height: 100vh;
  background: repeating-linear-gradient(
      -45deg,
      hsl(0, 0%, 94%) 0px,
      hsl(0, 0%, 94%) 5px,
      transparent 0px,
      transparent 40px
    ),
    repeating-linear-gradient(
      -45deg,
      hsl(0, 0%, 94%) 0px,
      hsl(0, 0%, 94%) 10px,
      transparent 0px,
      transparent 100px
    ),
    hsl(0, 0%, 97%);
  font-family: "Lato", sans-serif;
}

.app {
  background: #fff;
  padding: 1rem 2rem;
  max-width: 900px;
  margin: 2rem auto;
  min-height: 80vh;
  box-shadow: 0 1px 10px hsla(0, 0%, 0%, 0.1);
  /* display the containers in a column */
  display: flex;
  flex-direction: column;
}
/* separate the direct children vertically */
.app > * {
  margin: 1rem 0;
}
/* stretch the visualization to cover the available space, if any */
.visualization {
  flex-grow: 1;
  /* display the circle in a wrapping row */
  display: flex;
  flex-wrap: wrap;
}
/* style the div included for the data points as circles */
.visualization div {
  width: 48px;
  height: 48px;
  margin: 0.5rem;
  line-height: 48px;
  border-radius: 50%;
  text-align: center;
  font-weight: 900;
}
/* center the controls */
.controls {
  display: flex;
  justify-content: center;
}
.controls button {
  margin: 0 1rem;
  padding: 0.5rem 1rem;
  background: hsl(0, 0%, 20%);
  color: hsl(0, 0%, 94%);
  border: none;
  border-radius: 5px;
  box-shadow: 0 1px 5px hsla(0, 0%, 0%, 0.1);
  font-family: inherit;
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.1rem;
}
.controls button:active {
  transform: translateY(2px);
}

/* display the selections in a wrapping row */
.selection {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-around;
}
.selection div {
  margin: 1rem;
  border-radius: 20px;
  padding: 1rem;
}

/* style the classes describing the different selection with a different background and border-style */
.selection--update {
  background: hsl(240, 75%, 90%);
  border: 2px solid currentColor;
}
.selection--enter {
  background: hsl(120, 75%, 90%);
  border: 2px dashed currentColor;
}

.selection--exit {
  background: hsl(0, 75%, 90%);
  border: 2px dotted currentColor;
}

              
            
!

JS

              
                // utility functions to generate a random data point
const randomInt = (max = 10) => Math.floor(Math.random() * max);
const randomLetter = () => String.fromCharCode(Math.floor(Math.random() * ('Z'.charCodeAt() - 'A'.charCodeAt()) + 'A'.charCodeAt()));

// specify a unique identifier to differentiate the data points
const randomDataPoint = () => ({
  value: `${randomInt()}${randomLetter()}`,
  id: Math.random(),
});

const dataPoints = 10;
// array describing the data
let data = Array(dataPoints).fill('').map(() => randomDataPoint());

// function visualizing the data points
function visualizeData(inputData) {
  const visualization = d3
    .select('.visualization');

  // update selection, existing nodes
  const updateSelection = visualization
    .selectAll('div')
    .data(inputData, d => d.id); // object constancy, bind each element according to the id of the connected data point

  // enter selection, to be added
  const enterSelection = updateSelection
    .enter();

  // exit selection, to be removed
  const exitSelection = updateSelection
    .exit();

  // style the update selection with the matching class
  updateSelection
    .attr('class', 'selection--update');

  // add a div for each new data point
  enterSelection
    .append('div')
    .attr('class', 'selection--enter')
    .text(d => d.value)
    // transition the div from 0 to 1
    .style('transform', 'scale(0)')
    .transition()
    .delay((d, i) => 200 + 50 * i)
    .duration(500)
    .style('transform', 'scale(1)');

  // remove the div for each no-longer-existing data point
  exitSelection
    .attr('class', 'selection--exit')
    // remove after the transition
    // ! if two elements are removed in succession and before the transition is complete the exit selection will consider both
    .transition()
    .delay(200)
    .duration(1000)
    .style('transform', 'scale(0)')
    .remove();


  // complete the visualization updating the UI of the application
  // include the data points in the prescribed span
  d3
    .select('.input p span')
    .html(inputData.map(({ value }) => value));

  // add the selections to their respective container
  d3
    .select('.selection .selection--enter p')
    .text(enterSelection._groups[0].map(group => group.__data__.value));

  d3
    .select('.selection .selection--update p')
    .text(updateSelection._groups[0].map(group => group.__data__.value));

  d3
    .select('.selection .selection--exit p')
    .text(exitSelection._groups[0].map(group => group.__data__.value));
}

// immediately call the function to visualize the existing data
visualizeData(data);

// call the function following a click event on the .controls buttons, afte adding / removing a data point from the array
// ! add / remove a data point at a random index of the array
function addDataPoint() {
  const randomIndex = randomInt(data.length);
  data = [...data.slice(0, randomIndex), randomDataPoint(), ...data.slice(randomIndex)];
  visualizeData(data);
}
// function removing a data point
function removeDataPoint() {
  const randomIndex = randomInt(data.length);
  data = [...data.slice(0, randomIndex), ...data.slice(randomIndex + 1)];
  visualizeData(data);
}

// bind the function
d3
  .select('.controls--add')
  .on('click', addDataPoint);
d3
  .select('.controls--remove')
  .on('click', removeDataPoint);

              
            
!
999px

Console