<!DOCTYPE html>
<html>

  <head>
    <script data-require="d3@*" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
    <link href="https://fonts.googleapis.com/css?family=Ubuntu" rel="stylesheet">
    
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body>
    <svg id="chart-pos-only" class="chart"></svg>
  </body>

</html>
document.addEventListener('DOMContentLoaded', () => {
  var highlight = (d) => {
    if (d.lastMatchesAverage) { return 'last-matches-average'; }
    if (d.seasonAverage) { return 'season-average'; }
  };
  
  var extentWithPadding = (array, min, max) => {
    var extent = d3.extent(array);
    var range = Math.abs(extent[1] - extent[0]);
    return [extent[0] - (range * min), extent[1] + (range * max)]
  };
  
  var multiline = (text) => {
    text.each(function() {
      var data = this.__data__;
      var self = d3.select(this);
      
      // clear d3 generated axis text
      self.text('');
      
      // add multiple lines in tspans
      data.forEach((textLine, index) => {
        self.append('tspan')
          .attr('x', 0)
          .attr('dy', '1em')
          .text(textLine);
      });
    });
  };
  
  var render = (selector, settings, data) => {
    
    /**
     * 
     * settings
     */
     
    var size = settings.size;
    var margin = settings.margin || {};
    var padding = settings.padding || {};
    var format = settings.format || (d => `${ d }`);
    var defaultTagText = settings.defaultTagText || 'No convocado';
    
    var marginTop = margin.top || 0;
    var marginRight = margin.right || 0;
    var marginBottom = margin.bottom || 0;
    var marginLeft = margin.left || 0;
    
    var paddingMin = padding.min || 0;
    var paddingMax = padding.max || 0;
    
    var width = size.width - marginLeft - marginRight;
    var height = size.height - marginTop - marginBottom;
    
    
    var BAR_VALUE_PADDING = 4
    
    /**
     * declare scales
     */
     
    var x = d3.scaleBand()
      .domain(data.map(d => d.title))
      .range([0, width])
      .paddingOuter(1/3)
      .paddingInner(2/3);
      
    var y = d3.scaleLinear()
      .domain(extentWithPadding(data.map(d => d.value).concat(0), paddingMin, paddingMax))
      .range([height, 0]);
    
    /**
     * declare axis
     */
      
    var baseline = d3.axisBottom(x)
      .tickSize(0)
      .tickFormat('');
    
    var xAxis = d3.axisBottom(x)
      .tickSize(0);
      
    var yAxis = d3.axisLeft(y)
      .tickSize(0)
      .ticks(7)
      .tickFormat(format);
    
    /**
     * setup chart
     */
     
    var svg = d3.select(selector)
      .attr('width', size.width)
      .attr('height', size.height);
      
    var chart = svg.append('g')
      .attr('class', 'canvas')
      .attr('transform', `translate(${ marginLeft }, ${ marginTop })`);
    
    /**
     * add bars and bar values to chart
     */
     
    var bar = chart.selectAll('g')
      .data(data)
      .enter().append('g')
        .attr('class', d => `bar ${ d.value < 0 ? 'negative': 'positive' }`);
    
    bar.append('rect')
      .attr('x', d => x(d.title))
      .attr('y', d => y(Math.max(0, d.value)))
      .attr('width', x.bandwidth())
      .attr('height', d => Math.abs(y(d.value) - y(0)))
      .attr('class', highlight);
    
    var tag = bar.append('text')
      .attr('transform', d => `translate(${x(d.title) + x.bandwidth() / 2} ${y(d.value) - BAR_VALUE_PADDING}) rotate(-90)`)
      .text(d => d.value !== null ? format(d.value) : defaultTagText);
    
    /**
     * add axis to chart
     */
    
    chart.append('g')
      .attr('class', 'baseline')
      .attr('transform', `translate(0, ${ y(0) })`)
      .call(baseline);
    
    chart.append('g')
      .attr('class', 'axis x')
      .attr('transform', `translate(0, ${ height })`)
      .call(xAxis)
      .selectAll('.tick text')
        .call(multiline);
      
    chart.append('g')
      .attr('class', 'axis y')
      .call(yAxis);
  };

  
  d3.json('chart-pos-only.json', data => {
    var settings = {
      size: { width: 249, height: 80 },
      margin: { top: 5, left: 17, bottom: 15 },
      padding: { max: 0.2 },
      format: d => `${ d }`.replace('.', ',')
    };
    
    render('#chart-pos-only', settings, data);
  });
});
html  {
  zoom: 2
}

