let chartGender = { pad: { top: 10, right: 0, bottom: 35, left: 0 }, metric: 'cases', data: null } function createProvinceGender(parent, geoData, option={}) { let options = { metric: 'cases' } for (key in option) { if (options[key]) { options[key] = option[key] } } chartGender.data = geoData; //console.log(geoData) chart = chartGender; let container = d3.select(parent) let graphDiv = container.append('div').attr('class', 'breakdown-div') let svg = graphDiv.append('svg'); svg.attr('width', '100%').attr('height', '100%') let width = Number(graphDiv.style('width').split('px')[0]); let height = Number(graphDiv.style('height').split('px')[0]); svg.append('text').text('Age and Gender Breakdown') .attr('y', 25) .attr('x', width/2) .attr('class', 'bold') .attr('text-anchor', 'middle') let chartLayer = svg.append('g').attr('class', 'chart-layer') let maleData = geoData.data.gender.male.ages.filter(range => range.age_num !== null); let femaleData = geoData.data.gender.female.ages.filter(range => range.age_num !== null); let max = d3.max([d3.max(maleData, d => d[options.metric]), d3.max(femaleData, d => d[options.metric])]) if (max === 0) {max = 1} let gap = 28; let yScale = d3.scaleLinear() .domain([0, maleData.length]) .range([height - chart.pad.bottom, chart.pad.top]) let xScale = d3.scaleLinear() .domain([0, max]) .range([0, width/2 - chart.pad.left - gap/2]) let tot = geoData.data[options.metric].length; function makeSide(data, gender, color) { let bglayer = chartLayer.append('g').attr('class', `${gender}-bg-layer`) let xlayer = chartLayer.append('g').attr('class', `${gender}-layer`) xlayer.attr('transform', `translate(${width/2 + (gender === 'male' ? gap/2 : -gap/2)}, 0)`) bglayer.attr('transform', `translate(${width/2 + (gender === 'male' ? gap/2 : -gap/2)}, 0)`) let bg = bglayer.selectAll('rect').data(data) bg.enter().append('rect') .attr('x', d => gender === 'male' ? 0 : -xScale(max) ) .attr('width', d => xScale(max) ) .attr('y', (d, i) => yScale(i)) .attr('height', (height - chart.pad.bottom - chart.pad.top)/data.length - 1) .attr('shape-rendering', 'crispEdges') .attr('fill', '#ebebeb') .attr('opacity', 0) let x = xlayer.selectAll('rect').data(data) x.enter().append('rect') .attr('x', d => gender === 'male' ? 0 : -xScale(d[options.metric]) ) .attr('width', d => xScale(d[options.metric]) ) .attr('y', (d, i) => yScale(i)) .attr('height', (height - chart.pad.bottom - chart.pad.top)/data.length - 1) .attr('shape-rendering', 'crispEdges') .attr('fill', color) let xt = xlayer.selectAll('text').data(data) xt.enter().append('text') .text(d => `${Math.round(100*d[options.metric]/tot)}%`) .attr('x', d => { if (gender === 'male') { return xScale(d[options.metric]) + (Math.round(100*d[options.metric]/max) < 20 ? 5 : -6) } else { return -xScale(d[options.metric]) + (Math.round(100*d[options.metric]/max) < 20 ? -4 : 6) } }) .attr('y', (d, i) => yScale(i) + ((height - chart.pad.bottom - chart.pad.top)/maleData.length - 1) - 7 ) .attr('text-anchor', d => { if (gender === 'male') { return Math.round(100*d[options.metric]/max) < 20 ? 'start' : 'end' } else { return Math.round(100*d[options.metric]/max) < 20 ? 'end' : 'start' } }) .attr('font-size', '11px') .attr('fill', d => Math.round(100*d[options.metric]/max) < 20 ? '#2c2c2c' : 'white') } makeSide(maleData, 'male', '#6b1e2c') makeSide(femaleData, 'female', '#c46762') let labelData = geoData.data.gender.male.ages.filter(range => range.age_num !== null); let labelLayer = chartLayer.append('g').attr('class', 'label-layer') labelLayer.attr('transform', `translate(${width/2}, 0)`) let l = labelLayer.selectAll('text').data(labelData) l.enter().append('text') .text(d => d.age) .attr('x', 0) .attr('y', (d, i) => yScale(i) + ((height - chart.pad.bottom - chart.pad.top)/maleData.length - 1) - 6) .attr('text-anchor', 'middle') .attr('font-size', '12px') .attr('fill', '#2c2c2c') .attr('font-weight', 'bold') let buttons = container.append('div').attr('class', 'gender-buttons-div'); let cases = buttons.append('button').text('Cases') .attr('class', chartGender.metric === 'cases' ? 'selected' : '') .on('click', function() { this.parentElement.querySelectorAll('button').forEach(button => { button.classList.remove('selected') }) this.classList.add('selected') chartGender.metric = 'cases'; updateProvinceGender('#province-gender', chartGender.data, {metric: chartGender.metric} ) }) let deaths = buttons.append('button').text('Deaths') .attr('class', chartGender.metric === 'deaths' ? 'selected' : '') .on('click', function() { this.parentElement.querySelectorAll('button').forEach(button => { button.classList.remove('selected') }) this.classList.add('selected') chartGender.metric = 'deaths'; updateProvinceGender('#province-gender', chartGender.data, {metric: chartGender.metric}) }) window.addEventListener('resize', debounce(() => { resizeProvinceGender(parent, chartGender.data, chartGender) })) } /// UPDATE function updateProvinceGender(parent, geoData, option={}) { let options = { metric: 'cases' } for (key in option) { if (options[key]) { options[key] = option[key] } } chartGender.data = geoData; //console.log(geoData) chart = chartGender; let container = d3.select(parent) let graphDiv = container.select('.breakdown-div') let svg = graphDiv.select('svg'); svg.attr('width', '100%').attr('height', '100%') let width = Number(graphDiv.style('width').split('px')[0]); let height = Number(graphDiv.style('height').split('px')[0]); let chartLayer = svg.select('.chart-layer') let maleData = geoData.data.gender.male.ages.filter(range => range.age_num !== null); let femaleData = geoData.data.gender.female.ages.filter(range => range.age_num !== null); let max = d3.max([d3.max(maleData, d => d[options.metric]), d3.max(femaleData, d => d[options.metric])]) if (max === 0) {max = 1} let gap = 28; let yScale = d3.scaleLinear() .domain([0, maleData.length]) .range([height - chart.pad.bottom, chart.pad.top]) let xScale = d3.scaleLinear() .domain([0, max]) .range([0, width/2 - chart.pad.left - gap/2]) //console.log(geoData.current['deaths']) let tot = geoData.current[options.metric]; function makeSide(data, gender, color) { let bglayer = chartLayer.select(`.${gender}-bg-layer`) let xlayer = chartLayer.select(`.${gender}-layer`) xlayer.attr('transform', `translate(${width/2 + (gender === 'male' ? gap/2 : -gap/2)}, 0)`) bglayer.attr('transform', `translate(${width/2 + (gender === 'male' ? gap/2 : -gap/2)}, 0)`) let bg = bglayer.selectAll('rect').data(data) .attr('x', d => gender === 'male' ? 0 : -xScale(max)) .attr('width', d => xScale(max) ) .attr('y', (d, i) => yScale(i)) .attr('height', (height - chart.pad.bottom - chart.pad.top)/data.length - 1) .attr('shape-rendering', 'crispEdges') .attr('fill', '#d8d8d8') let x = xlayer.selectAll('rect').data(data) .attr('y', (d, i) => yScale(i)) .attr('height', (height - chart.pad.bottom - chart.pad.top)/data.length - 1) .transition().duration(500) .attr('x', d => gender === 'male' ? 0 : -xScale(d[options.metric]) ) .attr('width', d => xScale(d[options.metric]) ) let xt = xlayer.selectAll('text').data(data) .text(d => `${tot === 0 ? 0 : Math.round(100*d[options.metric]/tot)}%`) .attr('y', (d, i) => yScale(i) + ((height - chart.pad.bottom - chart.pad.top)/maleData.length - 1) - 7 ) .attr('text-anchor', d => { if (gender === 'male') { return Math.round(100*d[options.metric]/max) < 20 ? 'start' : 'end' } else { return Math.round(100*d[options.metric]/max) < 20 ? 'end' : 'start' } }) .transition().duration(500) .attr('fill', d => Math.round(100*d[options.metric]/max) < 20 ? '#2c2c2c' : 'white') .attr('x', d => { if (gender === 'male') { return xScale(d[options.metric]) + (Math.round(100*d[options.metric]/max) < 20 ? 5 : -6) } else { return -xScale(d[options.metric]) + (Math.round(100*d[options.metric]/max) < 20 ? -4 : 6) } }) } makeSide(maleData, 'male', '#1b8999') makeSide(femaleData, 'female', '#b92f56') let labelData = geoData.data.gender.male.ages.filter(range => range.age_num !== null); let labelLayer = chartLayer.select('.label-layer') labelLayer.attr('transform', `translate(${width/2}, 0)`) let l = labelLayer.selectAll('text').data(labelData) .text(d => d.age) .attr('x', 0) .attr('y', (d, i) => yScale(i) + ((height - chart.pad.bottom - chart.pad.top)/maleData.length - 1) - 6) .attr('text-anchor', 'middle') .attr('font-size', '12px') } function resizeProvinceGender(parent, geoData, option={}) { let options = { metric: 'cases' } for (key in option) { if (options[key]) { options[key] = option[key] } } //console.log(geoData) chart = chartGender; let container = d3.select(parent) let graphDiv = container.select('.breakdown-div') let svg = graphDiv.select('svg'); svg.attr('width', '100%').attr('height', '100%') let width = Number(graphDiv.style('width').split('px')[0]); let height = Number(graphDiv.style('height').split('px')[0]); let chartLayer = svg.select('.chart-layer') let maleData = geoData.data.gender.male.ages.filter(range => range.age_num !== null); let femaleData = geoData.data.gender.female.ages.filter(range => range.age_num !== null); let max = d3.max([d3.max(maleData, d => d[options.metric]), d3.max(femaleData, d => d[options.metric])]) if (max === 0) {max = 1} let gap = 28; let yScale = d3.scaleLinear() .domain([0, maleData.length]) .range([height - chart.pad.bottom, chart.pad.top]) let xScale = d3.scaleLinear() .domain([0, max]) .range([0, width/2 - chart.pad.left - gap/2]) let tot = geoData.data[options.metric].length; function makeSide(data, gender, color) { let bglayer = chartLayer.select(`.${gender}-bg-layer`) let xlayer = chartLayer.select(`.${gender}-layer`) xlayer.attr('transform', `translate(${width/2 + (gender === 'male' ? gap/2 : -gap/2)}, 0)`) bglayer.attr('transform', `translate(${width/2 + (gender === 'male' ? gap/2 : -gap/2)}, 0)`) let bg = bglayer.selectAll('rect').data(data) .attr('x', d => gender === 'male' ? 0 : -xScale(max)) .attr('width', d => xScale(max) ) .attr('y', (d, i) => yScale(i)) .attr('height', (height - chart.pad.bottom - chart.pad.top)/data.length - 1) .attr('shape-rendering', 'crispEdges') .attr('fill', '#d8d8d8') let x = xlayer.selectAll('rect').data(data) .attr('y', (d, i) => yScale(i)) .attr('height', (height - chart.pad.bottom - chart.pad.top)/data.length - 1) .attr('x', d => gender === 'male' ? 0 : -xScale(d[options.metric]) ) .attr('width', d => xScale(d[options.metric]) ) let xt = xlayer.selectAll('text').data(data) .text(d => `${tot === 0 ? 0 : Math.round(100*d[options.metric]/tot)}%`) .attr('y', (d, i) => yScale(i) + ((height - chart.pad.bottom - chart.pad.top)/maleData.length - 1) - 7 ) .attr('text-anchor', d => { if (gender === 'male') { return Math.round(100*d[options.metric]/max) < 20 ? 'start' : 'end' } else { return Math.round(100*d[options.metric]/max) < 20 ? 'end' : 'start' } }) .attr('fill', d => Math.round(100*d[options.metric]/max) < 20 ? '#2c2c2c' : 'white') .attr('x', d => { if (gender === 'male') { return xScale(d[options.metric]) + (Math.round(100*d[options.metric]/max) < 20 ? 5 : -6) } else { return -xScale(d[options.metric]) + (Math.round(100*d[options.metric]/max) < 20 ? -4 : 6) } }) } makeSide(maleData, 'male', '#1b8999') makeSide(femaleData, 'female', '#b92f56') let labelData = geoData.data.gender.male.ages.filter(range => range.age_num !== null); let labelLayer = chartLayer.select('.label-layer') labelLayer.attr('transform', `translate(${width/2}, 0)`) let l = labelLayer.selectAll('text').data(labelData) .text(d => d.age) .attr('x', 0) .attr('y', (d, i) => yScale(i) + ((height - chart.pad.bottom - chart.pad.top)/maleData.length - 1) - 6) .attr('text-anchor', 'middle') .attr('font-size', '12px') }