|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
@import url('https://fonts.googleapis.com/css?family=Patrick+Hand|Signika|Dosis'); |
|
|
|
.area { |
|
fill: steelblue; |
|
clip-path: url(#clip); |
|
} |
|
|
|
.zoom { |
|
cursor: move; |
|
fill: none; |
|
pointer-events: all; |
|
} |
|
|
|
.axis--y > .domain, .axis--x > .domain{ |
|
stroke: #BEBEBE; |
|
} |
|
|
|
.axis--y > g.tick > line, .axis--x > g.tick > line{ |
|
stroke: #BEBEBE; |
|
} |
|
|
|
#var_name { |
|
margin-left: 60px; |
|
margin-bottom: 5px; |
|
font-family: 'Dosis', sans-serif; |
|
font-size: 22px; |
|
font-weight: 800; |
|
} |
|
</style> |
|
<!-- <p id="var_name"></p> --> |
|
<svg width="960" height="500"></svg> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script> |
|
<script> |
|
|
|
const svg = d3.select("svg"), |
|
margin = { top: 20, right: 20, bottom: 110, left: 40 }, |
|
width = +svg.attr("width") - margin.left - margin.right, |
|
height = +svg.attr("height") - margin.top - margin.bottom; |
|
|
|
const x = d3.scaleBand().range([0, width]).padding(0.1), |
|
y = d3.scaleLinear().range([height, 0]); |
|
|
|
const xAxis = d3.axisBottom(x), |
|
yAxis = d3.axisLeft(y); |
|
|
|
let brush, zoom, ref_data, data, nbFt, mean_value; |
|
let focus; |
|
let displayed; |
|
let current_range; |
|
let my_region = 'FRE'; |
|
let variable_name = 'Ma variable'; |
|
let t; |
|
// d3.select('#var_name') |
|
// .text(variable_name); |
|
|
|
d3.json("nuts1_data.geojson", function(error, geojson_data) { |
|
if (error) throw error; |
|
ref_data = geojson_data.features.map(ft => ({ |
|
id: ft.properties.NUTS1_2016, |
|
EMP_2014: +ft.properties.EMP_2014, |
|
Y20_60_2014: +ft.properties['Y20.64_2014'] / 1000, |
|
TX_EMP_2014: (+ft.properties.EMP_2014 / +ft.properties['Y20.64_2014']) * 100000, |
|
ratio: (+ft.properties.EMP_2014 / +ft.properties['Y20.64_2014']) * 100000, |
|
})).filter(ft => ft.ratio); |
|
|
|
data = [].concat(ref_data); |
|
data.sort((a, b) => a.ratio - b.ratio); |
|
|
|
nbFt = data.length; |
|
current_range = [0, nbFt]; |
|
mean_value = d3.mean(data.map(d => d.ratio)); |
|
brush = d3.brushX() |
|
.extent([[0, 0], [width, height]]) |
|
.on('brush', brushed) |
|
.on("end", endbrush); |
|
|
|
svg.append("defs").append("clipPath") |
|
.attr("id", "clip") |
|
.append("rect") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
focus = svg.append("g") |
|
.attr("class", "focus") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
x.domain(data.map(ft => ft.id)); |
|
y.domain([d3.min(data, d => d.ratio), d3.max(data, d => d.ratio)]); |
|
|
|
focus.append("g") |
|
.attr("class", "axis axis--x") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(xAxis); |
|
|
|
focus.select('.axis--x') |
|
.selectAll("text") |
|
.style("text-anchor", "end") |
|
.attr("dx", "-.8em") |
|
.attr("dy", ".15em") |
|
.attr("transform", "rotate(-65)"); |
|
|
|
focus.append("g") |
|
.attr("class", "axis axis--y") |
|
.call(yAxis); |
|
|
|
let groupe_line_mean = focus.append('g').attr('class', 'mean'); |
|
groupe_line_mean.append('text') |
|
.attrs({ x: 60, y: y(mean_value) + 20 }) |
|
.styles({ |
|
'font-family': '\'Signika\', sans-serif', |
|
'fill': 'red', |
|
'fill-opacity': '0.8', |
|
display: 'none' |
|
}) |
|
.text('Valeur moyenne'); |
|
|
|
focus.append("g") |
|
.attr("class", "brush") |
|
.call(brush); |
|
|
|
svg.append('image') |
|
.attrs({ |
|
x: width + margin.left + 5, |
|
y: 385, |
|
width: 15, |
|
height: 15, |
|
'xlink:href': 'reverse_blue.png', |
|
id: 'img_reverse' |
|
}) |
|
.on('click', function () { |
|
if (data[0].ratio < data[data.length - 1].ratio) { |
|
data.sort((a, b) => b.ratio - a.ratio); |
|
} else { |
|
data.sort((a, b) => a.ratio - b.ratio); |
|
} |
|
x.domain(data.slice(current_range[0], current_range[1]).map(ft => ft.id)); |
|
update(); |
|
updateAxis(); |
|
}); |
|
|
|
const tooltip = svg.append("g") |
|
.attr("class", "tooltip") |
|
.style("display", "none"); |
|
|
|
tooltip.append("rect") |
|
.attr("width", 50) |
|
.attr("height", 40) |
|
.attr("fill", "white") |
|
.style("opacity", 0.5); |
|
|
|
tooltip.append("text") |
|
.attr('class', 'id_feature') |
|
.attr("x", 25) |
|
.attr("dy", "1.2em") |
|
.style("text-anchor", "middle") |
|
.attr("font-size", "14px"); |
|
|
|
tooltip.append("text") |
|
.attr('class', 'value_feature') |
|
.attr("x", 25) |
|
.attr("dy", "2.4em") |
|
.style("text-anchor", "middle") |
|
.attr("font-size", "14px") |
|
.attr("font-weight", "bold"); |
|
|
|
update(); |
|
}); |
|
|
|
function update() { |
|
displayed = 0; |
|
let bar = focus.selectAll(".bar") |
|
.data(data); |
|
|
|
bar |
|
.attr("x", d => x(d.id)) |
|
.attr("width", x.bandwidth()) |
|
.attr("y", d => y(d.ratio)) |
|
.attr("height", d => height - y(d.ratio)) |
|
.style('fill', d => d.id !== my_region ? 'steelblue' : 'yellow') |
|
.style("display", (d) => { |
|
let to_display = x(d.id) != null; |
|
if (to_display) { |
|
displayed += 1; |
|
return 'initial'; |
|
} |
|
return 'none'; |
|
}); |
|
|
|
bar.enter() |
|
.insert("rect", '.mean') |
|
.attr("class", "bar") |
|
.attr("x", d => x(d.id)) |
|
.attr("width", x.bandwidth()) |
|
.attr("y", d => y(d.ratio)) |
|
.attr("height", d => height - y(d.ratio)) |
|
.style('fill', d => d.id !== my_region ? 'steelblue' : 'yellow'); |
|
|
|
bar.exit().remove(); |
|
|
|
focus.selectAll(".bar") |
|
.on("mouseover", () => { |
|
clearTimeout(t); |
|
svg.select('.tooltip').style('display', null); |
|
}) |
|
.on("mouseout", () => { |
|
clearTimeout(t); |
|
t = setTimeout(() => { svg.select('.tooltip').style('display', 'none'); }, 250); |
|
}) |
|
.on("mousemove", function(d) { |
|
clearTimeout(t); |
|
const tooltip = svg.select('.tooltip').style('display', null); |
|
tooltip |
|
.select("text.id_feature") |
|
.text(`${d.id}`); |
|
tooltip.select('text.value_feature') |
|
.text(`${Math.round(d.ratio)}`); |
|
tooltip |
|
.attr('transform', `translate(${[d3.mouse(this)[0] - 5, d3.mouse(this)[1] - 25]})`); |
|
}); |
|
|
|
svg.select('.brush') |
|
.on('mousemove mousedown', function () { |
|
dispatchClickToBar(d3.event.pageX, d3.event.pageY, d3.event.clientX, d3.event.clientY, 'mousemove'); |
|
}) |
|
.on('mouseout', function () { |
|
clearTimeout(t); |
|
t = setTimeout(() => { svg.select('.tooltip').style('display', 'none'); }, 250); |
|
}) |
|
|
|
} |
|
|
|
function updateAxis() { |
|
let axis_x = focus.select(".axis--x").call(xAxis); |
|
axis_x.selectAll("text") |
|
.attrs(d => { |
|
if (displayed > 20) { |
|
return { dx: '-0.8em', dy: '0.15em', transform: 'rotate(-65)' }; |
|
} else { |
|
return { dx: '0', dy: '0.71em', transform: null }; |
|
} |
|
}) |
|
.style('text-anchor', d => displayed > 20 ? 'end' : 'middle'); |
|
} |
|
|
|
function dispatchClickToBar(pageX, pageY, clientX, clientY, type) { |
|
const elems = document.elementsFromPoint(pageX, pageY); |
|
const elem = elems.find(e => e.className.baseVal === 'bar'); |
|
if (elem) { |
|
const new_click_event = new MouseEvent(type, { |
|
pageX: pageX, |
|
pageY: pageY, |
|
clientX: clientX, |
|
clientY: clientY, |
|
bubbles: true, |
|
cancelable: true, |
|
view: window |
|
}); |
|
elem.dispatchEvent(new_click_event); |
|
} else { |
|
clearTimeout(t); |
|
t = setTimeout(() => { svg.select('.tooltip').style('display', 'none'); }, 5); |
|
} |
|
} |
|
|
|
function brushed() { |
|
clearTimeout(t); |
|
svg.select('.tooltip').style('display', 'none'); |
|
var s = d3.event.selection || [0,0]; |
|
let _current_range = [Math.round(s[0] / (width/nbFt)), Math.round(s[1] / (width/nbFt))]; |
|
focus.selectAll(".bar") |
|
.style('fill', (_, i) => { |
|
if (i >= _current_range[0] && i < _current_range[1]) { |
|
return 'orange'; |
|
} |
|
return 'steelblue'; |
|
}); |
|
} |
|
|
|
function endbrush() { |
|
// dispatchClickToBar( |
|
// d3.event.sourceEvent.pageX, |
|
// d3.event.sourceEvent.pageY, |
|
// d3.event.sourceEvent.clientX, |
|
// d3.event.sourceEvent.clientY, |
|
// 'mousemove'); |
|
var s = d3.event.selection || [0, 0]; |
|
let _current_range = [Math.round(s[0] / (width/nbFt)), Math.round(s[1] / (width/nbFt))]; |
|
focus.selectAll(".bar") |
|
.style('fill', (_, i) => { |
|
if (i >= _current_range[0] && i < _current_range[1]) { |
|
return 'red'; |
|
} |
|
return 'steelblue'; |
|
}); |
|
} |
|
</script> |