plot = {
reset;
let value = null;
let svg = d3.create("svg");
let data_total = derived_1990_2019;
let year_period_using = year_period;
let click = 0;
let data_mesh = topojson.mesh(us, us.objects.states);
let data_feature = topojson.feature(us, us.objects.states);
let path = path_us;
let map_using = null;
if(in_or_out == "In"){
map_using = experiment_map_2;
}
else{
map_using = experiment_map;
}
svg.attr("width", 1275)
.attr("height", 775)
let label_tweak = function(selection, label, fs) {
selection.attr('text-anchor', 'start')
.attr('font-size', fs)
.attr('font-weight', 'bold')
.attr('fill', d3.hcl(0,0,42))
.text(label)
}
let label_tweak1 = function(selection, label, fs) {
selection.attr('text-anchor', 'end')
.attr('font-size', fs)
.attr('font-weight', 'bold')
.attr('fill', d3.hcl(0,0,42))
.text(label)
}
svg.append('text')
.attr('transform', `translate(575, 35)`)
.call(label_tweak1, 'Interactive Migration Patterns Viewer', '30px')
svg.append('text')
.attr('transform', `translate(1075, 175)`)
.call(label_tweak1, 'Migration Ratio Heatmap(y to x)', '25px')
let line_text = svg.append('text')
.attr('transform', `translate(20, 485)`)
.call(label_tweak, 'Total Migration from 1990-2019', '15px')
let red_line_text = svg.append('text')
.attr('transform', `translate(75, 700)`)
let blue_line_text = svg.append('text')
.attr('transform', `translate(75, 725)`)
let bar_text_blue = svg.append('text')
.attr('transform', `translate(650, 685)`)
.call(label_tweak, 'Top Barplot: Top 5 Origin State', '15px').attr('fill', 'steelblue')
let bar_text_red = svg.append('text')
.attr('transform', `translate(650, 700)`)
.call(label_tweak, 'Bottom Barplot: Top 5 Destination State', '15px').attr('fill', 'red')
let rect = svg.append('rect')
.attr('x', 875)
.attr('y', 675)
.attr('width', 10)
.attr('height', 10)
.attr('fill', 'green')
let bar_text_green = svg.append('text')
.attr('transform', `translate(890, 685)`)
.call(label_tweak, 'Neighboring States', '12px')
let rect1 = svg.append('rect')
.attr('x', 935)
.attr('y', 690)
.attr('width', 10)
.attr('height', 10)
.attr('fill', 'orange')
let bar_text_yellow = svg.append('text')
.attr('transform', `translate(950, 700)`)
.call(label_tweak, 'Neighboring States', '12px')
// --------------------------------------------------Geographic Map-----------------------------------------------------------------
let g = svg.append('g').attr('transform', `translate(40, 45) scale(0.6, 0.6)`);
let regions = g.selectAll()
.classed('regions',true)
.data(data_feature.features)
.enter().append("path")
.attr("d", path)
.attr("fill", d => populationColor(populationByState.get(d.properties.name)))
regions.append("title")
.text(d => `${d.properties.name}`);
let selection_color = d3.hcl(30,30,60);
let selection_color_hover = d3.hcl(220,30,60);
var x = "empty";
g.append("path")
.datum(data_mesh, (a, b) => a !== b)
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-linejoin", "round")
.attr("pointer-events", "none")
.attr("d", path);
// --------------------------------------------------heatmap plot-----------------------------------------------------------------
let margin = options.margin,
width = options.width,
height = options.height,
container = options.container,
showLabels = options.show_labels,
startColor = options.start_color,
endColor = options.end_color,
startColor_blue = options.start_color_blue,
endColor_blue = options.end_color_blue,
highlightCellOnHover = options.highlight_cell_on_hover,
highlightCellColor = options.highlight_cell_color;
let dataValues = data['values'];
let dataLabels = data['labels'];
let maxValue = d3.max(dataValues, function(layer) { return d3.max(layer, function(d) { return d; }); });
let minValue = d3.min(dataValues, function(layer) { return d3.min(layer, function(d) { return d; }); });
let numrows = dataValues.length;
let numcols = dataValues[0].length;
let g_heat = svg.append("g")
.attr("transform", "translate(" + (margin.left + 600) + "," + (margin.top + 150) + ")");
let background = g_heat.append("rect")
.style("stroke", "black")
.style("stroke-width", "2px")
.attr("width", width)
.attr("height", height);
let x_1 = d3.scaleBand()
.domain(d3.range(numcols))
.range([0, width]);
let y = d3.scaleBand()
.domain(d3.range(numrows))
.range([0, height]);
let colorMap = d3.scaleLinear()
.domain([0,1])
.range([startColor, endColor]);
let colorMap_blue = d3.scaleLinear()
.domain([0,1])
.range([startColor_blue, endColor_blue]);
let row = g_heat.selectAll(".row")
.data(dataValues)
.enter().append("g")
.attr("class", "row")
.attr("transform", function(d, i) { return "translate(0," + y(i) + ")"; });
let cell = row.selectAll(".cell")
.data(function(d) { return d; })
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d, i) { return "translate(" + x_1(i) + ", 0)"; });
cell.append('rect')
.attr("width", width/numcols)
.attr("height", height/numrows)
.style("stroke-width", 0);
cell.append("text")
.attr("dy", ".32em")
.attr("x", x_1.range() / 2)
.attr("y", y.range() / 2)
.style("fill", function(d, i) { return d >= maxValue/2 ? 'white' : 'black'; })
.style("font", "1px times")
row.selectAll(".cell")
.data(function(d, i) { return dataValues[i].map(a => a.ratio); })
.style("fill", color_d=>
{ if(isNaN(color_d))
{return 'white'}
else
{return colorMap_blue(color_d)}});
if(highlightCellOnHover){
cell
.on("mouseover", function(d) {
d3.select(this).style("fill", highlightCellColor);
})
.on("mouseout", function() {
d3.select(this).style("fill", color_d=> { if(isNaN(color_d)){return 'white'} else if(color_d>=0){return colorMap_blue(color_d)
}else if(color_d<0){return colorMap(-color_d)}
});
});
}
if (showLabels){
var labels = g_heat.append('g')
.attr('class', "labels");
var columnLabels = labels.selectAll(".column-label")
.data(dataLabels)
.enter().append("g")
.attr("class", "column-label")
.attr("transform", function(d, i) { return "translate(" + x_1(i) + "," + height + ")"; });
columnLabels.append("line")
.style("stroke", "black")
.style("stroke-width", "1px")
.attr("y2", 5);
columnLabels.append("text")
.attr("x", 0)
.attr("dx", "-0.82em")
.attr("y", y.bandwidth() / 2)
.attr("dy", ".41em")
.attr("text-anchor", "end")
.attr("transform", "rotate(-90)")
.text(function(d, i) { return d; })
.style("font-size", "8px");
var rowLabels = labels.selectAll(".row-label")
.data(dataLabels)
.enter().append("g")
.attr("class", "row-label")
.attr("transform", function(d, i) { return "translate(" + 0 + "," + y(i) + ")"; });
rowLabels.append("line")
.style("stroke", "black")
.style("stroke-width", "1px")
.attr("x1", 0)
.attr("x2", -5)
rowLabels.append("text")
.attr("x", -8)
.attr("y", y.bandwidth() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "end")
.text(function(d, i) { return d; })
.style("font-size", "8px");;
}
// --------------------------------------------------temporal plot-----------------------------------------------------------------
let selected_item = null;
let year_range = 250;
let year_scale = d3.scaleBand()
.domain(year_period_using)
.range([0,year_range])
let pop_scale = d3.scaleLinear()
.domain([0, 127000000])
.range([150, 0])
let mig_scale = d3.scaleLinear()
.domain([2000000, 4600000])
.range([150, 0])
let g_temp = svg.append("g");
let pop_axis = null;
let pop_path = null;
let in_path = null;
g_temp.attr("transform", "translate(" + (margin.left - 20) + "," + (margin.top + 450) + ")");
g_temp.append('rect')
.attr('width', year_range)
.attr('height', 150)
.attr('fill', 'none').attr('stroke', d3.hcl(0,0,30)).attr('stroke-width', .7)
g_temp.append('g')
.classed('axis', true)
.attr('transform', `translate(0,150)`)
.call(d3.axisBottom(year_scale)).selectAll('text').attr("transform", "translate(-15, 15) rotate(-65)")
if(testt == "Population"){
pop_axis = g_temp.append('g')
.classed('axis', true)
.call(d3.axisLeft(pop_scale))
pop_path = g_temp.append("path")
.datum(total_pop)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return year_scale(d.year) })
.y(function(d) { return pop_scale(d.pop) }))
}
else{
pop_axis = g_temp.append('g')
.classed('axis', true)
.call(d3.axisLeft(mig_scale))
pop_path = g_temp.append("path")
.datum(total_migrants)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return year_scale(d.year) })
.y(function(d) { return mig_scale(d.pop) }))
in_path = g_temp.append("path");
}
// --------------------------------------------------Ranking Barplots------------------------------------------------------------------
let state_range = 150;
let state_out_scale = d3.scaleBand()
.domain(top_out_name)
.range([0,state_range])
let state_in_scale = d3.scaleBand()
.domain(top_in_name)
.range([0,state_range])
let num_scale = d3.scaleLinear()
.domain([0, 500000])
.range([0, 200])
let g_rank_out = svg.append("g");
let g_rank_in = svg.append("g");
let out_axis = null;
let in_axis = null;
let num_axis = null;
g_rank_out.attr("transform", "translate(" + (margin.left + year_range + 70) + "," + (margin.top + 375) + ")");
g_rank_out.append('rect')
.attr('width', 200)
.attr('height', state_range)
.attr('fill', 'none').attr('stroke', d3.hcl(0,0,30)).attr('stroke-width', .7)
g_rank_in.attr("transform", "translate(" + (margin.left + year_range + 70) + "," + (margin.top + 525) + ")");
g_rank_in.append('rect')
.attr('width', 200)
.attr('height', state_range)
.attr('fill', 'none').attr('stroke', d3.hcl(0,0,30)).attr('stroke-width', .7)
out_axis = g_rank_out.append('g')
.classed('axis', true)
.call(d3.axisLeft(state_out_scale))
in_axis = g_rank_in.append('g')
.classed('axis', true)
.call(d3.axisLeft(state_in_scale))
num_axis = g_rank_in.append('g')
.classed('axis', true)
.attr('transform', `translate(0, ${state_range})`)
.call(d3.axisBottom(num_scale))
num_axis.selectAll('text').attr("transform", "translate(-20, 20) rotate(-65)");
let bar_out = g_rank_out.selectAll(".bars")
.data(top_5_out_total)
bar_out
.enter()
.append("rect")
.attr("class", "bars")
.attr("x", 0)
.attr("y", function(d) { return state_out_scale(d.origin); })
.attr("width", function(d) { return num_scale(d.num_migrants); })
.attr("height", state_out_scale.bandwidth)
.attr("stroke", "white")
.attr("stroke-width", 1.5)
.attr("fill", "steelblue")
let bar_in = g_rank_in.selectAll(".bars")
.data(top_5_in_total)
bar_in
.enter()
.append("rect")
.attr("class", "bars")
.attr("x", 0)
.attr("y", function(d) { return state_in_scale(d.destination); })
.attr("width", function(d) { return num_scale(d.num_migrants); })
.attr("height", state_in_scale.bandwidth)
.attr("stroke", "white")
.attr("stroke-width", 1.5)
.attr("fill", "red")
// --------------------------------------------------click interaction-----------------------------------------------------------------
regions
.on('click', function(e,d) {
selected_item = d;
if(testt == "Population"){
g.selectAll("path")
.data(data_feature.features)
.attr("fill", da => (populationByState.get(da.properties.name) - populationByState.get(d.properties.name)) >= 0 ?
positiveColor(Math.abs(populationByState.get(da.properties.name) - populationByState.get(d.properties.name))) :
negativeColor(Math.abs(populationByState.get(da.properties.name) - populationByState.get(d.properties.name))));
let storing_pop = [];
for(let i = 0; i < data_total.length; i++){
for(let j = 0; j < data_total[0].length; j = j + 50){
if(data_total[i][j].origin == d.properties.name){
storing_pop.push({year: year_period_using[i], pop: data_total[i][j].origin_population});
break;
}
}
}
let area_scale = d3.scaleLinear()
.domain([0, 17000000])
.range([150, 0]);
pop_axis.call(d3.axisLeft(area_scale));
storing_pop.filter(d => d.pop > 0);
pop_path
.datum(storing_pop)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(a) { return year_scale(a.year) })
.y(function(a) { return area_scale(a.pop) }));
}
else{
line_text.call(label_tweak, 'Total Migration from & to ' + d.properties.name + ' from 1990-2019', '12px')
blue_line_text.call(label_tweak, 'Migration from ' + d.properties.name, '15px').attr('fill', 'steelblue')
red_line_text.call(label_tweak, 'Migration to ' + d.properties.name, '15px').attr('fill', 'red')
g.selectAll("path").data(data_feature.features)
.attr("fill", da => experiment(100*map_using.get(d.properties.name).get(da.properties.name)));
row.selectAll(".cell")
.data(function(de, i) {
if(dataValues[i][0].state == d.properties.name){
return dataValues[i].map(a => a.ratio);
}
else{
return dataValues[i].map(function(a) { return a.dest == d.properties.name ? a.ratio : 0;});
}
})
.style("fill", color_d=>
{ if(isNaN(color_d))
{return 'white'}
else
{return colorMap_blue(color_d)}});
let storing_pop = [];
for(let i = 0; i < data_total.length; i++){
let num = data_total[i]
.filter(a => a.origin == d.properties.name)
.map(d => d.num_migrants).filter(d => d > 0)
.reduce((a, b) => a + b, 0);
let num2 = data_total[i]
.filter(a => a.destination == d.properties.name)
.map(d => d.num_migrants).filter(d => d > 0)
.reduce((a, b) => a + b, 0);
storing_pop.push({year: year_period_using[i], out: num, in: num2});
}
let store_pop_in_out = [storing_pop.map(dat => dat.out),storing_pop.map(dat => dat.in)].flat()
let pop_exts = d3.extent(store_pop_in_out)
let area_scale = d3.scaleLinear()
.domain([0, 1.1*pop_exts[1]])
.range([150, 0]);
pop_axis.call(d3.axisLeft(area_scale));
pop_path
.datum(storing_pop)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return year_scale(d.year) })
.y(function(d) { return area_scale(d.out) }));
in_path
.datum(storing_pop)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return year_scale(d.year) })
.y(function(d) { return area_scale(d.in) }));
let temp_out_top = top_5_out.get(d.properties.name);
let temp_out_name = temp_out_top.map(a => a.destination);
let temp_out_num = temp_out_top.map(a => a.num_migrants);
let temp_in_top = top_5_in.get(d.properties.name);
let temp_in_name = temp_in_top.map(a => a.origin);
let temp_in_num = temp_in_top.map(a => a.num_migrants);
state_out_scale = d3.scaleBand()
.domain(temp_out_name)
.range([0,state_range])
state_in_scale = d3.scaleBand()
.domain(temp_in_name)
.range([0,state_range])
let domain_upper_bound = d3.max([d3.extent(temp_out_num)[1], d3.extent(temp_in_num)[1]]);
num_scale = d3.scaleLinear()
.domain([0, 1.1*domain_upper_bound])
.range([0, 200])
out_axis.call(d3.axisLeft(state_out_scale))
in_axis.call(d3.axisLeft(state_in_scale))
num_axis.call(d3.axisBottom(num_scale)).selectAll('text').attr("transform", "translate(-20, 20) rotate(-65)")
.style('fill', 'black')
bar_out = g_rank_out.selectAll('.bars').data(temp_out_top);
bar_out.exit().remove();
bar_out
.enter()
.append("rect")
.attr("class", "bars")
.merge(bar_out)
.attr("x", 0)
.attr("y", function(a) { return state_out_scale(a.destination); })
.attr("width", function(a) { return num_scale(a.num_migrants); })
.attr("height", state_out_scale.bandwidth)
.attr("stroke", "white")
.attr("stroke-width", 1.5)
.attr("fill", function(a){
let temp = neighbor_map.get(d.properties.name);
for(let i =0; i < temp.length; i++){
if(temp[i] == a.destination){
return "green";
}
}
return "steelblue";
})
bar_in = g_rank_in.selectAll('.bars').data(temp_in_top);
bar_in.exit().remove();
bar_in
.enter()
.append("rect")
.attr("class", "bars")
.merge(bar_in)
.attr("x", 0)
.attr("y", function(d) { return state_in_scale(d.origin); })
.attr("width", function(d) { return num_scale(d.num_migrants); })
.attr("height", state_in_scale.bandwidth)
.attr("stroke", "white")
.attr("stroke-width", 1.5)
.attr("fill", function(a){
let temp = neighbor_map.get(d.properties.name);
for(let i =0; i < temp.length; i++){
if(temp[i] == a.origin){
return "orange";
}
}
return "red";
})
}
x = d.properties.name;
g.selectAll("path").classed('click',false);
let is_clicked = d3.select(this).classed('click');
let fill_color = selection_color;
d3.select(this).attr('fill', fill_color).classed('click', 1);
})
// --------------------------------------------------hover interaction-----------------------------------------------------------------
regions
.on('mouseover', function(e,d) {
let fill_color = selection_color_hover;
d3.select(this).attr('fill', fill_color);
if(selected_item == null){
row.selectAll(".cell")
.data(function(de, i) {
if(dataValues[i][0].state == d.properties.name){
return dataValues[i].map(a => a.ratio);
}
else{
return dataValues[i].map(function(a) { return a.dest == d.properties.name ? a.ratio : 0;});
}
})
.style("fill", color_d=>
{ if(isNaN(color_d))
{return 'white'}
else
{return colorMap_blue(color_d)}});
}
else{
line_text.call(label_tweak,
'Migration between ' + d.properties.name + ' and ' + selected_item.properties.name + ' from 1990-2019', '12px')
blue_line_text.call(label_tweak,
'Migration from ' + selected_item.properties.name + ' to ' + d.properties.name, '15px')
.attr('fill', 'steelblue')
red_line_text.call(label_tweak,
'Migration from ' + d.properties.name + ' to ' + selected_item.properties.name, '15px')
.attr('fill', 'red')
row.selectAll(".cell")
.data(function(de, i) {
if(dataValues[i][0].state == d.properties.name){
return dataValues[i].map(a => a.ratio);
}else if(dataValues[i][0].state == selected_item.properties.name){
return dataValues[i].map(a => a.ratio);
}
else{
return dataValues[i].map(function(a) { return ((a.dest == d.properties.name)||(a.dest == selected_item.properties.name)
) ? -1*(a.ratio) : NaN;});
}
})
.style("fill", color_d=> { if(isNaN(color_d)){return 'white'} else if(color_d>=0){return colorMap_blue(color_d)
}else if(color_d<0){return colorMap(-color_d)}
});
}
if(selected_item != null && d.properties.name != selected_item.properties.name){
if(testt != "Population"){
let storing_pop = [];
for(let i = 0; i < data_total.length; i++){
let num = data_total[i]
.filter(a => (a.origin == selected_item.properties.name && a.destination == d.properties.name))
.map(a => a.num_migrants/a.origin_population*100).filter(a => a > 0)
.reduce((a, b) => a + b, 0);
let num2 = data_total[i]
.filter(a => (a.origin == d.properties.name && a.destination == selected_item.properties.name))
.map(a => a.num_migrants/a.origin_population*100).filter(a => a > 0)
.reduce((a, b) => a + b, 0);
storing_pop.push({year: year_period_using[i], out: num, in: num2});
}
let store_pop_in_out = [storing_pop.map(dat => dat.out),storing_pop.map(dat => dat.in)].flat()
let pop_exts = d3.extent(store_pop_in_out)
let area_scale = d3.scaleLinear()
.domain([0, 1.1*pop_exts[1]])
.range([150, 0]);
pop_axis.call(d3.axisLeft(area_scale));
pop_path
.datum(storing_pop)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(input) { return year_scale(input.year) })
.y(function(input) { return area_scale(input.out) }));
in_path
.datum(storing_pop)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(input) { return year_scale(input.year) })
.y(function(input) { return area_scale(input.in) }));
}
}
})
regions
.on('mouseout', function(e,d) {
let is_clicked = d3.select(this).classed('click');
var fill_color;
if(x=="empty"){
fill_color = d => populationColor(populationByState.get(d.properties.name));
}
else{
if(testt == "Population"){
let temp = (populationByState.get(d.properties.name) - populationByState.get(x)) >= 0 ?
positiveColor(Math.abs(populationByState.get(d.properties.name) - populationByState.get(x))) :
negativeColor(Math.abs(populationByState.get(d.properties.name) - populationByState.get(x)));
fill_color = is_clicked ? selection_color : temp;
}
else{
fill_color = is_clicked ? selection_color : experiment(100*map_using.get(x).get(d.properties.name));
}
}
d3.select(this).attr('fill', fill_color)
});
return svg.node();
}