block by jfsiii 55f1c89944cd96718bdccc8260aeea4e

responsive bar chart

Full Screen

a responsive bar chart. open in a new window ↗️
to enjoy the buttery responsiveness

built with window resize events from W

inspired by a conversation with @robcrock

an iteration on the bl.ock responsive d3 from benheubl

if you can figure out how to fix the selections and get v4.html to render the bars, that would be awesome. here are the most relevant d3 v4 docs I could find. when you get it, do shout at me on twitter @micahstubbs

forked from micahstubbs‘s block: responsive bar chart

index.html

<!DOCTYPE html>
<head>
<meta charset='utf-8'>
<script src='w.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.10.3/babel.min.js'></script>
</head>
<body>
<script lang='babel' type='text/babel'>
  const maxWidth = 5120;
  const maxHeight = 2880;
  const margin = {
    top: 10,
    right: 10,
    bottom: 25,
    left: 35
  };

  const xVariable = 'letter';
  const yVariable = 'frequency';
  
  const faintGray = '#ededed';

  let xScale;
  let yScale;
  let xAxis;
  let yAxis;

  d3.select('body')
    .style({
      margin: 0,
      position: 'fixed',
      top: 0,
      right: 0,
      bottom: 0,
      left: 0
    })

  const svg = d3.select('body').append('svg')
    .attr('width', '100%')
    .attr('height', '100%');

  const chartArea = svg.append('g')
    .classed('chartArea', true)
    .attr('transform', `translate(${margin.left}, ${margin.top})`)

  const barGroup = chartArea.append('g')
    .classed('bars', true);

  const xAxisG = chartArea.append('g')
    .classed('axis', true)
    .classed('x', true);

  const yAxisG = chartArea.append('g')
    .classed('axis', true)
    .classed('y', true);

  function type(d) {
    // coerce to a Number from a String (or anything)
    d[yVariable] = Number(d[yVariable]);
    return d;
  }

  d3.csv('data.csv', type, (error, data) => {
    console.log('data', data);

    function initChart() {
      const width = 100;
      const height = 50;

      // Initialise scales
      xScale = d3.scale.ordinal()
        .domain(data.map(d => d[xVariable]));

      yScale = d3.scale.linear()
        .domain([0, d3.max(data.map(d => d[yVariable]))]);

      // Build the x-axis
      xAxis = d3.svg.axis()
        .scale(xScale)
        .orient('bottom');

      // Build the y-axis
      yAxis = d3.svg.axis()
        .scale(yScale)
        .orient('left');
    }

    function updateScales() {
      const detectedWidth = window.innerWidth
        || document.documentElement.clientWidth
        || document.body.clientWidth;

      const detectedHeight = window.innerHeight
        || document.documentElement.clientHeight
        || document.body.clientHeight;

      const newWidth = d3.min([detectedWidth, maxWidth]) - margin.left - margin.right;
      const newHeight = d3.min([detectedHeight, maxHeight]) - margin.top - margin.bottom;

      xScale.rangeRoundBands([0, newWidth], 0.1);
      yScale.range([newHeight, 0]);
    }

    function updateAxes(firstCall) {
      const detectedHeight = window.innerHeight
        || document.documentElement.clientHeight
        || document.body.clientHeight;

      const newHeight = d3.min([detectedHeight, maxHeight]);

      // position the xAxisG before the transition the first time
      if (typeof firstCall !== 'undefined') {
        xAxisG
          .attr('transform', `translate(0, ${newHeight - margin.top - margin.bottom})`);
      }

      xAxisG
        .transition()
          .duration(0)
          .attr('transform', `translate(0, ${newHeight - margin.top - margin.bottom})`)
          .call(xAxis);

      yAxisG
        .transition()
          .duration(0)
          .call(yAxis);

      // style the axes
      d3.selectAll('.axis text')
        .style({
          'font-family': 'sans-serif',
          'font-size': '10px'
        })

      d3.selectAll('.axis path')
        .style({
          fill: 'none',
          stroke: '#161616'
        })

      d3.selectAll('.axis line')
        .style('stroke', 'black');
    }

    function updateBars() {
      const updateSelection = barGroup.selectAll('rect')
        .data(data);

      updateSelection.enter()
        .append('rect')
          .classed('rect', true)
          .style('fill', faintGray);

      updateSelection.exit()
        .remove();

      updateSelection
        .transition()
          .duration(0)
          .attr('x', function(d) {return xScale(d[xVariable]);})
          .attr('width', xScale.rangeBand)
          .attr('y', d => yScale(d[yVariable]))
          .attr('height', d => yScale(0) - yScale(d[yVariable]));

      updateSelection
        .on('mouseover', function () {
          d3.select(this)
            .style({
              'fill': 'steelblue',
              'fill-opacity': 0.6
            });
        })
        .on('mouseout', function () {
          d3.select(this)
            .style({
              'fill': faintGray,
              'fill-opacity': 1
            });
        });
    }

    function update(firstCall) {
      updateScales();
      updateAxes(firstCall);
      updateBars();
    }

    function initEvents() {
      // Set up event handler for resizes
      W.addListener(update);
    }

    initChart();
    update(true); // set parameter `firstCall` to true this once
    initEvents();
  });
