Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

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

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

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

              
                <body>
  <h1>REFViewer in D3</h1>
  <p>Using the <code>d3-rs-bars</code> component, this <a href="https://www.staff.city.ac.uk/~jwo/refviewer/">re-implements some of the excellent REFViewer</a> by Jo Wood of the giCentre at City University, London.</p>
  <p><a href="https://www.ref.ac.uk/">Research Excellence Framework (REF)</a> scores represent the quality of research at each UK institution using the 2008 assessment. Darker red colours represent higher quality research scores either as a proportion of submitted staff ('Scale by staff %') or by the volume of staff submitted ('Scale by staff counts'). Toggle the buttons below to animate changes.</p>
  <div class="options">
    <h5>Scale by</h5><button id="staff-pct">staff %</button><button id="staff-count">staff counts</button>
  </div>
  <div class="options">
    <h5>Order by</h5><button id="order-inst">institution</button><button id="order-4">4*</button><button id="order-43">4* + 3*</button>
  </div>
  <div id="elm"></div>
</body>
              
            
!

CSS

              
                body {
  margin: 1em;
}

button {
  margin-left: 1em;
}

div.options {
  display: flex;
  align-items: baseline;
  margin-top: 1em;
}
              
            
!

JS

              
                //RedSift
//Showcasing the d3-rs-bars library
const FILLS = ['#C8091A', '#DF3C47', '#F07679', '#FEADAA', '#FFE4DC'];  //colours fill in hex value
const LEGEND = ['4*', '3*', '2*', '1*', 'u/c'];  //legend 
const BAR_WIDTH = 800;   //width of bar
const BAR_HEIGHT = 8;    //height of bar
const BAR_PAD = 1;      //

let pct = d3.format('.0%'); // % formatter for the tip

let chart = d3_rs_bars.html('refviewer')
 .width(BAR_WIDTH)  //customise width
 .fill(FILLS)       //bars colour fill
 .legend(LEGEND)    //add legend
 .orientation('left')   //change the orientation
 .legendOrientation('top')  //add legend
 .displayHtml((d, i) => d.l + ', ' + LEGEND[i] + ', ' + (d.v[i] > 0 && d.v[i] < 1 ? pct(d.v[i]) : d.v[i]))
 .inset({  
   left: 180,
   right: 130,
   top: 16,
   bottom: 0
 });    //add tip display

function draw(data, animate) {   //update bar on called
 chart.barSize(BAR_HEIGHT);      //update bar height
 chart.height(data.length * (BAR_HEIGHT + BAR_PAD) + chart.margin() + chart.inset().top + chart.inset().bottom + 40); //update chart height

 let elm = d3.select('#elm').datum(data);  //update to element and data
 if (animate === true) {     //on animate
   elm = elm.transition();   //add transition
 } 
 elm.call(chart); // updating is a case of re-calling the chart
}

// helper to make the buttons exclusive
function makeOptions(ops, cb) {     
 ops.forEach(function(n, i) {      //toggle element & button class 
   let node = d3.select(n);        //select element

   if (i === 0) {                   
     node.attr('class', 'danger');  //button property 
   }

   node.on('click', function() {    //on click 
     let isSet = node.classed('danger');    //change the button class
     if (isSet) return;     //if true

     isSet = !isSet;        //if false
     node.attr('class', isSet ? 'danger' : ''); //toggle class if isSet return true or false

     ops.forEach(function(p, j) {     //toggle element & button class
       if (j !== i) {                 
         d3.select(ops[j]).attr('class', ''); //remove class if statement is met
       }
     });

     d3.event.stopPropagation();   //stop event 
     cb(i);     //stop update
   });
 });
}

// massage the data into presentable structures
d3.tsv("//static.redsift.io/blog/refviewer/institutions.txt", function(raw) {
 let lookup = {};  //create an object
 // create lookups table of display values
 raw.forEach(d => lookup[d['Institution name']] = d['Display']); //assign Institution name to Display 

  // from: https://www.staff.city.ac.uk/~jwo/refviewer/data/RAE2008In2014Format.txt
 d3.tsv("//static.redsift.io/blog/refviewer/RAE2008In2014Format.txt", function(raw) {
   let all = raw.map(d => ({    //map raw data
     l: lookup[d['Institution name']],  //Set Index to Institution name
     v: [parseInt(d['4*']), parseInt(d['3*']), parseInt(d['2*']), parseInt(d['1*']), parseInt(d['unclassified'])] //get each data and parse each as an Int
   }));
   
   // use the nest function to group
   let grouped = d3.nest()  
     .key(d => d.l)  //group by `l`
     .rollup(v => v.reduce(function(p, e) { //reduce array `v`    
       for (let i = 0; i < 5; ++i) {       
         p[i] = p[i] + e.v[i]   //add element to array p
       };
       return p; 
     }, [0, 0, 0, 0, 0]))
     .entries(all)  //dataset `all`
     .map(d => ({    //map data
       l: d.key,     //index data 
       v: d.value,   //value data
       t: d3.sum(d.value) //sum of value
     }));

   let normal = grouped.map(e => ({  
     l: e.l,  //set index (Institution)
     v: e.v.map(d => d / e.t) //set value dividing for stars
   }));

   let staff = 0; 
   let sort = 0;

   function update(animate) {  //update function for animation
     let data;      
     if (staff === 1) {     //toggle staff data 
       data = grouped.slice(); //slice grouped dataset to display staff
     } else {                   
       data = normal.slice();   //set using normal dataset
     }
     if (sort === 1) {         //toggle to sort = 1
       data = data.sort((a, b) => a.v[0] < b.v[0] ? 1 : a.v[0] > b.v[0] ? -1 : 0); //draw animation on selected button
     } else if (sort === 2) {   //toggle sort = 2
       data = data.sort((a, b) => a.v[0] + a.v[1] < b.v[0] + b.v[1] ? 1 : a.v[0] + a.v[1] > b.v[0] + b.v[1] ? -1 : 0); //draw animation on selected button
     }

     draw(data, animate); //generate chart  
   }
   
   // draw initial state
   update(false);   

   //function generating each chart
   makeOptions(['#staff-pct', '#staff-count'], (i) => (staff = i, update(true)));  
   makeOptions(['#order-inst', '#order-4', '#order-43'], (i) => (sort = i, update(true)));  
 });
});
              
            
!
999px

Console