D3 and HTML Forms
FREE     Duration: 17:37
Part of Course: Intermediate D3 Course
 

Takeaways:

  • An HTML form is creating using the <form> tag with opening and closing tag
  • Common attribute value pairs for an HTML form tag are the id, action, and method
  • Almost all HTML forms will have an input element with an attribute value pair of type="submit"
  • Using D3, you can create a selection of different parts of HTML Form Elements
  • Once you have the D3 selection, you can add event listeners to each element of the selection just like you have with the SVG event listeners
  • For instance, the HTML Form radio input button element has the event "change" which can be listened to
  • You can then use the HTML Form events to trigger changes to a D3 Data Visualization using D3 Transitions

Transcript:

D3 and HTML Forms

HTML Form Basics


You are probably familiar with this famous web form.

[ Image: Google Search Form ]

This is Google's search web form.

Type in anything you want and Google will conduct a search of web documents for you.

It's simplicity and functionality have been widely covered.

It consists of a text field input and two buttons.

A Google search button.

And an I'm feeling Lucky Button.

If you are watching this video, chances are that you are an expert at using slash filling out forms and may have even created your own forms in the past.

Because of this, we'll keep the basics brief.

For the purposes of this video, we'll cover the basic HTML form and radio buttons.


An HTML form is created using the form tag.

<form attribute="value" attribute="value" ...>
    ...
</form>

There is an opening and closing tag.

And just like any other HTML tag, we can add attribute value pairs to further define the form.


Some common attributes for a form are the id, action and method.

<form id="contact-form" action="/contact/" method="post">
    ...
</form>

The id specifies a unique identifier for the particular form.

The action attribute, value pair is used if you are submitting data.

It tells the form where to submit slash send the data to so that it can be processed.

The method attribute tells the data how to travel.

Two request methods you may be familiar with are GET and POST.

The GET method requests data from a specified resource.

The POST method submits data to be processed to a specified resource.

This is why almost all web forms you encounter will have the attribute, value pair of method equals POST.


Inside of the form we can have various form elements.

  • Text Boxes
  • Text Areas
  • Option Buttons
  • Radio Buttons
  • Check Boxes
  • Drop down List/Fixed Lists
  • Password Boxes
  • Command Buttons
  • Submit Buttons
  • Reset Buttons
  • Labels
  • Image Command Buttons
  • Hidden Form Values
  • Placeholder (HTML5)
  • Email (HTML5)
  • Url (HTML5)
  • Number (HTML5)
  • Range (HTML5)
  • Date/Time (HTML5)
  • Search (HTML5)
  • Color (HTML5)

You can see the new ones added by HTML5.

We can have as many elements as we want in any form and can mix and match as we please.


Last but not least, almost all forms will have an input element with the attribute type and value of submit.

<form ...>
    ...
    <input type="submit">
</form>

Instead of rendering an input element, this HTML element will render a button, that, when clicked, submits the form to whichever action was specified by the method specified.


That's pretty much the basics of the forms.

<form ...>
    ...
</form>

The bigger picture of a form is that it allows for further interaction between a user and the web page.

It is within this big picture thinking of a form that we will use it with D3.


More specifically, we will focus not on submitting information to a server, but the actual checking of a radio button.

<form ...>
    <input type="radio" ....>
    <input type="radio" ....>    
</form>

The radio button has a "change" event that we can listen to, so it's easy to get started this way.

Check boxes and other form input types need a bit more hand-holding with JavaScript, so we'll leave those for later.

Radio buttons are a good starting point because of the way that they makes it easy for us to capture both their change as well as their value.

When a user clicks on a radio-button, it becomes selected, and all other radio-buttons with an equal name become unselected.


Let's take a look at what we are going to be building in this video.


On the screen you can see two radio buttons - one with the label Width and one with the label Height.

When I click on Width radio button, you can see the width of each rectangle expand and then contract.

When I click on Height radio button, you can see the height of each rectangle expand and then contract.

This can be very useful when toggling between data sets, showing different features of one data set or showing two different types of visualization of the same data set.

Notice that in this form we are not submitting anything, we are just using the change event of the HTML Form Radio Buttons to cause D3 to transition either the width or height of each Rectangle Element.


Let's now dive into the code to see how we are going to build this.



Listening For HTML Form Events


Let's start with the HTML form that we are going to use.

<form>
      <label>
          <input type="radio" name="radio_b" value="width" />
          Width
      </label>
      <br>
      <label>
          <input type="radio" name="radio_b" value="height" />
          Height
      </label>
  </form>

There are two labels for the form.

