ksh
Nagyvadak, aprovadak
Szamlalas februar vege
const | |
title = {height: 30, text: 'Vadállomány - Kilőtt vad [ezer db]'}, | |
legend = {height: 50}, | |
margin = {top: 20, right: 20, bottom: 30, left: 40}, | |
bar = {marginRatio: 0.8, paddingRatio: 0.1}, | |
width = document.body.clientWidth - margin.left - margin.right, | |
height = document.documentElement.clientHeight, | |
y = d3.scaleLinear().range([ | |
height - margin.top - margin.bottom - legend.height - title.height, // the bottom | |
margin.top + margin.bottom + legend.height]), // the top | |
xOuter = d3.scaleBand().range([0, width]), | |
xInner = d3.scaleBand(), | |
colorScale = d3.scaleBand().range([0, 1]), | |
colors = x => d3.schemeTableau10[Math.floor(10 * colorScale(x))], | |
xAxis = d3.axisBottom().scale(xOuter), | |
yAxis = d3.axisLeft().scale(y).tickFormat(d => `${d}`), | |
isNumber = v => Number.isFinite(Number(v)), | |
rowKeys = data => data && Object.keys(data).filter(isNumber), | |
sanitize = str => typeof str == 'string' ? str.replace(/,/g, '') : str, | |
rowVals = rowData => Object.values(rowData).map(sanitize).filter(isNumber), | |
dataRows1 = [2, 3, 4, 5, 6, 8, 9, 10], | |
breeds = data => data.filter((d, i) => (dataRows1.includes(i))).map(d => d.megnevezes), // 2-6, 8-10 | |
maxVal = data => { | |
const rowMaxArr = data.map(d => Math.max(...rowVals(d))); | |
return Math.max(...rowMaxArr); | |
}; | |
const svgNode = d3.select("body").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom + legend.height + title.height) | |
.append("g") | |
.attr("transform", `translate(${margin.left},${margin.top})`); | |
svgNode.append('text') | |
.text(title.text) | |
.attr('x', width / 2) | |
.attr('y', margin.top / 2) | |
.attr('text-anchor', 'middle') | |
.attr('style', 'font-face:Times;font-size:20px;'); | |
// global data | |
let legendData = null; | |
let labels; | |
// fix nodes | |
let legendNode; | |
const chartNode = svgNode | |
.append("g") | |
.attr('class', 'chart'); | |
const tooltipNode = d3.select('body') | |
.append('div') | |
.attr('class', 'tooltip') | |
.style('opacity', 0), | |
tooltipContent = (text, value, killed) => `${text}<br/>` + | |
`<span style='color:green'>${value}</span> | <span style='color:red'>${killed}</span>`; | |
const selectedData = d => { | |
const selected = legendData.filter(l => l.selected).map(l => l.name); | |
return d.filter(d => selected.includes(d.megnevezes)); | |
}; | |
const createLegend = (data) => { | |
legendData = breeds(data).map((d, i) => ({name: d, selected: i < 2})); | |
labels = rowKeys(data[dataRows1[0]]); | |
xOuter.domain(labels); | |
colorScale.domain(breeds(data)); | |
legendNode = svgNode.append('g') | |
.attr('class', 'legend') | |
.attr('transform', `translate(${margin.left},${title.height})`); | |
}; | |
const drawLegend = data => { | |
const breedsJoin = legendNode.selectAll('.breed') | |
.data(legendData); | |
const breed = breedsJoin | |
.enter() | |
.append('g') | |
.attr('class', 'breed'); | |
breedsJoin | |
.join() | |
.select('circle') | |
.attr('style', d => { | |
const fillColor = d.selected ? colors(d.name) : 'none'; | |
const strokeColor = colors(d.name); | |
return `fill:${fillColor};fill-opacity:0.6;stroke:${strokeColor};stroke-width:1`; | |
}); | |
breed.on('click', d => { | |
d.selected = !d.selected; | |
draw(data); | |
drawLegend(data); | |
}); | |
breed | |
.append('circle') | |
.attr('cx', 5).attr('cy', 5) | |
.attr('r', 5) | |
.attr('style', d => { | |
const fillColor = d.selected ? colors(d.name) : 'none'; | |
const strokeColor = colors(d.name); | |
return `fill:${fillColor};fill-opacity:0.6;stroke:${strokeColor};stroke-width:1`; | |
}) | |
.attr('transform', 'translate(0,-8)'); | |
breed.append('text') | |
.text(d => d.name) | |
.attr('transform', 'translate(15,0)'); | |
// adjust legend nodes width to text width | |
breed.call(nodeList => { | |
let lastLabelWidth = 0; | |
nodeList.nodes().forEach(node => { | |
const n = d3.select(node); | |
n.attr('transform', `translate(${lastLabelWidth},0)`); | |
lastLabelWidth += n.select('text').node().getComputedTextLength() + 15 + 15; | |
}); | |
}) | |
}; | |
const draw = (data) => { | |
// filteredData -> breeds selected on legend | |
const filteredData = selectedData(data); | |
xInner.range([0, xOuter.bandwidth() * bar.marginRatio]).domain(filteredData.map(f => f.megnevezes)); | |
y.domain([0, maxVal(filteredData)]); // always recalc | |
// Always redraw Y axis REMOVE + APPEND | |
svgNode.selectAll('g.axis').remove(); | |
svgNode.append("g") | |
.attr("class", "y axis") | |
.call(yAxis) | |
.append("text") | |
.attr("transform", "rotate(-90)") | |
.attr("y", 6) | |
.attr("dy", ".7em") | |
.style("text-anchor", "end"); | |
svgNode.append("g") | |
.attr("class", "x axis") | |
// do we need this ? | |
.attr("transform", `translate(0,${y(0)})`) | |
.call(xAxis); | |
const years = rowKeys(filteredData[0]); | |
const nodeByYear = chartNode | |
.selectAll('g.year-node') | |
.data(years) | |
.join('g') | |
.attr('class', 'year-node') | |
.attr('transform', | |
(d, i) => `translate(${xOuter.bandwidth() * i + (xOuter.bandwidth() * bar.paddingRatio / 2)},0)`); | |
const barsData = filteredData.slice(0, filteredData.length / 2), | |
capsData = filteredData.slice(filteredData.length / 2); | |
const barStyle = opacity => d => | |
`fill:${colors(d.megnevezes)};` + | |
`stroke:${colors(d.megnevezes)};stroke-width:1px;` + | |
`stroke-opacity:0.6;fill-opacity:${opacity};`; | |
nodeByYear | |
.selectAll('rect') | |
.data( | |
year => barsData.map((f, i) => ({ | |
megnevezes: f.megnevezes, | |
val: isNumber(f[year]) ? f[year] : 0, | |
killed: capsData[i][year], | |
year: year | |
})), | |
d => d.megnevezes // the key function !!! | |
) | |
.join(enter => | |
enter.append('rect') | |
.attr('class', 'bar') | |
.attr('x', d => xInner(d.megnevezes) + xOuter.bandwidth() * bar.paddingRatio) | |
.attr('width', xInner.bandwidth() * bar.marginRatio) | |
.attr('style', barStyle(0.6)) | |
.attr('y', y(0)) | |
.attr('height', 0) | |
.call( | |
enter => enter | |
.transition() | |
.duration(1000) | |
.attr('y', d => y(d.val)) | |
.attr('height', d => y(0) - y(d.val)) | |
), | |
update => update | |
.call( | |
update => update | |
.transition() | |
.duration(1000) | |
.attr('x', d => xInner(d.megnevezes) + xOuter.bandwidth() * bar.paddingRatio) | |
.attr('width', xInner.bandwidth() * bar.marginRatio) | |
.attr('y', d => y(d.val)) | |
.attr('height', d => y(0) - y(d.val)) | |
), | |
exit => exit | |
.call( | |
exit => exit | |
.transition() | |
.duration(1000) | |
.attr('width', xInner.bandwidth() * bar.marginRatio) | |
.attr('y', y(0)) | |
.attr('height', 0) | |
.remove() | |
) | |
) | |
.on('mouseover', function (d, i) { | |
d3.select(this) | |
.attr('style', barStyle(0.4)); | |
let [x1, y1] = d3.mouse(this); | |
// because we used transform on nodeByYear | |
x1 += xOuter(d.year); | |
tooltipNode | |
.html(tooltipContent(d.megnevezes, d.val, d.killed)) | |
.style('left', `${x1}px`) // hard to not miss the px | |
.style('top', `${y1}px`) | |
.call(node => node.transition().duration(200) | |
.style('opacity', 0.9)); | |
}) | |
.on('mouseout', function (d) { | |
d3.select(this) | |
.transition() | |
.duration(200) | |
.attr('style', barStyle(0.6)); | |
tooltipNode.transition().duration(500) | |
.style('opacity', 0); | |
}) | |
; | |
nodeByYear | |
.selectAll('line') | |
.data( | |
year => capsData.map(f => ({ | |
megnevezes: f.megnevezes, | |
val: isNumber(f[year]) ? f[year] : 0 | |
})), | |
d => d.megnevezes | |
) | |
.join( | |
enter => enter.append('line') | |
.attr('class', 'cap') | |
.attr('x1', d => xInner(d.megnevezes) + xOuter.bandwidth() * bar.paddingRatio) | |
.attr('x2', d => xInner(d.megnevezes) + xOuter.bandwidth() * bar.paddingRatio + | |
xInner.bandwidth() * bar.marginRatio) | |
.attr('y1', d => y(0)) | |
.attr('y2', d => y(0)) | |
.attr('style', | |
d => | |
`stroke:red;stroke-width:3px;` + | |
'stroke-opacity:0.6;fill-opacity:0.6;') | |
.call(enter => enter | |
.transition() | |
.duration(1000) | |
.attr('x1', d => xInner(d.megnevezes) + xOuter.bandwidth() * bar.paddingRatio) | |
.attr('x2', d => xInner(d.megnevezes) + xOuter.bandwidth() * bar.paddingRatio + | |
xInner.bandwidth() * bar.marginRatio) | |
.attr('y1', d => y(d.val)) | |
.attr('y2', d => y(d.val)) | |
) | |
, | |
update => update.call( | |
update => update.transition() | |
.duration(1000) | |
.attr('x1', d => xInner(d.megnevezes) + xOuter.bandwidth() * bar.paddingRatio) | |
.attr('x2', d => xInner(d.megnevezes) + xOuter.bandwidth() * bar.paddingRatio + | |
xInner.bandwidth() * bar.marginRatio) | |
.attr('y1', d => y(d.val)) | |
.attr('y2', d => y(d.val)) | |
), | |
exit => exit.call( | |
exit => exit.transition() | |
.duration(1000) | |
.attr('y1', d => y(0)) | |
.attr('y2', d => y(0)) | |
.remove() | |
) | |
); | |
}; | |
// Let's kick-off the party | |
d3.csv("vadallomany.csv").then(data => { | |
createLegend(data); | |
draw(data); | |
drawLegend(data); | |
}); |
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
font: 10px sans-serif; | |
} | |
.axis path, | |
.axis line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.x.axis path { | |
display: none; | |
} | |
.y.axis path { | |
display: none; | |
} | |
g.legend { | |
cursor: pointer; | |
} | |
.breed circle { | |
filter: drop-shadow( 1px 1px 1px rgba(0, 0, 0, .7)); | |
} | |
div.tooltip { | |
position: absolute; | |
text-align: center; | |
padding: 2px; | |
font: 12px sans-serif; | |
background: lightsteelblue; | |
border: 0px; | |
border-radius: 8px; | |
pointer-events: none; | |
font-weight: bolder; | |
} | |
svg { | |
user-select: none; | |
} | |
</style> | |
<body> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> | |
<script src="chart.js"></script> | |
</body> |
megnevezes | 1990 | 1995 | 2000 | 2001 | 2002 | 2003 | 2004 | 2005 | 2006 | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2018 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
$Vadállomány, ezer db (február) | ||||||||||||||||||||||
Nagyvad | ||||||||||||||||||||||
gímszarvas | 42.2 | 50.1 | 77.8 | 82.6 | 82.6 | 82.6 | 78.5 | 74.1 | 69.2 | 76.9 | 85.1 | 87.1 | 92.6 | 94.1 | 96.5 | 101.6 | 102.1 | 101.4 | 99.8 | 101.5 | 111.5 | |
dámszarvas | 10.9 | 16.0 | 20.6 | 22.1 | 20.9 | 20.9 | 20.6 | 21.6 | 21.8 | 23.9 | 25.9 | 26.7 | 30.0 | 30.5 | 33.2 | 32.7 | 35.1 | 33.8 | 33.7 | 34.7 | 35.6 | |
őz | 173.1 | 233.4 | 293.8 | 319.5 | 324.4 | 324.4 | 320.9 | 316.2 | 310.9 | 312.0 | 340.4 | 349.6 | 366.6 | 355.7 | 365.6 | 375.1 | 370.6 | 359.2 | 357.3 | 361.5 | 381.6 | |
muflon | 10.6 | 8.5 | 10.5 | 9.6 | 9.3 | 9.3 | 7.9 | 8.3 | 8.8 | 10.1 | 9.9 | 10.5 | 11.0 | 11.5 | 12.3 | 13.2 | 12.6 | 11.9 | 12.1 | 10.8 | 13.3 | |
vaddisznó | 38.8 | 39.4 | 76.1 | 91.1 | 86.6 | 86.6 | 77.8 | 78.1 | 77.7 | 77.8 | 95.6 | 99.3 | 106.7 | 105.8 | 109.8 | 120.2 | 105.2 | 105.7 | 101.7 | 102.6 | 105.2 | |
Apróvad | ||||||||||||||||||||||
mezei nyúl | 795.7 | 597.5 | 514.8 | 582.5 | 630.9 | 630.8 | 535.1 | 520.8 | 535.2 | 472.1 | 522.9 | 523.8 | 538.7 | 454.5 | 497.2 | 480.0 | 445.6 | 461.0 | 414.5 | 387.6 | 381.7 | |
fácán | 1,099.3 | 784.5 | 789.8 | 824.8 | 880.6 | 880.6 | 691.0 | 737.4 | 796.9 | 723.7 | 790.4 | 795.4 | 761.7 | 612.8 | 678.8 | 611.2 | 560.1 | 630.4 | 581.5 | 558.8 | 556.2 | |
fogoly | 50.5 | 73.2 | 65.9 | 51.4 | 50.8 | 50.9 | 40.0 | 41.0 | 42.7 | 38.0 | 39.8 | 36.6 | 32.4 | 24.8 | 22.8 | 17.7 | 16.3 | 16.0 | 14.1 | 12.4 | 11.6 | |
$Kilőtt vad, ezer db | ||||||||||||||||||||||
Nagyvad | ||||||||||||||||||||||
gímszarvas | 35.2 | 21.8 | 29.0 | 34.1 | 41.7 | 41.6 | 39.1 | 36.7 | 32.0 | 34.0 | 36.2 | 39.3 | 41.1 | 47.7 | 47.7 | 53.1 | 53.7 | 53.6 | 55.1 | 58.1 | .. | |
dámszarvas | 4.6 | 5.5 | 6.0 | 6.8 | 9.0 | 6.6 | 7.6 | 8.9 | 8.4 | 9.3 | 9.7 | 10.5 | 10.8 | 11.7 | 10.1 | 12.3 | 13.9 | 11.6 | 14.3 | 13.7 | .. | |
őz | 41.5 | 37.9 | 52.8 | 61.9 | 72.5 | 76.6 | 85.6 | 89.9 | 80.6 | 79.5 | 86.1 | 89.8 | 88.6 | 93.1 | 96.3 | 100.4 | 111.5 | 114.5 | 113.7 | 113.6 | .. | |
muflon | 3.0 | 2.3 | 2.3 | 2.8 | 3.7 | 2.5 | 2.5 | 2.8 | 2.3 | 2.6 | 2.9 | 3.1 | 3.4 | 3.5 | 3.4 | 3.2 | 4.1 | 3.3 | 3.6 | 3.6 | .. | |
vaddisznó | 46.7 | 35.0 | 67.7 | 94.5 | 94.0 | 72.1 | 77.2 | 79.5 | 64.4 | 94.0 | 94.4 | 111.2 | 112.4 | 128.9 | 144.7 | 128.4 | 135.8 | 125.6 | 143.1 | 158.1 | .. | |
Apróvad | ||||||||||||||||||||||
mezei nyúl | 128.5 | 132.4 | 85.2 | 170.8 | 132.1 | 102.4 | 104.3 | 105.1 | 89.3 | 95.7 | 104.0 | 106.8 | 78.8 | 98.9 | 85.1 | 76.5 | 84.7 | 69.1 | 62.8 | 54.7 | .. | |
fácán | 812.2 | 543.5 | 430.3 | 536.9 | 558.5 | 391.3 | 439.1 | 474.0 | 361.6 | 432.3 | 420.8 | 377.7 | 306.5 | 375 | 328.6 | 331.4 | 399.4 | 369.8 | 382 | 378.5 | .. | |
fogoly | 4.0 | 3.1 | 1.2 | 1.4 | 2.1 | 2.4 | 2.9 | 2.9 | 2.9 | 3.8 | 2.3 | 3.8 | 2.2 | 2.2 | 2.8 | 1.5 | 3.2 | 2.4 | 3.7 | 2.7 | .. |