Gravity Forms to Graph Interaction

I’ve long wanted to build more interactive digital content after being inspired by Brett Victor’s explorable explanations. I’m now finally coming to the place where my ability to build things is starting to match my desire to create them.

This particular piece of interactive content was inspired by the NYT’s You Draw It Obama article. We did a direct imitation with carbon sequestration a while ago but I liked the broader concept of guessing at something and then being presented with data. In this case we’re trying to get people to think a little harder about the good things they got out of life just by being born.

Screenshot of the submission pattern for the confronting your privilege form. Buttons from -10 to 10 are horizontally across the top and a grid of yes/no questions are below.

Form Tweaking

First I wanted to tweak the gravity form layout. Given our first question was a placement on a scale of -10 to 10, that content needed to be arranged horizontally. This is Gravity Forms and the radio button input type. So I copied the HTML into a codepen to play with it. This is the result.

See the Pen
gform layout
by Tom (@twwoodward)
on CodePen.

Results Display

Another pattern I find myself repeating is roughing out the data display portion in Codepen. I can then figure out how I need Gravity Forms/WordPress to pass the data live but it’s easier to build out the experience a bit without worrying about getting live data. Isolate your variables and gradually add complexity.

In the codepen below you can see that scores are set manually for the demo. The function goes through each score and gets the div with the same ID. It also increments the data-count +1 each time it cycles through. That allows me to set an inline CSS style for border-top to be the data-count * 8.5 (or whatever looks good). Once we have enough entries we can tone this down but prest-o change-o a bar chart using nothing but thick borders.

let scores = ['pos-1', 'pos-1', 'pos-3','neg-2','pos-1', 'pos-1', 'pos-3','neg-2', 'neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','pos-7']

countThem(scores)
function countThem(scores){
  scores.forEach(function(score){
    let bubble = document.getElementById(score);
    console.log(bubble)
    let count = parseInt(bubble.getAttribute('data-count'),10);
    count = count+1;
    bubble.setAttribute('data-count', count);
    bubble.setAttribute('style', 'border-top:' + (count*8.5) + 'px solid #00b3be;');
  })
}
<div class="bubble-holder" id="bubble-zone">
    <div class="bubble" id="neg-10" data-count="0">-10</div>
    <div class="bubble" id="neg-9" data-count="0">-9</div>
    <div class="bubble" id="neg-8" data-count="0">-8</div>
    <div class="bubble" id="neg-7" data-count="0">-7</div>
    <div class="bubble" id="neg-6" data-count="1">-6</div>
    <div class="bubble" id="neg-5" data-count="0">-5</div>
    <div class="bubble" id="neg-4" data-count="0">-4</div>
    <div class="bubble" id="neg-3" data-count="2">-3</div>
    <div class="bubble" id="neg-2" data-count="1">-2</div>
    <div class="bubble" id="neg-1" data-count="11">-1</div>
    <div class="bubble" id="zero-0" data-count="1">0</div>
    <div class="bubble" id="pos-1" data-count="3">1</div>
    <div class="bubble" id="pos-2" data-count="0">2</div>
    <div class="bubble" id="pos-3" data-count="3">3</div>
    <div class="bubble" id="pos-4" data-count="0">4</div>
    <div class="bubble" id="pos-5" data-count="5" >5</div>
    <div class="bubble" id="pos-6" data-count="0">6</div>
    <div class="bubble" id="pos-7" data-count="0">7</div>
    <div class="bubble" id="pos-8" data-count="0">8</div>
    <div class="bubble" id="pos-9" data-count="0">9</div>
    <div class="bubble" id="pos-10" data-count="0">10</div>
  </div>

See the Pen
bubblish chart exploration
by Tom (@twwoodward)
on CodePen.

WordPress/Gravity Forms Land

Now to get the live data and integrate it all. This function gets the Gravity Form entries and creates the data structure we need to pass on to the javascript.