One for the Width

And one for the Height.

There are two radio input buttons.

These radio input buttons are created with the HTML tag "input".

We specify the type attribute to be equal to "radio".

Each button has attributes of type, name and value.

The type is what defines the buttons as radio buttons.

The name is what is used to link the two buttons together.

The value is the value of the radio button.

Additionally, the labels provide a text that we can read next to the radio buttons.


If we look at the two radio buttons, we can see their similarities and differences clearly.

<input type="radio" name="radio_b" value="width" />

<input type="radio" name="radio_b" value="height" />

The similarity that we are going to focus on is the fact that both use the same tag of "input".

D3 gives us the ability to create a selection based on HTML tags, so we will be using the "input" tag to create a selection of these two HTML Form Elements.

Note - we can use input because the example web page we are going to be working with, only has two input elements.

If we were working as part of a larger project where the web page might have more than two input elements, we would use a class attribute for each input relevant to the visualization in order to create a D3 selection of the correct elements.


Similar to when we select rectangles, circles or any other type of DOM element, we can create a selection using the D3 selectAll functionality.

<input type="radio" name="radio_b" value="width" />
<input type="radio" name="radio_b" value="height" />

var inputElems = d3.selectAll("input");

Just like with any other type of selections, we can add any attribute, value pair that we want to every element in this selection.

This is where D3 really shines through for me - not only are we able to affect SVG elements, we can also affect HTML elements in the same code.


Once we have this selection, we can add an event listener to each element in the selection.

<input type="radio" name="radio_b" value="width" />
<input type="radio" name="radio_b" value="height" />

var inputElems = d3.selectAll("input");

inputElems.on(event, function(...) {...});

Event listeners on HTML elements work exactly the same way as event listeners on SVG elements.

This is because the event listeners are DOM event listeners and HTML and SVG elements are both DOM elements.


The radio button input element event that we can listen to is "change".

inputElems.on("change", function(d, i) {   // ** Highlight Change **
     // do something here
});

When the radio button is clicked, it will emit a "change" event.

When we use D3 to listen for the "change" event, we are able to specify what JavaScript function should be run.

Because we use D3, the d variable, i variable and this variable are available to us for use inside of the function.

We can either use an anonymous function as shown or we can defined a named function elsewhere and put the name of the function as the second argument of the D3 on event listener.


This will be the first example we run in the Javascript Console.

<input type="radio" name="radio_b" value="width" />
<input type="radio" name="radio_b" value="height" />

  var inputElems = d3.selectAll("input");

  function inputChange() {
      alert(this.value);   // ** Highlight this.value **
  }
  
  inputElems.on("change", inputChange);

A few things to note:

First - the HTML Form will be hard coded into the HTML document.

So it will be something that we add slash edit in a text editor.

Second - we are using this dot value in the function even though we aren't passing it in.

Without getting too deep into JavaScript function scope, closures and nested functions, know that if a variable is available in a function, then it will be available in a function inside of that function.

This is how we can use the this variable without actually passing it in.

Third - this dot value will give us the value of the radio button that was clicked.

In our specific case, this will either be the string "width" or the string "height".


Let's run the example in the JavaScript Console.


First, we add in the HTML form into the web page document using a text editor.

Note that we put the label, radio input and text in one line for ease of using the Chrome Developer tools.

Otherwise the labels interpret the empty spaces before and after the text as actual spaces and jumble up the code.

We save this file down and open up the web browser.


This web page has the D3.js library imported from the d3js.org website.

Let's take a look at the Chrome Developer Tools

BROWSER - click into the form, labels and inputs.

We can see the form, the two labels and the two input elements.


First, we define the input element selection.

var inputElems = d3.selectAll("input");

inputElems;

We can see that the selection looks like any other D3 selection.

We can also see there are two input elements in the selection.


Second, we define the inputChange named function which will alert us when a radio button is selected.

function inputChange() {
    alert(this.value);
}


Lastly, we define add an event listener for every element in the inputElems selection.

inputElems.on("change", inputChange);

BROWSER click on the width and height input elements.

You can see that as I click on either one of the input elements, that an alert pops up telling us that the event listener has been triggered.

This shows us that we can use D3 to listen to HTML Form Events and specify what functionality we want to happen when the event is triggered.


We will use this in the next section to trigger SVG element transitions whenever one of the input elements is selected.



Form Event Triggers D3 Transitions


Now that we have an HTML form that has inputs that have D3 event listeners attached to them, let's work on the SVG part.

<div id="visualization">
</div>

Previously, we have just been appending the SVG element to the Body element.

