Exploring the US Federal Budget - an Interactive Visualization (d3.js)

I am in the middle of working on a visualization for exploring the US Federal Budget, built with d3.js, bootstrap, and jQuery DataTables. The current version can be found at either of these links:

I have primarily tested on Chrome, a little on Safari and Firefox, and not at all with Internet Explorer (at least IE9 would be required).

Exploring the US Federal Budget
http://learnforeverlearn.com/usbudget/
Data: 1962 to 2019

The spending and receipts data files are from the OMB's Public Budget Database, available at http://www.whitehouse.gov/omb/budget/supplemental. Specifically, the "Outlays" csv file and the "Receipts" csv file. These contain both historical and projected data, going back to 1962 and up to 2019. I have spot-checked the rolled-up values against the corresponding values in the Historical Tables file from the gpo (which checked out), but this is still a work-in-progress.

Responsive Design - a (Tedious) Work-in-Progress

Perhaps obvious, but I have decided that every single possible screen size requires a potentially completely different interface design, with unique decision encumbrances for all of them. Each may present a nice little puzzle, but there are a wearying number of them. And it is difficult to predict ahead of time what the necessary adjust will be be: you have to see it.

So far, I have attempted to deal with a handful of cases, a few of which are listed below. I have tested only on Chrome on a Mac, an iPad, and an iPhone. It will be interesting to see how well the other browsers handle it.

Large Screen Desktop, iPad Landscape

In this case, all of the features are available, and you can explore the detail tables.

Large screens and iPad Landscape: you can drill down into detailed tables
Small, But Room at the Bottom

In this case, there are no detailed breakdowns available, but there's sufficient space for the horizontal scroll region at the bottom, where you can select different years.

Small Screen, but Room for the Horizontal Bar
Small, and No Room at the Bottom

In this case, there are no detailed breakdowns available, nor does the horizontal scroll area have enough room (per my current parameters), but you can change years and see the totals and how the spending breaks down in terms of mandatory, interest, and discretionary.

I have yet to deal with iPhone landscape.

Friends Don't Make Friends Read Vertical Text