function gform_stepper($entry, $form){
   $search_criteria = array(
    'status'        => 'active',    
);

  $sorting         = array();
  $paging          = array( 'offset' => 0, 'page_size' => 100 );//getting most recent 100 right now
  $total_count     = 0;

  $entries = GFAPI::get_entries(4, $search_criteria, $sorting, $paging, $total_count );
  $html = '';
  $total_scores = [];
  $total_guesses = [];
    foreach ($entries as $entry) {
      if (intval($entry['gsurvey_score'])>0){
        $pre = 'pos-';//add pos if greater than 0
      }
      if (intval($entry['gsurvey_score'])<0){
        $pre = 'neg';//add neg if less than 0
      } 
      if (intval($entry['gsurvey_score']) === 0) {
        $pre = 'zero-';
      }
      array_push($total_scores,$pre . $entry['gsurvey_score']);
      $guess = array_push($total_guesses, $entry[3]);
    }    
      $gform_scores = array(          
           'scores' => $total_scores,
       );
     wp_localize_script('main-course', 'gformScores', $gform_scores); //sends data to script as variable     
}

let scores = gformScores.scores;//THIS IS THE VARIABLE PASSED via localize
countThem(scores)
function countThem(scores){

  scores.forEach(function(score){
    let bubble = document.getElementById(score);
    console.log(bubble)
    let count = parseInt(bubble.getAttribute('data-count'),10);
    count = count+1;
    bubble.setAttribute('data-count', count);
    bubble.setAttribute('style', 'border-top:' + (Math.ceil(count*3.5)) + 'px solid rgba(0, 179, 190,1);');
  })
}

The result is a form that you can submit and get results like you see below. Just the beginning of things but a step in the right direction I think.

After form submission the data is graphed and displayed. Boxes show your guess vs your actual score and combined scores of previous participants.

Gravity Forms to Graph Interaction

I’ve long wanted to build more interactive digital content after being inspired by Brett Victor’s explorable explanations. I’m now finally coming to the place where my ability to build things is starting to match my desire to create them.

This particular piece of interactive content was inspired by the NYT’s You Draw It Obama article. We did a direct imitation with carbon sequestration a while ago but I liked the broader concept of guessing at something and then being presented with data. In this case we’re trying to get people to think a little harder about the good things they got out of life just by being born.

Screenshot of the submission pattern for the confronting your privilege form. Buttons from -10 to 10 are horizontally across the top and a grid of yes/no questions are below.

Form Tweaking

First I wanted to tweak the gravity form layout. Given our first question was a placement on a scale of -10 to 10, that content needed to be arranged horizontally. This is Gravity Forms and the radio button input type. So I copied the HTML into a codepen to play with it. This is the result.

See the Pen
gform layout
by Tom (@twwoodward)
on CodePen.

Results Display

Another pattern I find myself repeating is roughing out the data display portion in Codepen. I can then figure out how I need Gravity Forms/WordPress to pass the data live but it’s easier to build out the experience a bit without worrying about getting live data. Isolate your variables and gradually add complexity.

In the codepen below you can see that scores are set manually for the demo. The function goes through each score and gets the div with the same ID. It also increments the data-count +1 each time it cycles through. That allows me to set an inline CSS style for border-top to be the data-count * 8.5 (or whatever looks good). Once we have enough entries we can tone this down but prest-o change-o a bar chart using nothing but thick borders.

let scores = ['pos-1', 'pos-1', 'pos-3','neg-2','pos-1', 'pos-1', 'pos-3','neg-2', 'neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','pos-7']