</script>
</body>












data.csv

letter,frequency
A,0.08167
B,0.01492
C,0.02782
D,0.04253
E,0.12702
F,0.02288
G,0.02015
H,0.06094
I,0.06966
J,0.00153
K,0.00772
L,0.04025
M,0.02406
N,0.06749
O,0.07507
P,0.01929
Q,0.00095
R,0.05987
S,0.06327
T,0.09056
U,0.02758
V,0.00978
W,0.02360
X,0.00150
Y,0.01974
Z,0.00074

v4.html

<!DOCTYPE html>
<head>
<meta charset='utf-8'>
<script src='w.js'></script>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script src='https://d3js.org/d3-selection-multi.v0.4.min.js'></script>
<script src='https://npmcdn.com/babel-core@5.8.34/browser.min.js'></script>
</head>
<body>
<script>
  const maxWidth = 5120;
  const maxHeight = 2880;
  const margin = {
    top: 10,
    right: 10,
    bottom: 25,
    left: 35
  };

  const xVariable = 'letter';
  const yVariable = 'frequency';

  const faintGray = '#ededed';

  let xScale;
  let yScale;
  let xAxis;
  let yAxis;

  d3.select('body')
    .styles({
      margin: 0,
      position: 'fixed',
      top: 0,
      right: 0,
      bottom: 0,
      left: 0
    })

  const svg = d3.select('body').append('svg')
    .attr('width', '100%')
    .attr('height', '100%');

  const chartArea = svg.append('g')
    .classed('chartArea', true)
    .attr('transform', `translate(${margin.left}, ${margin.top})`)

  const barGroup = chartArea.append('g')
    .classed('bars', true);

  const xAxisG = chartArea.append('g')
    .classed('axis', true)
    .classed('x', true);

  const yAxisG = chartArea.append('g')
    .classed('axis', true)
    .classed('y', true);

  function type(d) {
    // coerce to a Number from a String (or anything)
    d[yVariable] = Number(d[yVariable]);
    return d;
  }

  d3.csv('data.csv', type, (error, data) => {
    console.log('data', data);

    function initChart() {
      const width = 100;
      const height = 50;

      // Initialise scales
      xScale = d3.scaleBand()
        .domain(data.map(d => d[xVariable]));

      yScale = d3.scaleLinear()
        .domain([0, d3.max(data.map(d => d[yVariable]))]);

      // Build the x-axis
      xAxis = d3.axisBottom()
        .scale(xScale);

      // Build the y-axis
      yAxis = d3.axisLeft()
        .scale(yScale);
    }

    function updateScales() {
      const newWidth = d3.min([W.getViewportWidth(), maxWidth]) - margin.left - margin.right;
      const newHeight = d3.min([W.getViewportHeight(), maxHeight]) - margin.top - margin.bottom;

      xScale
        .range([0, newWidth])
        .paddingInner(0.1)
        .bandwidth(10);

      yScale.range([newHeight, 0]);
    }

    function updateAxes(firstCall) {
      const newHeight = d3.min([W.getViewportHeight(), maxHeight]);

      // position the xAxisG before the transition the first time
      if (typeof firstCall !== 'undefined') {
        xAxisG
          .attr('transform', `translate(0, ${newHeight - margin.top - margin.bottom})`);
      }

      xAxisG
        .transition()
          .duration(150)
          .attr('transform', `translate(0, ${newHeight - margin.top - margin.bottom})`)
          .call(xAxis);

      yAxisG
        .transition()
          .duration(150)
          .call(yAxis);

      // style the axes
      d3.selectAll('.axis text')
        .styles({
          'font-family': 'sans-serif',
          'font-size': '10px'
        })

      d3.selectAll('.axis path')
        .styles({
          fill: 'none',
          stroke: '#161616'
        })

      d3.selectAll('.axis line')
        .style('stroke', 'black');
    }

    function updateBars() {
      const updateSelection = barGroup.selectAll('rect')
        .data(data);

      const enterSelection = updateSelection.enter()
        .append('rect')
          .classed('rect', true)
          .style('fill', faintGray);

      updateSelection.exit()
        .remove();

      enterSelection
        .merge(updateSelection)
        .transition()
          .duration(150)
          .attr('x', function(d) {return xScale(d[xVariable]);})
          .attr('width', xScale.bandwidth)
          .attr('y', d => yScale(d[yVariable]))
          .attr('height', d => yScale(0) - yScale(d[yVariable]));

      updateSelection
        .on('mouseover', function () {
          d3.select(this)
            .styles({
              'fill': 'steelblue',
              'fill-opacity': 0.6
            });
        })
        .on('mouseout', function () {
          d3.select(this)
            .styles({
              'fill': faintGray,
              'fill-opacity': 1
            });
        });
    }

    function update(firstCall) {
      updateScales();
      updateAxes(firstCall);
      updateBars();
    }

    function initEvents() {
      // Set up event handler for resizes
      W.addListener(update);
    }

    initChart();
    update(true); // set parameter `firstCall` to true this once
    initEvents();
  });