Once we start doing more complicated visualizations, it's helpful to separate different parts so that it's clear what is going on where.

To this end, we will add an HTML div tag with an id of "visualization".

Inside this HTML div tag is where we will add the SVG Container and construct the SVG Rectangle elements.


This code selects the HTML div and appends an SVG element to it as the last child element.

var svg = d3.select("#visualization")
    .append("svg")
    .attr("width", "300")
    .attr("height", "300");

Then we define the width and height of the SVG Viewport.


This code defines three different rectangles.

var rectData = [{"x": 5,   "y": 25,  "height": 50, "width": 50},
                {"x": 85,  "y": 105, "height": 50, "width": 50},
                {"x": 165, "y": 185, "height": 50, "width": 50}];

var rects = svg.selectAll("rect").data(rectData).enter().append("rect");

Each data object has an x, y, height and width.

Then we use the D3 pattern to bound the rectData array of JavaScript object literals to SVG Rectangle elements.


Once the data is bound to the SVG rectangle elements, we use the bound data to create the attributes necessary for the rectangles to be displayed.

rects
    .attr("x",      function(d, i) { return d.x;         })
    .attr("y",      function(d, i) { return d.y;         })
    .attr("width",  function(d, i) { return d.width;     })
    .attr("height", function(d, i) { return d.height;    })
    .attr("id",     function(d, i) { return "rect-" + i; });

Note that we add an id attribute to the rectangle.

Though it's not necessary for this example, it's good to always name the elements you create, as it helps with debugging the code.


Next, we define a function that when evaluated triggers a transition of the rectangles' width.

function changeWidth() {
    rects
        .transition()
            .duration(3000)
            .attr("width", "300")
        .transition()
            .duration(3000)
            .attr("width", "50");
};

We will call this function changeWidth.

It uses the rectangles selection and applies two transitions to each element of the selection.

First, it increases the width of the rectangle to 300 units over a span of 3 seconds.

Then, once the first transition is done, it decreases the width of the rectangle back to 50 units over a span of 3 seconds.


Next, we define a function that when evaluated triggers a transition of the rectangles' height.

function changeHeight() {
    rects
        .transition()
            .duration(3000)
            .attr("height", "300")
        .transition()
            .duration(3000)
            .attr("height", "50");
}

We will call this function changeHeight.

It uses the rectangles selection and applies two transitions to each element of the selection.

First, it increases the height of the rectangle to 300 units over a span of 3 seconds.

Then, once the first transition is done, it decreases the height of the rectangle back to 50 units over a span of 3 seconds.


We now have two functions that will cause a transformation of the SVG rectangles.

changeWidth();

changeHeight();

While we could have written these functions as anonymous functions within the event listener, this way of writing functions is easier to follow and debug.


Going back to our event listener and inputChange function, we have to do two things in order to get this to work correctly.

function inputChange() {
    alert(this.value);
}
  
inputElems.on("change", inputChange);

One - we have to be able to differentiate between the width radio button being clicked and the height radio button being clicked.

Two - once we know which button was clicked, we have to call the right function.


We can use the "this dot value" to get the value of the radio button that was clicked.

function inputChange() {
    var inputValue = this.value;

    if      (inputValue === "width" ) { changeWidth();  }
    else if (inputValue === "height") { changeHeight(); };
}

Once we have the value, this solves issue number one - how to differentiate between the two radio buttons that were clicked.

Then, we can use an if else conditional expression to cause either the changeWidth or changeHeight functions to be evaluated.

This solves issue two.

Now, when the event listener receives a "change" event, it will figure out what the value of the input element is and then evaluate one of the two functions that causes a D3 transformation.


Lastly, we always have to remember to bind the event listener to elements.

inputElems.on("change", inputChange);

Because we are working in the JavaScript console, this will always be the last thing we do.

If you are looking at someone else's code, often times this will come before the definitions of the JavaScript functions.

This is due to JavaScript Function Hoisting.

Which is something for another time and place.

Let's concentrate on the example we are creating.


Let's run the example in the JavaScript Console.


First, we add in the HTML DIV to the web page document that contains our form using a text editor.

Note that we put it after the form to ensure that the form element is above the SVG element.


We save this file down and open up the web browser.

This web page has the D3.js library imported from the d3js.org website.


Let's take a look at the Chrome Developer Tools

BROWSER - click into the form, labels, inputs and DIV.

We can see the form, the two labels, the two input elements and the Visualization DIV.


Before we start building the visualization, we create a D3 selection of the HTML Form Radio Button Input Elements.

