Skip to content

Instantly share code, notes, and snippets.

@espetro
Last active July 3, 2017 22:27
Show Gist options
  • Save espetro/2ec652970229b5c244e83d8dbe04c8d4 to your computer and use it in GitHub Desktop.
Save espetro/2ec652970229b5c244e83d8dbe04c8d4 to your computer and use it in GitHub Desktop.
d3.slopegraph() | reusable slopegraph v3

Reusable Slopegraph

Feeding it with open data from Spain gender-based violence studies

Slopegraph: It's a chart that connects the same data point in two different data sets making it very easy to visualize the differences between those two sets.


Following Mike Bostocks' advice, this graphic adds code integrity plus enhanced reusability: thus, if drawing a big dataset with lots of lines, you can add a 'category' to classify and review in groups each datapoint, providing an uncluttered overview.

For the sake of simplicity, my fed data is previously structured using d3.nest() function, which allows for a tree key-value structure.

[
	{
		key: "iPad",
		values: {
			category: "Apple",
			count: {
				2013: 200000,
				2014: 250000,
				2015: 179560
			}
		}
	},
	{
		key: "iCloud",
		values: {
			category: "Apple",
			count: {
				2013: 35000,
				2014: 57500,
				2015: 87020
			}
		}
	},
	{
		key: "Nexus 5",
		values: {
			category: "Google",
			count: {
				2013: 120000,
				2014: 172000,
				2015: 48000
			}
		}
	}
]
 */

If you like this graphic, I encourage you to share it, modify it and publish it.

If you need any help you may find me here.