In some cases, the red bar for the deficit is too small for a label. In these cases, the label dynamically slides out when you select one of these years, and tries to be as non-vertical as possible (there's a little leeway still to optimize, I think). An example is shown below. It's debatable if angled text is any easier to read than vertical text, but maybe it's the thought that counts.

Moving the Deficit label out when necessary,
but not going vertical, on a small screen
Next Steps

I have a healthy number of TODO's yet to get to - improve the performance of the rendering of the detail tables, better synchronize the animations - and there might be more important ones that come up based on initial feedback.

A Short Note on Making Your d3.js Viz Responsive

OK - this seems to work, and I wanted to make a note of it. It's not a new thing, but might save you some time when you need to do the same thing.

The basic thing we want to do is re-render a d3.js graph after a user has resized their browser window.

With this approach, on a window resize we reconstruct everything svg-related from scratch. This might not be feasible in your case, but otherwise it can be more involved and it is easy to miss something.

We also use David Walsh's "debounce" plugin that can be used to throttle how often you try to rerender while the user is still resizing. There might be a better way to tell when a user is "done resizing" - please let me know how: it doesn't affect the core idea here, just how often you call the "rebuild from scratch" method.

So, so here is one way that works (even when you change your iPhone from portrait to landscape - sweet yay):

  • Set up debounce when the page loads, via
    
        window.addEventListener('resize', debouncedResize);
    
  • Set up your "debouncedResize" method (this is using a delay of 250ms before calling the rerender)
    
    var debouncedResize = debounce(function() {yourRerenderAfterResizeMethod();},
                                    250);
    
  • Set up your rerender method (it might be a method on another object or whatever)
    
    function yourRerenderAfterResizeMethod() {
    
    //destroy existing svg elements
    d3.select('#idOfYourSvgElementToReconstruct').selectAll('*').remove()
    
    // ... other svg elements you will rebuilding
    
    //make sure you don't have straggler object properties hanging around that are used
    //  when rendering (this bit me once - I was trying to add svg elements to an empty thing, and
    //  all actions were silently ignored)
    
    //Now go to town and build all of your svg stuff, 
    // taking into account the new screen size.
    //Note that I have found (so far) that it seems best 
    // to set the width/height attributes of the svg elements so that
    //  when the user is resizing (and before the rerender is called), 
    //  things don't scale crazy. YMMV.
    
    }
    

From my experience so far, you can prevent potential inadvertent transient wacky scaling while the user is resizing by explicitly setting the width/height of the relevant svg elements. This might only matter when the enclosing element has a width set to 100% of the parent, but the jankiness until the rerender is called can be distracting otherwise.

Passing Data to (and from) a Modal Popup in Angular

Success! Perhaps naïve and a bit crude, but it is something that seems to work so far.

As part of playing with StrongLoop's Loopback framework, I am also beginning to learn about Angular via Loopback's examples. Today, I ran into needing to do something pretty basic in Angular, and it seems to be a bit harder to do than I expected... at least to someone very new to it.

The feature in question is the ability to pass parameters/info into a modal popup. The scenario is when you have a list of stuff, and you want to show a popup when the user clicks on one of the elements in the list, say for a confirm delete or something.

Anyone who has a list of stuff the user will interact with will need this feature, and so that's why it surprised me that it wasn't quite as straightforward as I anticipated.

Well, not straightforward to someone as new to Angular as myself.

So, below are the basics on one way that works - not a complete example, but this is kind of a "fresh note from the field", and may help someone nevertheless. It is based on Angular's sample on using the ui.bootstrap.modal functionality, and this stackoverflow post.

Here are the pieces:

  • Angular (of course)
  • Bootstrap
  • UI Bootstrap - Bootstrap components from the AngularUI Team
  • You have an html list where each row has some attributes unique for a row (id, etc.); here is a snippet in Jade syntax (N.B. The diminished clutter of writing and reading Jade for html is a pleasure, imo):
    
      tr(ng-repeat="file in files")
        td
          strong
          | {{file.originalFileName }}
        td
          button(ng-click="openConfirmDelete(file.originalFileName, file.id)",title="Delete")
          | Delete
    
    A basic list of stuff (Jade syntax)
    We want a styled popup specific to the row clicked

  • In your html file, define a script defining a template for the modal popup - here is a stripped-down one (yes, that's "raw" html inside the script tag):
    
      script#myModalContent(type='text/ng-template')
        .modal-header
          h3.modal-title Delete File {{name}}?
        .modal-footer
          button(ng-click="cancel()") Cancel
          button(ng-click="doDelete()") Yes, Delete File
    
    
    Defining the script "template" for the popup for Angular

  • In your controller js file, make sure and include 'ui.bootstrap", a la
    
       angular.module('app', ['angularFileUpload','ui.bootstrap'])
          .controller(...
    
    Referencing the UI Bootstrap stuff
    so it can be used

  • In the controller javascript (and I am assuming this is in definition of the controller that is used for the list), define the controller for the popup:
    
      var PopUpController = function($scope, $modalInstance, name,id) {
       
        $scope.name = name;
        $scope.doDelete = function() {  // this is the function called when the user 
                                        //  user clicks the "Yes Delete File" button              
          $modalInstance.close({doDelete:true, id:id}); //this gets passed back to 
                                                        // the code that created the popup
        };
        $scope.cancel = function () {
          $modalInstance.dismiss('cancel');
        };
      };
    
    Defining the controller for the modal confirmation popup

  • In the relevant controller for the list, define the function openConfirmDelete that will make use of both the script template and the PopUpController:
    
      ...).controller('FilesController', 
            function ($scope, $http, $modal) {
              $scope.openConfirmDelete = function (name,id) {
                 var modalInstance = $modal.open({
                                        templateUrl: 'myModalContent',
                                        controller: PopUpController,
                                        resolve: {
                                          name: function() {return name;}
                                          id: function () {return id;}
                                        }
                                     });
                                   });
    
                  modalInstance.result.then(function (opts) {
                      if (opts.doDelete === true) {
                            //you are now free to deal with the file/whatever,
                            //   as you will be able to get the id from opts.id
                            //  e.g, delete(opts.id);
                            // Note: as "id" is in a closure, I'm not sure it
                            //         is necessary to pass it back here, but
                            //         this shows how to pass anything back
                      }
                    }, 
                     function () {console.log('Modal dismissed at: ' + new Date());}
                  }); // modalInstance.result
              }; // openConfirmDelete
            ...
            ...
            }); //FilesController...
    
  • The final piece: creating the popup when
    the user clicks the "Delete" button on an item in the list

Whew.

Popular Posts