var inputElems = d3.selectAll("input");

This selection will be used when we want to attach event listeners to the input elements.


Now, to create the visualization, let's first create the SVG Viewport in the HTML Div with the id of "visualization"

var svg = d3.select("#visualization")
    .append("svg")
    .attr("width", "300")
    .attr("height", "300");

BROWSER - open up the DIV in the Chrome Developer Tools Elements Section


Next, let's define the rectangle data that we will be using.

var rectData = [{"x": 5,   "y": 25,  "height": 50, "width": 50},
                {"x": 85,  "y": 105, "height": 50, "width": 50},
                {"x": 165, "y": 185, "height": 50, "width": 50}];

This data is an array of JavaScript Object Literals.


Next, using the D3 pattern, we bind the data to SVG rectangle elements.

var rects = svg.selectAll("rect").data(rectData).enter().append("rect");

BROWSER - open up the SVG to see the rectangle elements

You can see the rectangle elements in the Chrome Developer Tools Elements Section.


Finally, we define the rectangle attributes so that they will appear on the screen.

rects
    .attr("x",      function(d, i) { return d.x;         })
    .attr("y",      function(d, i) { return d.y;         })
    .attr("width",  function(d, i) { return d.width;     })
    .attr("height", function(d, i) { return d.height;    })
    .attr("id",     function(d, i) { return "rect-" + i; });

And with that we now have the rectangles that will experience transformations each time the HTML Form Radio Buttons are clicked.


Next, we define the width transformation function called changeWidth.

function changeWidth() {
    rects
        .transition()
            .duration(3000)
            .attr("width", "300")
        .transition()
            .duration(3000)
            .attr("width", "50");
};

Recall that this function expands the width and then contracts the width of every rectangle in the "rects" selection.


Now, we define the height transformation function called changeHeight.

function changeHeight() {
    rects
        .transition()
            .duration(3000)
            .attr("height", "300")
        .transition()
            .duration(3000)
            .attr("height", "50");
};

Recall that this function expands the height and then contracts the height of every rectangle in the "rects" selection.


Let's test the changeWidth function.

changeWidth();

You can see that it works correctly.

Each rectangle has it's width go from 50 to 300 and then back down to 50.

One question that may have come up is why do all the rectangle's right edges seemingly end up at the same place?

That is, if the rectangles are at a slant then we would expect that when their widths increase by the same amount, that the right edges would still be at a slant.

The issue is that the rectangles go beyond the 300 by 300 SVG container so the extra parts of the rectangles disappear.

This is very helpful to encounter and think about when you are making data visualizations with D3.

Remember - if it's out of the SVG container dimensions, it will not be visible on your screen.


Let's test the changeHeight function.

changeHeight();

You can see that it works correctly.

Each rectangle has it's height go from 50 to 300 and then back down to 50.

Similarly to the changeWidth function, the rectangle's bottom edges seemingly all end up at the same place.

This is the same issue as with the width.

The SVG Viewport is 300 by 300 so a good portion of the rectangle's new height is hidden away from us.

So remember - if it's out of the SVG container dimensions, it will not be visible on your screen.

Alright, so both transformation functions work correctly.


Next we define the inputChange function which is what will drive the functionality.

function inputChange() {
    var inputValue = this.value;

    if      (inputValue === "width" ) { changeWidth();  }
    else if (inputValue === "height") { changeHeight(); };
};

Note that this function is difficult to test from the JavaScript Console command line because we need a this object to get the value from in order to use the Conditional Expression.


Finally - we attach an event listener to each element in the inputElems selection.

inputElems.on("change", inputChange);

BROWSER - click on the HTML Form Radio Buttons slowly and wait for them to finish.

You can see on the screen that we were able to replicate the example at the beginning of this video.

The full description of what is happening is the following:

Each HTML Form input element is now listening to the "change" event.

When it receives this event it will call the inputChange function.

The inputChange function will then figure out the value of the button that was pressed.

Then, using this value, it calls either the changeWidth function or the changeHeight function.

If the changeWidth function is called, it will increase and then decrease the width of all of the rectangles.

If the changeHeight function is called, it will increase and then decrease the height of all of the rectangles.

Somewhat complicated, correct?

This is why we use named functions rather than anonymous functions.

Though it is entirely possible to write all of this functionality as a series of nested anonymous functions, it is better to use named functions.


And with that we have covered how we can use the HTML Form basics to build HTML Forms whose input elements have event listeners attached to them that when triggered cause D3 transitions on SVG elements.

<< Back To D3 Screencast and Written Tutorials Index