countThem(scores)
function countThem(scores){
  scores.forEach(function(score){
    let bubble = document.getElementById(score);
    console.log(bubble)
    let count = parseInt(bubble.getAttribute('data-count'),10);
    count = count+1;
    bubble.setAttribute('data-count', count);
    bubble.setAttribute('style', 'border-top:' + (count*8.5) + 'px solid #00b3be;');
  })
}
<div class="bubble-holder" id="bubble-zone">
    <div class="bubble" id="neg-10" data-count="0">-10</div>
    <div class="bubble" id="neg-9" data-count="0">-9</div>
    <div class="bubble" id="neg-8" data-count="0">-8</div>
    <div class="bubble" id="neg-7" data-count="0">-7</div>
    <div class="bubble" id="neg-6" data-count="1">-6</div>
    <div class="bubble" id="neg-5" data-count="0">-5</div>
    <div class="bubble" id="neg-4" data-count="0">-4</div>
    <div class="bubble" id="neg-3" data-count="2">-3</div>
    <div class="bubble" id="neg-2" data-count="1">-2</div>
    <div class="bubble" id="neg-1" data-count="11">-1</div>
    <div class="bubble" id="zero-0" data-count="1">0</div>
    <div class="bubble" id="pos-1" data-count="3">1</div>
    <div class="bubble" id="pos-2" data-count="0">2</div>
    <div class="bubble" id="pos-3" data-count="3">3</div>
    <div class="bubble" id="pos-4" data-count="0">4</div>
    <div class="bubble" id="pos-5" data-count="5" >5</div>
    <div class="bubble" id="pos-6" data-count="0">6</div>
    <div class="bubble" id="pos-7" data-count="0">7</div>
    <div class="bubble" id="pos-8" data-count="0">8</div>
    <div class="bubble" id="pos-9" data-count="0">9</div>
    <div class="bubble" id="pos-10" data-count="0">10</div>
  </div>

See the Pen
bubblish chart exploration
by Tom (@twwoodward)
on CodePen.

WordPress/Gravity Forms Land

Now to get the live data and integrate it all. This function gets the Gravity Form entries and creates the data structure we need to pass on to the javascript.


function gform_stepper($entry, $form){
   $search_criteria = array(
    'status'        => 'active',    
);

  $sorting         = array();
  $paging          = array( 'offset' => 0, 'page_size' => 100 );//getting most recent 100 right now
  $total_count     = 0;

  $entries = GFAPI::get_entries(4, $search_criteria, $sorting, $paging, $total_count );
  $html = '';
  $total_scores = [];
  $total_guesses = [];
    foreach ($entries as $entry) {
      if (intval($entry['gsurvey_score'])>0){
        $pre = 'pos-';//add pos if greater than 0
      }
      if (intval($entry['gsurvey_score'])<0){
        $pre = 'neg';//add neg if less than 0
      } 
      if (intval($entry['gsurvey_score']) === 0) {
        $pre = 'zero-';
      }
      array_push($total_scores,$pre . $entry['gsurvey_score']);
      $guess = array_push($total_guesses, $entry[3]);
    }    
      $gform_scores = array(          
           'scores' => $total_scores,
       );
     wp_localize_script('main-course', 'gformScores', $gform_scores); //sends data to script as variable     
}

let scores = gformScores.scores;//THIS IS THE VARIABLE PASSED via localize
countThem(scores)
function countThem(scores){

  scores.forEach(function(score){
    let bubble = document.getElementById(score);
    console.log(bubble)
    let count = parseInt(bubble.getAttribute('data-count'),10);
    count = count+1;
    bubble.setAttribute('data-count', count);
    bubble.setAttribute('style', 'border-top:' + (Math.ceil(count*3.5)) + 'px solid rgba(0, 179, 190,1);');
  })
}

The result is a form that you can submit and get results like you see below. Just the beginning of things but a step in the right direction I think.

After form submission the data is graphed and displayed. Boxes show your guess vs your actual score and combined scores of previous participants.

Making an Index Using Javascript

Working with a faculty member we had a rather long page that was originally written in Google Docs. It had many sections that were (mostly) designated by H tags of various denominations. The goal was to and put it on a website quickly build an index of anchor links. I did not wish to do the index portion by hand.