</script>
</body>

w.js

;(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.W = factory();
  }
}(this, function() {
/*! W 1.6.3 (https://github.com/pyrsmk/W) */

// Prepare
var listeners = [],
	resize_trigger = false,
	orientationchange = false,
	orientationchange_trigger = false;

// Catch window resize event
if(window.addEventListener) {
	if('onorientationchange' in window) {
		orientationchange = true;
		window.addEventListener('orientationchange', function() {
			orientationchange_trigger = true;
		}, false);
	}
	window.addEventListener('resize', function() {
		resize_trigger = true;
	}, false);
}
else{
	window.attachEvent('onresize', function() {
		resize_trigger = true;
	});
}

// Verify resizes every 10ms
setInterval(function() {
	var trigger = false;
	if(orientationchange) {
		if(orientationchange_trigger && resize_trigger) {
			trigger = true;
		}
	}
	else if(resize_trigger) {
		trigger = true;
	}
	if(trigger && document.documentElement.clientWidth) {
		orientationchange_trigger = false;
		resize_trigger = false;
		for(var i=0, j=listeners.length; i<j; ++i) {
			listeners[i].func();
		}
	}
}, 10);

// Get screen orientation
function getOrientation() {
	var landscape;
	if('orientation' in window) {
		// Mobiles
		var orientation = window.orientation;
		landscape = (orientation == 90 || orientation == -90);
	}
	else {
		// Desktop browsers
		landscape = window.innerWidth > window.innerHeight;
	}
	return landscape ? 'landscape' : 'portrait';
}

// Viewport resolution detection
function detectViewport(absolute) {
	// Detect screen size
	var screen_width = screen.width,
		screen_height = screen.height;
	if(getOrientation() == 'landscape' && screen_width < screen_height) {
		screen_width = screen.height;
		screen_height = screen.width;
	}
	// Absolute mode
	if(absolute) {
		return {
			width: screen_width,
			height: screen_height
		};
	}
	// Relative mode
	else {
		var w = window.innerWidth,
			h = window.innerHeight;
		if(!w || !h || w > screen_width || h > screen_height || w == 980) {
			w = window.outerWidth;
			h = window.outerHeight;
		}
		if(!w || !h || w > screen_width || h > screen_height) {
			w = screen.availWidth;
			h = screen.availHeight;
		}
		return {width: w, height: h};
	}
}

// Define W object
var W = {
	getViewportDimensions: function(absolute) {
		return detectViewport(absolute);
	},
	getViewportWidth: function(absolute) {
		return detectViewport(absolute).width;
	},
	getViewportHeight: function(absolute) {
		return detectViewport(absolute).height;
	},
	getOrientation: function() {
		return getOrientation();
	},
	addListener: function(func, key) {
		listeners.push({
			func: func,
			key: key
		});
		return func;
	},
	removeListener: function(key) {
		for(var i=0, j=listeners.length; i<j; ++i) {
			if(listeners[i].key == key) {
				listeners.splice(i, 1);
				break;
			}
		}
	},
	clearListeners: function() {
		listeners = [];
	},
	trigger: function(key) {
		for(var i=0, j=listeners.length; i<j; ++i) {
			if(typeof key == 'undefined' || listeners[i].key == key) {
				listeners[i].func();
			}
		}
	}
};

return W;
}));