Tag Cloud from Gravity Form Entries WP Plugin

A tag cloud of filler text.

Based on a conversation I had on Tuesday there appeared to be a need to generate a tag cloud1 from Gravity Forms responses. I didn’t see any immediate solutions after browsing for plugins. Granted, I could have jerry rigged something by creating posts and doing something weird where I added the words as tags . . . but I’m trying to do things in a more sustainable way lately and I figured this wouldn’t be too tough.

I’m going to try to do a decent job of moving through the development process in stages. If you just want to mess with the plugin, it’s on GitHub.

Find a Decent Tag Cloud Library

I looked around and found wordcloud2.js. It seemed perfect. It has some real flexibility and the hard work has been done for me. I played around with a demo in CodePen. This lets me work out any kinks I might have in the js/html/css side of things before adding any WordPress and PHP complexities. If I don’t force patterns like this, I’ll add enough complexity that figuring out which thing I’m not understanding is much harder. It might take some extra time but it saves me quite a bit more in the long run.

Building the Plugin Foundation

enqueue the main scripts

Following my mantra of slowly adding complexity, first I added the scripts. They’re all nicely enqueued the way WordPress likes. They’re just the css and js that wordcloud2.js needs to work. After verifying these loading correctly, I can move on to phase two.

add_action('wp_enqueue_scripts', 'gqcloud_load_scripts');
function gqcloud_load_scripts() {                              
    wp_enqueue_script('gravity-cloud-main-js', plugin_dir_url( __FILE__) . 'js/gravity-cloud-main.js',array(), '1.0', false); 
    wp_enqueue_style( 'gravity-cloud-main-css', plugin_dir_url( __FILE__) . 'css/gravity-cloud-main.css');  

At this point I need two additional pieces to make sure all is working as expected. I need an HTML element with an ID and I need a little javascript that loads some data and targets that ID for the cloud creation.

I can make the HTML directly in the post easily enough and I know it works on based on my codepen testing.

<div id="demo" style="width: 1170px; height: 760px; position: relative;"></div>

There are a couple different ways I could add the javascript for the test but I opted to do it via content filter. That’s also why I’m initially loading the wordcloud2.js in the header rather than the footer. Load order matters and I’m loading this little script pretty randomly so I want to make sure the main script is there first. You’ll note in the script below I’m not trying to load any data from Gravity Forms yet, I’m just making sure all this stuff works how I expect it to.

function my_the_content_filter($content) {
  $script = "<script>
let list = [['bar', 56], ['buzz', 72], ['spanish', 21], ['green', 11]];
var attempt = WordCloud(document.getElementById('demo'), { list: list } );
  return $content . $script;
add_filter( 'the_content', 'my_the_content_filter' );

Seeing this work, I now know that any issues I have are going to be things I’ve done.

enqueue the little script

Now we’ll just take that little script and enqueue it properly. That’s just a matter of adding an additional line. Now we’re enqueueing two scripts and one css file. Once I make sure it all still works, I can move on to figuring out how to get the data.

add_action('wp_enqueue_scripts', 'gqcloud_load_scripts');

function gqcloud_load_scripts() {                              
    wp_enqueue_script('gravity-cloud-main-js', plugin_dir_url( __FILE__) . 'js/gravity-cloud-main.js',array(), '1.0', true); 
    wp_enqueue_style( 'gravity-cloud-main-css', plugin_dir_url( __FILE__) . 'css/gravity-cloud-main.css');  
    wp_enqueue_script('gravity-cloud-indiv-js', plugin_dir_url( __FILE__) . 'js/gravity-cloud-indiv.js',array('gravity-cloud-main-js'),'1.0', true); 

Getting the Data

Once again, I tried to keep one thing in motion at a time. First we want to get the data. Retrieving data from Gravity Forms is pretty easy if you’re familiar with the WordPress query loop. I started off by hard coding the form ID and the desired field ID. Once I made sure it was returning what I wanted I added the form ID and field ID options into the shortcode. I ended up with the function below that gets the data.

function get_gform_words($form_id, $fields){
	$search_criteria = array(
    'status'        => 'active',

  $sorting         = array();
  $paging          = array( 'offset' => 0, 'page_size' => 200);
  $total_count     = 0;

  $entries = GFAPI::get_entries($form_id, $search_criteria, $sorting, $paging, $total_count );
  $raw = "";
  $tag_data = [];
  $fields_array = explode(',',$fields);
  foreach ($entries as $key => $value) {	 
     foreach ($fields_array as $field_id) {
       $raw .= $value[$field_id];
     //$common_removed = remove_common_words($raw); // this will be where I do the removal of common words
  	 $no_punctuation = preg_replace("/(?![=$%-])\p{P}/u", "",$raw); //removes punctuation
  	 $bits = preg_split('/\s+/', $no_punctuation);
     foreach ($bits as $key => $bit) {
      if(multiKeyExists($tag_data, $bit)) {
        $i = $tag_data[$bit] = $tag_data[$bit]+1;
      } else {
        $tag_data[$bit] = 1;
     return $tag_data;

As a minor tip, if you’re trying to figure out PHP variables var_dump is fine but you’ll have a much better time using the bit of code below. It makes reading the arrays etc. much, much easier.


The Shortcode

The shortcode ends up with three variables right now. The first is the form ID, followed by the field IDs you want, and finally the size of the font you want. You can see that they default to 1, 1, and 1.6 respectively if you leave them blank.

The more interesting portion is wp_localize_script, which we’re using to pass the php variables into the javascript.

function gqcloud_make_the_list( $atts, $content = null ) {
    extract(shortcode_atts( array(
         'id' => '', //gform ID   
         'fields' => '',//field IDs separated by commas
         'size' => '', //this determines the size of the font    
    ), $atts));         
    	$id = $id;   
    } else {
      $id = 1;
    if ($fields){
      $fields = $fields;
    }  else {
      $fields = 1;
     if ($size){
      $size = $size;
     } else {
      $size = 1.6;
    $entries = get_gform_words($id, $fields);   
   //return $entries;
    $cloud_data = array(          
           'source' => json_encode($entries),
           'size' => $size,
    wp_localize_script('gravity-cloud-indiv-js', 'cloudData', $cloud_data); //sends data to script as variable
	  return '<div id="gc-cloud" style="100%; height: 50vw; position: relative;">foo</div>';//gives us the HTML
add_shortcode( 'gcloud', 'gqcloud_make_the_list' );

On the javascript side of things, we have this.

let list = cloudData.source;//the localized script variable from the php that gives the data
let weight = cloudData.size;//the localized script variable from the php that sets the font size
let a = JSON.parse(list);
let b = [];
for(var i in a){
	b.push([i, a[i]]);

var attempt = WordCloud(document.getElementById('gc-cloud'), { 
  list: b,  
  fontFamily: 'Times, serif', 
  rotateRatio: 0.5,
  rotationSteps: 2,
  backgroundColor: '#fff',  
  drawOutOfBound: true,
  gridSize: Math.round(16 * jQuery('#gc-cloud').width() / 1024),
  weightFactor: function (size) {
    return Math.pow(size, weight) * jQuery('#gc-cloud').width() / 1024;
  minFontSize: 200,


And that’s pretty much it. I’ve got to add an “ignore words” list but it’s functional and might be useful to someone else.

1 Welcome to 2008? While I mock many uses of the tag cloud there are some interesting applications in the right scenarios.

Comments on this post

  1. Alan Levin said on April 7, 2019 at 12:45 pm

    Thanks for useful leads- Wordcloud2.js looks useful with WordPress API playing. And wp_localize_script is new; I’d been doing some silly things with hidden values in forms to make stuff available to scripts

    • Tom Woodward said on April 8, 2019 at 8:45 am

      I’d seen people talk about localize script but had always worked around it by setting data values on html elements etc. Having now done it, I should have been doing it long ago. So many tricks to know . . .