With javascript things like this are relatively pleasant. You can see the whole thing in this codepen but I’ll break it down a bit below.

First we can get all the H tags with querySelectorAll.

let headers = document.querySelectorAll("h2, h3, h4, h5, h6")

I can console.log(headers) and I’ll see a NodeList of all the headers it found. I tend to work console.log all my variables as I go just to make sure it’s really happening the way I think it is.

My next move is to add an id to each of these headers so that we can navigate to them via anchor links. with this forEach loop each header will get an id of header-whatever number we’re on in the loop.

let i = 1;
headers.forEach(function(header) {
  header.id = "header-" + i;
  ++i;
});

Now that I have headers that I can link to as anchor links, I need to build the index and put it somewhere.

In this case it was easy for me to add a div to the source manually so I did. That will be where my index ends up.

    I’ll get that div as a destination.

    let indexHolder = document.getElementById('index');
    

    I’ll assign a variable (indexHtml) to hold a string of HTML which will be all the li tags with the links and titles.

    We’ll go back to our forEach (cleaned of other stuff for clarity) loop and use it to build out that HTML.

    let i = 1;
    let indexHtml = '';
    headers.forEach(function(header) {
      indexHtml = indexHtml + '<li><a href="#header-' + i + '">'+header.innerHTML+'</a></li>' ;
      ++i;
    });//
    indexHolder.innerHTML = indexHtml;
    

    Now that we have the string, we can assign it as the innerHTML of the index div we created earlier.

    indexHolder.innerHTML = indexHtml;
    

    The whole thing looks like this.

    let headers = document.querySelectorAll("h2, h3, h4, h5, h6")
    let i = 1;
    let indexHtml = '';
    let indexHolder = document.getElementById('index');
    headers.forEach(function(header) {
      header.id = "header-" + i;
      indexHtml = indexHtml + '<li><a href="#header-' + i + '">'+header.innerHTML+'</a></li>' ;
      ++i;
    });//
    indexHolder.innerHTML = indexHtml;
    
    

    Google Sheets Data Flow

    This is a pretty specific thing but the concepts ought to be broadly applicable and interesting for the 3 to 5 people who will end up reading this. It’s a fairly amusing blend of less standard Google Functions and a bit of Google Script to do something fairly decent that had been quite a bit of hassle to do previously.

    We have Social Work students who are assigned to various supervisor/liaison people. There are a lot of students. We wanted students to be able to submit a form to Google Drive and we’d keep track of all this and show only the relevant data to the various supervisors.

    Files from Form

    Setting up a form that requires you to be logged in and accepts files is now very easy in Google Forms. It also remains easy to log that information to a spreadsheet associated with the form.

    Merging the Data

    The student email address became the unique ID that would allow us to tie the form submission to the list of students and their programs, liaisons etc. Now we needed a formula to link these two sheets via email. I started with =VLOOKUP but that would have required the student email to be the leftmost column in the data and that would be awkward for other things. After some banging around and general bad attitude on my part I found =MATCH. MATCH doesn’t care about the order and returns the row where the match occurs. That formula =MATCH(H2,’Form Responses 1′!D:D,0) let me build a few others to pull in what I wanted like this =INDIRECT(“‘Form Responses 1’!A”&I2). Note that to use other cell values as variables in other cell functions you need to use =INDIRECT. This ended up getting me the time of submission for the file and a link to the file.

    Creating the Individual Views

    Next up I needed to create views for 24 different people who were associated with various students. To get this I used the =UNIQUE function on the column holding their names. I didn’t want to do this by hand because I don’t like to do boring things. I also needed to figure out the function that would pull in the correct data from the combined sheet and prevent people from accidentally messing up that function.

    The formula ended up being a combination of =IMPORTRANGE and =QUERY. The following function did the trick.

    function makeSupervisorFormula(sup){
      var supervisor = "'"+sup+"'";
      var formula = '=QUERY(IMPORTRANGE("https://docs.google.com/spreadsheets/d/YOUR_FILE_ID_HERE/","student list!A:K"),"SELECT * WHERE Col1=' + supervisor +'")';
      return formula;
    }

    Now I needed to create a spreadsheet for each person, name it, set cell A1 to be that function, protect it, and then put all the spreadsheets in the same folder.

    function makeSupervisorSheet(sup){
      var ssNew = SpreadsheetApp.create(sup);//make new ss
      var id = ssNew.getId();
      var sheet = ssNew.getSheets()[0];//get first and only sheet
    
      var cell = sheet.getRange("A1");//get cell A1
      var formula = makeSupervisorFormula(sup);
      cell.setFormula(formula);//set our formula via the function above
      var protection = cell.protect().setDescription('Sorry this links elsewhere.');//protect this with the emails for users defined below
      protection.addEditor('foo@vcu.edu');
      protection.addEditor('bar@vcu.edu');
    
      var file = DriveApp.getFileById(id);//put it all in the same folder
      var folder = DriveApp.getFolderById('YOUR_FOLDER_ID_HERE');
      folder.addFile(file);
    }
    

    Now we just needed to run all this through an array containing the names of the supervisor people.

    function allSups(){
      var sups = ['foo','bar','buzz'];//your supervisors here . . .   
     sups.forEach(function(sup){
      makeSupervisorSheet(sup);//do that work
    });
    }
    

    And presto, a whole bunch of files that do what we want. The one hassle is that it seems you have to approve the =IMPORTRANGE function by hand from the created sheet.

    Starting to Think Through a Mapping Theme with ACF

    We’re working with Dr. Nicole Turner on a mapping site that will accompany her upcoming book. There’s a lot of specifics there which we’re considering while trying to walk the fine line where what we make is also something we’ll be able to use with other people down the road. We want to generalize but not too much. I’m sketching out some early thinking here as a way to document it personally and to share it with Jeff (who’s thinking through the javascript side of things).1

    Thinking About the Data

    It seems that any mapping project would have three basic data types– People, Locations, and Events. People because humans are usually important in these scenarios. Locations being important in mapping and events for things that have limited duration. Matt described it well as the Who, When, and Where.

    Those types could be associated with each other in multiple ways. A person might be associated with various locations and various events. Events might involve various people in various places. If I think too hard I’ll make this more complex.

    The Custom Post Types & ACF Structure

    I feel relatively good about those three main types so breaking down the details of what those big boxes should contain was the next consideration.

    People

    • First Name
    • Middle Name
    • Last Name
    • Title
    • Description/Bio
    • Birth Event*
    • Death Event*
    • Events*
    • Locations*
    • Categories
    • Featured Image

    Locations

    • Title
    • Latitude
    • Longitude
    • Description
    • Street Address
    • Events*
    • People*
    • Categories
    • Featured Image

    Events

    • Title
    • Start
    • End
    • Description
    • Location*
    • People*
    • Categories
    • Featured Image

    The items designated with the * are tied in via the ACF relationship field. This was something new to me but was super easy to set up. I turned on the ACF to Rest API plugin in addition to making sure these custom post types would appear in the WP REST API ‘show_in_rest’ => true.

    Now I’ve got a decent interface for backend editing that makes adding connections between all these things as easy as clicking. We’ll refine things more going forward but it confirms some solid options that we can build on.
    Animation showing association of one post with another.

    The Data

    With relationships we can either return the ID of the associated post or we can return the whole post object. Now at the REST endpoints (wp-json/wp/v2/event?_embed) for the various post types we’ll get JSON like this. I think this is going to be very nice for some of the related navigation construction.
    JSON data structure showing the associated ACF post data.


    1 I started to write him an email but it go very long and emails aren’t friendly to some of the formatting so I figured why not write a blog post.