.chart {
  border: 1px solid deepskyblue;
  display: block;
  margin-bottom: 8px
}

.chart text {
  font: 5px sans-serif;
  font-family: 'Ubuntu', sans-serif;
  fill: #000;
}

.chart .bar text {
  text-anchor: start;
  dominant-baseline: middle;
}

.chart .bar {
  fill: #d8d8d8;
}

.chart .baseline path {
  stroke: #808080;
  stroke-opacity: 0.3;
  stroke-width: 0.5;
  shape-rendering: crispEdges;
}

.chart .axis path {
  stroke: #525252;
}

.chart rect.season-average {
  fill: #009400;
}

.chart rect.last-matches-average {
  fill: #808080;
}
D3 Bar Chart
============

Usage
-----

How to support vertical and default texts:

 1. Define a setting to specify the default text for null values
 2. Define a setting to specify the text direction
 3. Use CSS `transform`s for the text positioning to be able to rotate the text
    without dealing with offsets
 4. Add rotation, CSS class for vertical texts (including the property for
    center alignment) and the default text logic to the render

Notes
-----

### d3 v4

We are using D3 v4 while the examples are in v3 so there are some API
changes to be aware of

Additional info
---------------

### Tutorials used

  * [Let's make a bar chart](https://bost.ocks.org/mike/bar/)
  * [Let's make a bar chart, Part II](https://bost.ocks.org/mike/bar/2/)
  * [Let's make a bar chart, Part III](https://bost.ocks.org/mike/bar/3/)

### Examples used

  * [Bar chart with negative values](https://bl.ocks.org/mbostock/2368837)
  * [Wrapping Long Labels](https://bl.ocks.org/mbostock/7555321)
  * [Multiline text in axis](https://stackoverflow.com/a/31277568/1815446)
  * [Zero Ticks](https://bl.ocks.org/mbostock/2996766)
  * [Hide tick labels](https://stackoverflow.com/questions/19787925)
  * [Padding between axis and data](https://stackoverflow.com/questions/34888205)

### Read more

  * [D3 tutorials](https://github.com/d3/d3/wiki/Tutorials)
[  
    {
      "title": ["J26", "ESP-BET"], 
      "value": -1
    },
    {
      "title": ["J27", "ESP-BET"],
      "value": -5
    },
    {
      "title": ["J28", "ESP-BET"],
      "value": -2
    },
    {
      "title": ["J29", "ESP-BET"],
      "value": 5
    },
    {
      "title": ["MEDIA 4", "ÚLTIMOS PART."],
      "value": -1,
      "lastMatchesAverage": true
    },
    {
      "title": ["MEDIA", "TEMPORADA"],
      "value": 2,
      "seasonAverage": true
    }
]
[  
    {
      "title": ["J26", "ESP-BET"], 
      "value": 59
    },
    {
      "title": ["J27", "ESP-BET"],
      "value": 59
    },
    {
      "title": ["J28", "ESP-BET"],
      "value": 44.5
    },
    {
      "title": ["J29", "ESP-BET"],
      "value": 84
    },
    {
      "title": ["MEDIA 4", "ÚLTIMOS PART."],
      "value": 0,
      "lastMatchesAverage": true
    },
    {
      "title": ["MEDIA", "TEMPORADA"],
      "value": null,
      "seasonAverage": true
    }
]