<!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
}
]