Things to do:
  1. Normalize object structure (by d3.nest() function)
  2. Add group interaction (if there's a category)
  3. Enable the ES6 "exports" way
  4. Fix width for long numeric values
  5. Only apply focus to single datapoints instead of category (if any) on hovering Is it really useful?
  6. Fix slopegraph height depending on the number of datapoints (perhaps height = data.length > 15 ? 80 : 60; then height = height * data.length)
  7. Update to last version, d3.v4
year community province reports
2009 Andalucía Almería 2604.0
2009 Andalucía Cádiz 3693.0
2009 Andalucía Córdoba 1335.0
2009 Andalucía Granada 3888.0
2009 Andalucía Huelva 2103.0
2009 Andalucía Jaén 1634.0
2009 Andalucía Málaga 5168.0
2009 Andalucía Sevilla 5718.0
2009 Aragón Huesca 371.0
2009 Aragón Teruel 155.0
2009 Aragón Zaragoza 2322.0
2009 Principado de Asturias Asturias 2373.0
2009 Islas Baleares Islas Baleares 4453.0
2009 Canarias Las Palmas 5163.0
2009 Canarias Santa Cruz 3819.0
2009 Cantabria Cantabria 1172.0
2009 Castilla y León Ávila 430.0
2009 Castilla y León Burgos 798.0
2009 Castilla y León León 839.0
2009 Castilla y León Palencia 284.0
2009 Castilla y León Salamanca 573.0
2009 Castilla y León Segovia 273.0
2009 Castilla y León Soria 171.0
2009 Castilla y León Valladolid 1429.0
2009 Castilla y León Zamora 293.0
2009 Castilla - La Mancha Albacete 959.0
2009 Castilla - La Mancha Ciudad Real 1080.0
2009 Castilla - La Mancha Cuenca 405.0
2009 Castilla - La Mancha Guadalajara 1354.0
2009 Castilla - La Mancha Toledo 1572.0
2009 Cataluña Barcelona 12262.0
2009 Cataluña Girona 2110.0
2009 Cataluña Lleida 1068.0
2009 Cataluña Tarragona 2778.0
2009 Comunitat Valenciana Alicante 7757.0
2009 Comunitat Valenciana Castellón 1663.0
2009 Comunitat Valenciana Valencia 9930.0
2009 Extremadura Badajoz 1114.0
2009 Extremadura Cáceres 588.0
2009 Galicia A Coruña 2400.0
2009 Galicia Lugo 581.0
2009 Galicia Ourense 619.0
2009 Galicia Pontevedra 2467.0
2009 Comunidad de Madrid Madrid 20863.0
2009 Región de Murcia Murcia 6085.0
2009 Comunidad Foral de Navarra Navarra 1236.0
2009 País Vasco Araba/Álava 740.0
2009 País Vasco Gipuzkoa 1072.0
2009 País Vasco Bizkaia 2246.0
2009 La Rioja La Rioja 834.0
2009 Ceuta Ceuta 334.0
2009 Melilla Melilla 361.0
2010 Andalucía Almería 2752.0
2010 Andalucía Cádiz 3803.0
2010 Andalucía Córdoba 1026.0
2010 Andalucía Granada 3683.0
2010 Andalucía Huelva 1712.0
2010 Andalucía Jaén 1531.0
2010 Andalucía Málaga 5517.0
2010 Andalucía Sevilla 7023.0
2010 Aragón Huesca 334.0
2010 Aragón Teruel 183.0
2010 Aragón Zaragoza 2056.0
2010 Principado de Asturias Asturias 2147.0
2010 Islas Baleares Islas Baleares 4231.0
2010 Canarias Las Palmas 4729.0
2010 Canarias Santa Cruz 3597.0
2010 Cantabria Cantabria 1231.0
2010 Castilla y León Ávila 445.0
2010 Castilla y León Burgos 653.0
2010 Castilla y León León 818.0
2010 Castilla y León Palencia 315.0
2010 Castilla y León Salamanca 504.0
2010 Castilla y León Segovia 231.0
2010 Castilla y León Soria 137.0
2010 Castilla y León Valladolid 1055.0
2010 Castilla y León Zamora 269.0
2010 Castilla - La Mancha Albacete 942.0
2010 Castilla - La Mancha Ciudad Real 1171.0
2010 Castilla - La Mancha Cuenca 447.0
2010 Castilla - La Mancha Guadalajara 1552.0
2010 Castilla - La Mancha Toledo 1553.0
2010 Cataluña Barcelona 12782.0
2010 Cataluña Girona 2175.0
2010 Cataluña Lleida 925.0
2010 Cataluña Tarragona 2984.0
2010 Comunitat Valenciana Alicante 7146.0
2010 Comunitat Valenciana Castellón 1908.0
2010 Comunitat Valenciana Valencia 10315.0
2010 Extremadura Badajoz 1137.0
2010 Extremadura Cáceres 641.0
2010 Galicia A Coruña 1997.0
2010 Galicia Lugo 502.0
2010 Galicia Ourense 685.0
2010 Galicia Pontevedra 2086.0
2010 Comunidad de Madrid Madrid 21195.0
2010 Región de Murcia Murcia 5513.0
2010 Comunidad Foral de Navarra Navarra 1470.0
2010 País Vasco Araba/Álava 763.0
2010 País Vasco Gipuzkoa 1025.0
2010 País Vasco Bizkaia 1903.0
2010 La Rioja La Rioja 660.0
2010 Ceuta Ceuta 273.0
2010 Melilla Melilla 373.0
2011 Andalucía Almería 2357.0
2011 Andalucía Cádiz 3427.0
2011 Andalucía Córdoba 1224.0
2011 Andalucía Granada 3347.0
2011 Andalucía Huelva 1466.0
2011 Andalucía Jaén 1422.0
2011 Andalucía Málaga 5981.0
2011 Andalucía Sevilla 7826.0
2011 Aragón Huesca 378.0
2011 Aragón Teruel 169.0
2011 Aragón Zaragoza 2845.0
2011 Principado de Asturias Asturias 2656.0
2011 Islas Baleares Islas Baleares 4417.0
2011 Canarias Las Palmas 4534.0
2011 Canarias Santa Cruz 3515.0
2011 Cantabria Cantabria 1168.0
2011 Castilla y León Ávila 332.0
2011 Castilla y León Burgos 676.0
2011 Castilla y León León 847.0
2011 Castilla y León Palencia 290.0
2011 Castilla y León Salamanca 469.0
2011 Castilla y León Segovia 209.0
2011 Castilla y León Soria 128.0
2011 Castilla y León Valladolid 1493.0
2011 Castilla y León Zamora 318.0
2011 Castilla - La Mancha Albacete 884.0
2011 Castilla - La Mancha Ciudad Real 1143.0
2011 Castilla - La Mancha Cuenca 607.0
2011 Castilla - La Mancha Guadalajara 979.0
2011 Castilla - La Mancha Toledo 1297.0
2011 Cataluña Barcelona 12861.0
2011 Cataluña Girona 2090.0
2011 Cataluña Lleida 904.0
2011 Cataluña Tarragona 2620.0
2011 Comunitat Valenciana Alicante 7287.0
2011 Comunitat Valenciana Castellón 1644.0
2011 Comunitat Valenciana Valencia 9609.0
2011 Extremadura Badajoz 1384.0
2011 Extremadura Cáceres 611.0
2011 Galicia A Coruña 1875.0
2011 Galicia Lugo 559.0
2011 Galicia Ourense 540.0
2011 Galicia Pontevedra 2179.0
2011 Comunidad de Madrid Madrid 20708.0
2011 Región de Murcia Murcia 5766.0
2011 Comunidad Foral de Navarra Navarra 1449.0
2011 País Vasco Araba/Álava 808.0
2011 País Vasco Gipuzkoa 1161.0
2011 País Vasco Bizkaia 2156.0
2011 La Rioja La Rioja 710.0
2011 Ceuta Ceuta 354.0
2011 Melilla Melilla 323.0
2012 Andalucía Almería 2123.0
2012 Andalucía Cádiz 3462.0
2012 Andalucía Córdoba 1174.0
2012 Andalucía Granada 3447.0
2012 Andalucía Huelva 1621.0
2012 Andalucía Jaén 1461.0
2012 Andalucía Málaga 6322.0
2012 Andalucía Sevilla 6581.0
2012 Aragón Huesca 308.0
2012 Aragón Teruel 127.0
2012 Aragón Zaragoza 2793.0
2012 Principado de Asturias Asturias 2431.0
2012 Islas Baleares Islas Baleares 4739.0
2012 Canarias Las Palmas 4230.0
2012 Canarias Santa Cruz 3471.0
2012 Cantabria Cantabria 1106.0
2012 Castilla y León Ávila 316.0
2012 Castilla y León Burgos 624.0
2012 Castilla y León León 705.0
2012 Castilla y León Palencia 250.0
2012 Castilla y León Salamanca 419.0
2012 Castilla y León Segovia 246.0
2012 Castilla y León Soria 113.0
2012 Castilla y León Valladolid 1155.0
2012 Castilla y León Zamora 304.0
2012 Castilla - La Mancha Albacete 704.0
2012 Castilla - La Mancha Ciudad Real 1138.0
2012 Castilla - La Mancha Cuenca 492.0
2012 Castilla - La Mancha Guadalajara 715.0
2012 Castilla - La Mancha Toledo 1262.0
2012 Cataluña Barcelona 12312.0
2012 Cataluña Girona 2135.0
2012 Cataluña Lleida 981.0
2012 Cataluña Tarragona 2349.0
2012 Comunitat Valenciana Alicante 7246.0
2012 Comunitat Valenciana Castellón 1730.0
2012 Comunitat Valenciana Valencia 8854.0
2012 Extremadura Badajoz 1343.0
2012 Extremadura Cáceres 624.0
2012 Galicia A Coruña 1633.0
2012 Galicia Lugo 479.0
2012 Galicia Ourense 466.0
2012 Galicia Pontevedra 2210.0
2012 Comunidad de Madrid Madrid 20935.0
2012 Región de Murcia Murcia 4796.0
2012 Comunidad Foral de Navarra Navarra 1333.0
2012 País Vasco Araba/Álava 734.0
2012 País Vasco Gipuzkoa 975.0
2012 País Vasco Bizkaia 2144.0
2012 La Rioja La Rioja 701.0
2012 Ceuta Ceuta 341.0
2012 Melilla Melilla 317.0
2013 Andalucía Almería 2079.0
2013 Andalucía Cádiz 3733.0
2013 Andalucía Córdoba 1627.0
2013 Andalucía Granada 3293.0
2013 Andalucía Huelva 1640.0
2013 Andalucía Jaén 1472.0
2013 Andalucía Málaga 6209.0
2013 Andalucía Sevilla 6423.0
2013 Aragón Huesca 312.0
2013 Aragón Teruel 130.0
2013 Aragón Zaragoza 2632.0
2013 Principado de Asturias Asturias 2438.0
2013 Islas Baleares Islas Baleares 4372.0
2013 Canarias Las Palmas 3958.0
2013 Canarias Santa Cruz 3153.0
2013 Cantabria Cantabria 1137.0
2013 Castilla y León Ávila 233.0
2013 Castilla y León Burgos 587.0
2013 Castilla y León León 822.0
2013 Castilla y León Palencia 330.0
2013 Castilla y León Salamanca 312.0
2013 Castilla y León Segovia 216.0
2013 Castilla y León Soria 104.0
2013 Castilla y León Valladolid 1302.0
2013 Castilla y León Zamora 247.0
2013 Castilla - La Mancha Albacete 810.0
2013 Castilla - La Mancha Ciudad Real 1130.0
2013 Castilla - La Mancha Cuenca 421.0
2013 Castilla - La Mancha Guadalajara 699.0
2013 Castilla - La Mancha Toledo 1299.0
2013 Cataluña Barcelona 12098.0
2013 Cataluña Girona 1883.0
2013 Cataluña Lleida 896.0
2013 Cataluña Tarragona 2272.0
2013 Comunitat Valenciana Alicante 6500.0
2013 Comunitat Valenciana Castellón 1650.0
2013 Comunitat Valenciana Valencia 8920.0
2013 Extremadura Badajoz 1287.0
2013 Extremadura Cáceres 647.0
2013 Galicia A Coruña 1772.0
2013 Galicia Lugo 532.0
2013 Galicia Ourense 494.0
2013 Galicia Pontevedra 2376.0
2013 Comunidad de Madrid Madrid 19506.0
2013 Región de Murcia Murcia 4656.0
2013 Comunidad Foral de Navarra Navarra 1219.0
2013 País Vasco Araba/Álava 751.0
2013 País Vasco Gipuzkoa 964.0
2013 País Vasco Bizkaia 2222.0
2013 La Rioja La Rioja 548.0
2013 Ceuta Ceuta 269.0
2013 Melilla Melilla 311.0
2014 Andalucía Almería 2084.0
2014 Andalucía Cádiz 3806.0
2014 Andalucía Córdoba 1653.0
2014 Andalucía Granada 3447.0
2014 Andalucía Huelva 1600.0
2014 Andalucía Jaén 1705.0
2014 Andalucía Málaga 5923.0
2014 Andalucía Sevilla 6692.0
2014 Aragón Huesca 293.0
2014 Aragón Teruel 157.0
2014 Aragón Zaragoza 2791.0
2014 Principado de Asturias Asturias 2486.0
2014 Islas Baleares Islas Baleares 4687.0
2014 Canarias Las Palmas 3845.0
2014 Canarias Santa Cruz 3091.0
2014 Cantabria Cantabria 1230.0
2014 Castilla y León Ávila 274.0
2014 Castilla y León Burgos 680.0
2014 Castilla y León León 920.0
2014 Castilla y León Palencia 181.0
2014 Castilla y León Salamanca 342.0
2014 Castilla y León Segovia 216.0
2014 Castilla y León Soria 142.0
2014 Castilla y León Valladolid 2062.0
2014 Castilla y León Zamora 315.0
2014 Castilla - La Mancha Albacete 808.0
2014 Castilla - La Mancha Ciudad Real 1149.0
2014 Castilla - La Mancha Cuenca 361.0
2014 Castilla - La Mancha Guadalajara 560.0
2014 Castilla - La Mancha Toledo 1405.0
2014 Cataluña Barcelona 12383.0
2014 Cataluña Girona 1794.0
2014 Cataluña Lleida 897.0
2014 Cataluña Tarragona 2268.0
2014 Comunitat Valenciana Alicante 6355.0
2014 Comunitat Valenciana Castellón 1511.0
2014 Comunitat Valenciana Valencia 8769.0
2014 Extremadura Badajoz 1302.0
2014 Extremadura Cáceres 631.0
2014 Galicia A Coruña 1928.0
2014 Galicia Lugo 545.0
2014 Galicia Ourense 506.0
2014 Galicia Pontevedra 2230.0
2014 Comunidad de Madrid Madrid 19270.0
2014 Región de Murcia Murcia 5039.0
2014 Comunidad Foral de Navarra Navarra 1328.0
2014 País Vasco Araba/Álava 663.0
2014 País Vasco Gipuzkoa 1084.0
2014 País Vasco Bizkaia 2243.0
2014 La Rioja La Rioja 549.0
2014 Ceuta Ceuta 239.0
2014 Melilla Melilla 303.0
2015 Andalucía Almería 2306.0
2015 Andalucía Cádiz 3766.0
2015 Andalucía Córdoba 1638.0
2015 Andalucía Granada 3486.0
2015 Andalucía Huelva 1616.0
2015 Andalucía Jaén 1482.0
2015 Andalucía Málaga 6177.0
2015 Andalucía Sevilla 7111.0
2015 Aragón Huesca 303.0
2015 Aragón Teruel 143.0
2015 Aragón Zaragoza 2189.0
2015 Principado de Asturias Asturias 2359.0
2015 Islas Baleares Islas Baleares 4658.0
2015 Canarias Las Palmas 3994.0
2015 Canarias Santa Cruz 3764.0
2015 Cantabria Cantabria 1327.0
2015 Castilla y León Ávila 283.0
2015 Castilla y León Burgos 621.0
2015 Castilla y León León 984.0
2015 Castilla y León Palencia 204.0
2015 Castilla y León Salamanca 284.0
2015 Castilla y León Segovia 224.0
2015 Castilla y León Soria 118.0
2015 Castilla y León Valladolid 1589.0
2015 Castilla y León Zamora 337.0
2015 Castilla - La Mancha Albacete 797.0
2015 Castilla - La Mancha Ciudad Real 1194.0
2015 Castilla - La Mancha Cuenca 437.0
2015 Castilla - La Mancha Guadalajara 669.0
2015 Castilla - La Mancha Toledo 1537.0
2015 Cataluña Barcelona 13457.0
2015 Cataluña Girona 1983.0
2015 Cataluña Lleida 904.0
2015 Cataluña Tarragona 2170.0
2015 Comunitat Valenciana Alicante 6760.0
2015 Comunitat Valenciana Castellón 1525.0
2015 Comunitat Valenciana Valencia 9109.0
2015 Extremadura Badajoz 1605.0
2015 Extremadura Cáceres 831.0
2015 Galicia A Coruña 1925.0
2015 Galicia Lugo 535.0
2015 Galicia Ourense 505.0
2015 Galicia Pontevedra 2245.0
2015 Comunidad de Madrid Madrid 18527.0
2015 Región de Murcia Murcia 5034.0
2015 Comunidad Foral de Navarra Navarra 1191.0
2015 País Vasco Araba/Álava 826.0
2015 País Vasco Gipuzkoa 1140.0
2015 País Vasco Bizkaia 2266.0
2015 La Rioja La Rioja 616.0
2015 Ceuta Ceuta 151.0
2015 Melilla Melilla 291.0
2016 Andalucía Almería 2675.0
2016 Andalucía Cádiz 3741.0
2016 Andalucía Córdoba 1511.0
2016 Andalucía Granada 3883.0
2016 Andalucía Huelva 1932.0
2016 Andalucía Jaén 1698.0
2016 Andalucía Málaga 6722.0
2016 Andalucía Sevilla 7250.0
2016 Aragón Huesca 287.0
2016 Aragón Teruel 191.0
2016 Aragón Zaragoza 2684.0
2016 Principado de Asturias Asturias 2747.0
2016 Islas Baleares Islas Baleares 5268.0
2016 Canarias Las Palmas 4803.0
2016 Canarias Santa Cruz 4601.0
2016 Cantabria Cantabria 1507.0
2016 Castilla y León Ávila 241.0
2016 Castilla y León Burgos 763.0
2016 Castilla y León León 1010.0
2016 Castilla y León Palencia 256.0
2016 Castilla y León Salamanca 313.0
2016 Castilla y León Segovia 259.0
2016 Castilla y León Soria 108.0
2016 Castilla y León Valladolid 1374.0
2016 Castilla y León Zamora 309.0
2016 Castilla - La Mancha Albacete 817.0
2016 Castilla - La Mancha Ciudad Real 1659.0
2016 Castilla - La Mancha Cuenca 369.0
2016 Castilla - La Mancha Guadalajara 667.0
2016 Castilla - La Mancha Toledo 1219.0
2016 Cataluña Barcelona 14382.0
2016 Cataluña Girona 2049.0
2016 Cataluña Lleida 1048.0
2016 Cataluña Tarragona 2067.0
2016 Comunitat Valenciana Alicante 7496.0
2016 Comunitat Valenciana Castellón 1632.0
2016 Comunitat Valenciana Valencia 10303.0
2016 Extremadura Badajoz 1755.0
2016 Extremadura Cáceres 878.0
2016 Galicia A Coruña 2193.0
2016 Galicia Lugo 691.0
2016 Galicia Ourense 589.0
2016 Galicia Pontevedra 2210.0
2016 Comunidad de Madrid Madrid 21535.0
2016 Región de Murcia Murcia 6302.0
2016 Comunidad Foral de Navarra Navarra 1502.0
2016 País Vasco Araba/Álava 884.0
2016 País Vasco Gipuzkoa 1307.0
2016 País Vasco Bizkaia 2573.0
2016 La Rioja La Rioja 690.0
2016 Ceuta Ceuta 174.0
2016 Melilla Melilla 411.0
"use strict";
// Using pre-loaded d3.v3 and lodash.js
// Feeding slopegraph.js
(function () {
// data & chart
var slopegraph;
var data;
// keys values from data to be applied
var keyValues = d3.range(2009, 2017).map(function (y) {
return y.toString();
});
// category to be applied - "key" if none
var category = "community";
// track any user interactions
var state = {
// have an array to mutate
keys: keyValues,
// toggle highlights
navToggle: [],
// track line selection
highlight: null
};
var formatNum = d3.format(".1f");
function type(d) {
d["reports"] = +d["reports"];
return d;
}
d3.csv("community-reports-0916.csv", type, function (error, csv) {
if (error) throw error;
var tree = d3.nest().key(function (dp) {
return dp["province"];
}).rollup(function (leaves) {
var count = {};
leaves.map(function (dp) {
return count[dp["year"]] = dp["reports"];
});
return {
// as every leaf is the same province over the years
"community": leaves[0]["community"],
"count": count
};
}).entries(csv);
// access data outside this callback
// data = tree;
// initial render chart
render(tree, keyValues, category);
// alternative navigation
navAlt(tree);
});
// Interactive filtering via category
function navAlt(data) {
var cat = _.uniq(data.map(function (d) {
return d.values[category];
}));
// checks if no category has been selected
cat = cat.length == 1 ? _.uniq(data.map(function (d) {
return d.key;
})) : cat;
// create array values
_.times(cat.length, function (n) {
return state.navToggle.push(true);
});
// Aqui tengo 19 comunidades autonomas - 19 indices
d3.select('#nav-alt').attr('margin', '-1px').append('ul').selectAll('li').data(cat).enter().append('li').attr('class', function (d, i) {
return "navAlt li-" + i;
}).on('click', function (d, i) {
if (!state.navToggle[i]) {
// update toggle state
state.navToggle[i] = true;
resetSelection();
state.highlight = null;
} else if (state.navToggle[i]) {
state.navToggle[i] = false;
// hover to highlight line category
highlightCat(i);
// highlight nav in relation to line
highlightNav(i);
// update state
state.highlight = i;
}
}).text(function (d) {
return d.toUpperCase();
});
}
// Render slopegraph chart
function render(data, keys) {
var category = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "key";
resetSelection();
// create chart
slopegraph = d3.slopegraph().margin({ top: 20, bottom: 20, left: 100, right: 100 }).gutter(25).keyValues(keys).category(category).on('_hover', function (d, i) {
// hover to highlight line
highlightLine(i);
// update state of selected highlight line
state.highlight = i;
});
// apply chart
d3.select('#slopegraph').datum(data).call(slopegraph);
// ensure highlight is maintained on update
if (!_.isNull(state.highlight)) {
d3.selectAll('.elm').style('opacity', 0.2);
d3.selectAll(".sel-" + state.highlight).style('opacity', 1);
highlightNav(state.highlight);
}
}
function highlightLine(i) {
d3.selectAll('.elm').transition().style('opacity', 0.15);
d3.selectAll(".sel-ind-" + i).transition().style('opacity', 1);
}
function highlightCat(cat) {
d3.selectAll('.elm').transition().style('opacity', 0.15);
d3.selectAll(".sel-" + cat).transition().style('opacity', 1);
}
function highlightNav(i) {
d3.selectAll('.navAlt').transition().style('opacity', 0.5);
d3.select(".li-" + i).transition().style('opacity', 1);
}
function resetSelection() {
d3.selectAll('.elm').transition().style('opacity', 1);
d3.selectAll('.navAlt').transition().style('opacity', 1);
}
// EXTRA: Config for bl.ocks viewer size
d3.select(self.frameElement).style('height', '800px');
})();
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>A glimpse to gender-based violence</title>
<meta name="author" content="Sundar Singh | eesur.com">
<meta name="author" content="Quino Terrasa | espetro.github.io/blog">
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Source+Code+Pro:400,600">
<link rel="stylesheet" href="main.css">
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js" charset="utf-8"></script>
</head>
<body>
<header>
<h1>Gender-based violence in Spain</h1>
<p>Total reports per region from 2009 to 2016.</p>
<nav id='filter'></nav>
<nav id='nav-alt'></nav>
</header>
<section id="slopegraph" class="slope"></section>
<!-- *************** start js/d3 code ***************** -->
<!-- reusable slopegraph -->
<script src="slopegraph.js"></script>
<!-- render slopegraph chart -->
<script src="draw.js"></script>
</body>
</html>
body {
position: relative;
color: #130C0E;
background-color: #fefefe;
padding: 5px 20px;
font-family: "Source Code Pro", Consolas, monaco, monospace;
line-height: 1.5;
font-weight: 400;
}
p {
padding-top: 0;
margin-top: 0;
font-size: 13px;
max-width: 600px;
}
h1 {
font-size: 18px;
font-weight: 400;
margin-bottom: 0;
}
#slopegraph {
min-height: 400px;
/*padding: 20px 0;*/
}
.slope {
display: inline-block;
width: 400px;
}
.labels {
font-size: 11px;
}
#nav-alt ul, #filter ul {
color: #130C0E;
font-size: 11px;
letter-spacing: 1px;
list-style: none;
padding: 0;
margin: 10px 0 10px 0;
}
#nav-alt ul li, #filter ul li {
display: inline-block;
padding: 2px 8px;
margin-right: 1px;
background: #A4CD39;
cursor: pointer;
}
#filter ul li {
display: table-cell;
}
#nav-alt ul li:hover, #filter ul li:hover {
background: #7AC143;
}
text.s-title {
fill: #7AC143;
letter-spacing: 2px;
font-size: 11px;
}
// Using pre-loaded d3.v3 and lodash.js
// Feeding slopegraph.js
(() => {
// data & chart
var slopegraph;
var data;
// keys values from data to be applied
const keyValues = d3.range(2009,2017).map(y => y.toString());
// category to be applied - "key" if none
const category = "community";
// track any user interactions
let state = {
// have an array to mutate
keys: keyValues,
// track filtered sets
filter: [],
// toggle highlights
navToggle: [],
// track line selection
highlight: null
};
const formatNum = d3.format(".1f");
function type(d) {
d["reports"] = +d["reports"];
return d;
}
d3.csv("community-reports-0916.csv", type, (error, csv) => {
if(error) throw error;
let tree = d3.nest()
.key(dp => dp["province"])
.rollup(leaves => {
let count = {};
leaves.map(dp => count[dp["year"]] = dp["reports"]);
return {
// as every leaf is the same province over the years
"community": leaves[0]["community"],
"count": count
};
})
.entries(csv);
// access data outside this callback
// data = tree;
// initial render chart
render(tree, keyValues, category);
// alternative navigation
navAlt(tree);
});
// Interactive filtering via category
function navAlt(data) {
let cat = _.uniq(data.map(d => d.values[category]));
// checks if no category has been selected
cat = cat.length == 1 ? _.uniq(data.map(d => d.key)) : cat;
// create array values
_.times(cat.length, n => state.navToggle.push(true));
// Aqui tengo 19 comunidades autonomas - 19 indices
d3.select('#nav-alt')
.attr('margin', '-1px')
.append('ul')
.selectAll('li')
.data(cat)
.enter()
.append('li')
.attr('class', (d, i) => `navAlt li-${i}`)
.on('click', (d, i) => {
if (!state.navToggle[i]) {
// update toggle state
state.navToggle[i] = true;
resetSelection();
state.highlight = null;
} else if (state.navToggle[i]) {
state.navToggle[i] = false;
// hover to highlight line category
highlightCat(i);
// highlight nav in relation to line
highlightNav(i);
// update state
state.highlight = i;
}
})
.text(d => d.toUpperCase());
}
// Render slopegraph chart
function render(data, keys, category = "key") {
resetSelection();
// create chart
slopegraph = d3.slopegraph()
.margin({top: 20, bottom: 20, left: 100, right: 100})
.gutter(25)
.keyValues(keys)
.category(category)
.on('_hover', (d, i) => {
// hover to highlight line
highlightLine(i);
// update state of selected highlight line
state.highlight = i;
});
// apply chart
d3.select('#slopegraph')
.datum(data)
.call(slopegraph);
// ensure highlight is maintained on update
if (!_.isNull(state.highlight)) {
d3.selectAll('.elm').style('opacity', 0.2);
d3.selectAll(`.sel-${state.highlight}`).style('opacity', 1);
highlightNav(state.highlight);
}
}
function highlightLine(i) {
d3.selectAll('.elm').transition().style('opacity', 0.2);
d3.selectAll(`.sel-ind-${i}`).transition().style('opacity', 1);
}
function highlightCat(cat) {
d3.selectAll('.elm').transition().style('opacity', 0.2);
d3.selectAll(`.sel-${cat}`).transition().style('opacity', 1);
}
function highlightNav(i) {
d3.selectAll('.navAlt').transition().style('opacity', 0.5);
d3.select(`.li-${i}`).transition().style('opacity', 1);
}
function resetSelection() {
d3.selectAll('.elm').transition().style('opacity', 1);
d3.selectAll('.navAlt').transition().style('opacity', 1);
}
// EXTRA: Config for bl.ocks viewer size
d3.select(self.frameElement).style('height', '800px');
})();
// *****************************************
// reusable multiple slopegraph chart
// *****************************************
(() => {
d3.slopegraph = function module() {
// default variables
let w = 150,
h = 70,
margin = {top: 40, bottom: 40, left: 80, right: 80},
gutter = 50,
strokeColour = 'black',
// key data values (in order)
keyValues = [],
// category (default for key)
type = "key",
// array with total number of categories
categories = [],
format = d3.format(''),
sets = 0;
var dispatch = d3.dispatch('_hover');
var svg, yScale;
function exports(_selection) {
_selection.each(data => {
let allValues = [];
// get number of categories
categories = _.uniq(data.map(d => d.values[type]));
// checks if no category has been selected
categories = categories.length == 1 ? _.uniq(data.map(d => d.key)) : categories;
// get dataset lengths
data.forEach(d => {
_.times(keyValues.length, n => {
let year = keyValues[n];
allValues.push(d.values.count[year]);
});
});
// create max value so scale is consistent
let maxValue = _.max(allValues);
// adapt the size against number of sets
w = w * keyValues.length;
h = h * categories.length;
// have reference for number of sets
sets = keyValues.length -1;
// use same scale for both sides
yScale = d3.scale.linear()
.domain([0, maxValue])
.range([h - margin.top, margin.bottom]);
// clean start
// WARN: es2015 babel wrongly transpiles 'this'
d3.select(this).select('svg').remove();
svg = d3.select(this).append('svg')
.attr("width", w)
.attr("height", h);
render(data, 0);
});
}
// recursive function to apply each set
// then the start and end labels (as only needed once)
function render (data, n) {
if (n < keyValues.length-1 ) {
lines(data, n);
middleLabels(data, n);
return render(data, n+1);
} else {
startLabels(data);
endLabels(data);
return n;
}
}
// render connecting lines
function lines(data, n) {
/*
s-line-{n} toggles column values (1st column, 2nd column, ...)
sel-${cat.indexOf(d)} toggles category values (cat1, cat2, ...)
sel-ind-${i} toggles line values (obj1, obj2, ...)
*/
let lines = svg.selectAll(`.s-line-${n}`)
.data(data)
.enter().append('line');
lines.attr({
x1: () => {
if (n === 0) {
return margin.left;
} else {
return ((w / sets) * n) + margin.left/2;
}
},
y1: d => yScale(d.values.count[keyValues[n]]),
x2: () => {
if (n === sets-1) {
return w - margin.right;
} else {
return ((w / sets) * (n+1)) - gutter;
}
},
y2: d => yScale(d.values.count[keyValues[n+1]]),
stroke: strokeColour,
'stroke-width': 1,
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH
class: (d,i) => `elm s-line-${n} sel-${categories.indexOf(d.values[type])} sel-ind-${i}`
})
.on('mouseover', dispatch._hover);
}
// middle labels in-between sets
function middleLabels(data, n) {
if (n !== sets-1) {
var middleLabels = svg.selectAll(`.m-labels-${n}`)
.data(data);
middleLabels.enter().append('text')
.attr({
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH
class: (d,i) => `labels m-labels-${n} elm sel-${categories.indexOf(d.values[type])} sel-ind-${i}`,
x: ((w / sets) * (n+1)) + 15,
y: d => yScale(d.values.count[keyValues[n+1]]) + 4,
})
.text(d => format(d.values.count[keyValues[n+1]]))
.style('text-anchor','end')
.on('mouseover', dispatch._hover);
// title
svg.append('text')
.attr({
class: 's-title',
x: ((w / sets) * (n+1)),
y: margin.top/2
})
.text(`${keyValues[n+1]} ↓`)
.style('text-anchor','end');
}
}
// start labels applied left of chart sets
function startLabels(data) {
var startLabels = svg.selectAll('.l-labels')
.data(data);
startLabels.enter().append('text')
.attr({
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH
class: (d,i) => `labels l-labels elm sel-${categories.indexOf(d.values[type])} sel-ind-${i}`,
x: margin.left - 3,
y: d => yScale(d.values.count[keyValues[0]]) + 4
})
.text(d => `${d.key} ${format(d.values.count[keyValues[0]])}`)
.style('text-anchor','end')
.on('mouseover', dispatch._hover);
// title
svg.append('text')
.attr({
class: 's-title',
x: margin.left - 3,
y: margin.top/2
})
.text(`${keyValues[0]} ↓`)
.style('text-anchor','end');
}
// end labels applied right of chart sets
function endLabels(data) {
// let cat = _.uniq(data.map(d => d.values["comunidad"]));
var end = keyValues.length-1;
var endLabels = svg.selectAll('r.labels')
.data(data);
endLabels.enter().append('text')
.attr({
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH
class: (d,i) => `labels r-labels elm sel-${categories.indexOf(d.values[type])} sel-ind-${i}`,
x: w - margin.right + 3,
y: d => yScale(d.values.count[keyValues[end]]) + 4
})
.text(d => `${d.key} ${format(d.values.count[keyValues[end]])}`)
.style('text-anchor','start')
.on('mouseover', dispatch._hover);
// title
svg.append('text')
.attr({
class: 's-title',
x: w - margin.right + 3,
y: margin.top/2
})
.text(`↓ ${keyValues[end]}`)
.style('text-anchor','start');
}
// getter/setters for overrides
exports.w = function(value) {
if (!arguments.length) return w;
w = value;
return this;
};
exports.h = function(value) {
if (!arguments.length) return h;
h = value;
return this;
};
exports.margin = function(value) {
if (!arguments.length) return margin;
margin = value;
return this;
};
exports.gutter = function(value) {
if (!arguments.length) return gutter;
gutter = value;
return this;
};
exports.format = function(value) {
if (!arguments.length) return format;
format = value;
return this;
};
exports.strokeColour = function(value) {
if (!arguments.length) return strokeColour;
strokeColour = value;
return this;
};
exports.keyValues = function(value) {
if (!arguments.length) return keyValues;
keyValues = value;
return this;
};
exports.category = function(value) {
if(!arguments.length) return category;
type = value;
return this;
}
d3.rebind(exports, dispatch, 'on');
return exports;
};
})();
'use strict';
// *****************************************
// reusable multiple slopegraph chart
// *****************************************
(function () {
d3.slopegraph = function module() {
// default variables
var w = 150,
h = 70,
margin = { top: 40, bottom: 40, left: 80, right: 80 },
gutter = 50,
strokeColour = 'black',
// key data values (in order)
keyValues = [],
// category (default for key)
type = "key",
// array with total number of categories
categories = [],
format = d3.format(''),
sets = 0;
var dispatch = d3.dispatch('_hover');
var svg, yScale;
function exports(_selection) {
var _this = this;
_selection.each(function (data) {
var allValues = [];
// get number of categories
categories = _.uniq(data.map(function (d) {
return d.values[type];
}));
// checks if no category has been selected
categories = categories.length == 1 ? _.uniq(data.map(function (d) {
return d.key;
})) : categories;
// get dataset lengths
data.forEach(function (d) {
_.times(keyValues.length, function (n) {
var year = keyValues[n];
allValues.push(d.values.count[year]);
});
});
// create max value so scale is consistent
var maxValue = _.max(allValues);
// adapt the size against number of sets
w = w * keyValues.length;
h = h * categories.length;
// have reference for number of sets
sets = keyValues.length - 1;
// use same scale for both sides
yScale = d3.scale.linear().domain([0, maxValue]).range([h - margin.top, margin.bottom]);
// clean start
// WARN: es2015 babel wrongly transpiles 'this'
d3.select(this).select('svg').remove();
svg = d3.select(this).append('svg').attr("width", w).attr("height", h);
render(data, 0);
});
}
// recursive function to apply each set
// then the start and end labels (as only needed once)
function render(data, n) {
if (n < keyValues.length - 1) {
lines(data, n);
middleLabels(data, n);
return render(data, n + 1);
} else {
startLabels(data);
endLabels(data);
return n;
}
}
// render connecting lines
function lines(data, n) {
/*
s-line-{n} toggles column values (1st column, 2nd column, ...)
sel-${cat.indexOf(d)} toggles category values (cat1, cat2, ...)
sel-ind-${i} toggles line values (obj1, obj2, ...)
*/
var lines = svg.selectAll('.s-line-' + n).data(data).enter().append('line');
lines.attr({
x1: function x1() {
if (n === 0) {
return margin.left;
} else {
return w / sets * n + margin.left / 2;
}
},
y1: function y1(d) {
return yScale(d.values.count[keyValues[n]]);
},
x2: function x2() {
if (n === sets - 1) {
return w - margin.right;
} else {
return w / sets * (n + 1) - gutter;
}
},
y2: function y2(d) {
return yScale(d.values.count[keyValues[n + 1]]);
},
stroke: strokeColour,
'stroke-width': 1,
class: function _class(d, i) {
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH
return 'elm s-line-' + n + ' sel-' + categories.indexOf(d.values[type]) + ' sel-ind-' + i;
}
}).on('mouseover', dispatch._hover);
}
// middle labels in-between sets
function middleLabels(data, n) {
if (n !== sets - 1) {
var middleLabels = svg.selectAll('.m-labels-' + n).data(data);
middleLabels.enter().append('text').attr({
class: function _class(d, i) {
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH
return 'labels m-labels-' + n + ' elm sel-' + categories.indexOf(d.values[type]) + ' sel-ind-' + i;
},
x: w / sets * (n + 1) + 15,
y: function y(d) {
return yScale(d.values.count[keyValues[n + 1]]) + 4;
}
}).text(function (d) {
return format(d.values.count[keyValues[n + 1]]);
}).style('text-anchor', 'middle').on('mouseover', dispatch._hover);
// title
svg.append('text').attr({
class: 's-title',
x: w / sets * (n + 1),
y: margin.top / 2
}).text(keyValues[n + 1] + ' \u2193').style('text-anchor', 'middle');
}
}
// start labels applied left of chart sets
function startLabels(data) {
var startLabels = svg.selectAll('.l-labels').data(data);
startLabels.enter().append('text').attr({
class: function _class(d, i) {
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH
return 'labels l-labels elm sel-' + categories.indexOf(d.values[type]) + ' sel-ind-' + i;
},
x: margin.left - 3,
y: function y(d) {
return yScale(d.values.count[keyValues[0]]) + 4;
}
}).text(function (d) {
return d.key + ' ' + format(d.values.count[keyValues[0]]);
}).style('text-anchor', 'end').on('mouseover', dispatch._hover);
// title
svg.append('text').attr({
class: 's-title',
x: margin.left - 3,
y: margin.top / 2
}).text(keyValues[0] + ' \u2193').style('text-anchor', 'end');
}
// end labels applied right of chart sets
function endLabels(data) {
var end = keyValues.length - 1;
var endLabels = svg.selectAll('r.labels').data(data);
endLabels.enter().append('text').attr({
class: function _class(d, i) {
// fix: IF NO CATEGORY IS USED, 'categories.indexOf()' WILL CRASH
return 'labels r-labels elm sel-' + categories.indexOf(d.values[type]) + ' sel-ind-' + i;
},
x: w - margin.right + 3,
y: function y(d) {
return yScale(d.values.count[keyValues[end]]) + 4;
}
}).text(function (d) {
return d.key + ' ' + format(d.values.count[keyValues[end]]);
}).style('text-anchor', 'start').on('mouseover', dispatch._hover);
// title
svg.append('text').attr({
class: 's-title',
x: w - margin.right + 3,
y: margin.top / 2
}).text('\u2193 ' + keyValues[end]).style('text-anchor', 'start');
}
// getter/setters for overrides
exports.w = function (value) {
if (!arguments.length) return w;
w = value;
return this;
};
exports.h = function (value) {
if (!arguments.length) return h;
h = value;
return this;
};
exports.margin = function (value) {
if (!arguments.length) return margin;
margin = value;
return this;
};
exports.gutter = function (value) {
if (!arguments.length) return gutter;
gutter = value;
return this;
};
exports.format = function (value) {
if (!arguments.length) return format;
format = value;
return this;
};
exports.strokeColour = function (value) {
if (!arguments.length) return strokeColour;
strokeColour = value;
return this;
};
exports.keyValues = function (value) {
if (!arguments.length) return keyValues;
keyValues = value;
return this;
};
exports.category = function (value) {
if (!arguments.length) return category;
type = value;
return this;
};
d3.rebind(exports, dispatch, 'on');
return exports;
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment