chart = {
const root = pack(data);
let focus = root;
let view;
const nodeLocations = Object({
"":[],
"Chicago-Naperville-Elgin, IL-IN-WI": [width*0.2, 450, 2*Math.sqrt(194205/Math.PI), "Chicago, IL"],
"Philadelphia-Camden-Wilmington, PA-NJ-DE-MD": [width*0.5, 450, 2*Math.sqrt(191468/Math.PI), "Philadelphia, PA"],
"Atlanta-Sandy Springs-Roswell, GA": [width*0.77, 450, 2*Math.sqrt(116120/Math.PI), "Atlanta, GA"],
"":[],
"Cleveland-Elyria, OH":[175, 1050, 2*Math.sqrt(33648/Math.PI), "Cleveland, OH"],
"Tampa-St. Petersburg-Clearwater, FL":[475, 1050, 2*Math.sqrt(53959/Math.PI), "Tampa Bay, FL"],
"Denver-Aurora-Lakewood, CO":[775, 1050, 2*Math.sqrt(47342/Math.PI), "Denver, CO"],
"":[],
"Montgomery, AL":[175, 1460, 2*Math.sqrt(9474/Math.PI)],
"Chattanooga, TN-GA":[475, 1460, 2*Math.sqrt(12979/Math.PI), "Chattanooga, TN"],
"Green Bay, WI":[775, 1460, 2*Math.sqrt(5562/Math.PI)],
"":[],
"Santa Maria-Santa Barbara, CA":[1200, 1050, 2*Math.sqrt(26676/Math.PI), "Santa Barbara, CA"],
"Durham-Chapel Hill, NC":[1500, 1050, 2*Math.sqrt(44848/Math.PI)],
"Lubbock, TX":[1800, 1050, 2*Math.sqrt(33540/Math.PI)],
"":[],
"Lafayette-West Lafayette, IN": [1200, 1500, 2*Math.sqrt(35106/Math.PI), "Lafayette, IN"],
"Tucson, AZ": [1500, 1500, 2*Math.sqrt(33777/Math.PI)],
"Corvallis, OR": [1800, 1500, 2*Math.sqrt(18094/Math.PI)],
// "Athens-Clarke County, GA": [1200, 2000, 2*Math.sqrt(33445/Math.PI), "Athens, GA"],
"":[], // Weak College Towns
"Topeka, KS": [1200,1860,2*Math.sqrt(3958/Math.PI)],
"Amarillo, TX": [1500,1860,2*Math.sqrt(4853/Math.PI)],
"Lake Charles, LA": [1800,1860,2*Math.sqrt(5276/Math.PI)],
// "Florence-Muscle Shoals, AL": [1200,2300,2*Math.sqrt(4967/Math.PI), "Florence, AL"],
})
const heiacronyms = Object({
"": "", // Philadelphia
/*
"University of Pennsylvania": "UPenn",
"Temple University": "Temple",
"University of Delaware": "UDelaware",
"Drexel University": "Drexel",
"Rowan University": "Rowan",
"West Chester University of Pennsylvania": "West Chester",
"Villanova University": "Villanova",
"": "", // Atlanta
"Georgia State University": "Georgia State",
"Kennesaw State University": "Kennesaw State",
"Georgia Institute of Technology-Main Campus": "Georgia Tech",
"Emory University": "Emory",
"University of West Georgia": "W Georgia",
"Georgia Gwinnett College": "Gwinnett",
"Clark Atlanta University": "Clark Atl",
"Clayton State University": "Clayton",
"Gordon State College": "Gordon",
"": "", // San Francisco
"University of California-Berkeley": "UC Berkeley",
"University of Georgia": "UGeorgia",
"": "", // Syracuse
"Syracuse University": "Syracuse University",
"SUNY College at Oswego": "SUNYOswego",
"Colgate University": "Colgate",
"Le Moyne College": "LeMoyne",
"Morrisville State College": "Morrisv",
"SUNY College of Environmental Science and Forestry": "ESF",
"Upstate Medical University": "UMU",
"Cazenovia College": "Caz",
"": "", // Denver
"Metropolitan State University of Denver": "Metro State",
"University of Colorado Denver/Anschutz Medical Campus": "UColorado",
"University of Denver": "UDenver",
"Colorado School of Mines": "CO Mines",
"Regis University": "Regis",
"Community College of Denver": "CCD",
"Red Rocks Community College": "RRCC",
"Johnson & Wales University-Denver": "J&W",
"Colorado Christian University": "",
"Denver Seminary": "",
"Iliff School of Theology": "",
"": "", // Tampa FL
"University of South Florida-Main Campus": "U South Florida",
"The University of Tampa": "U Tampa",
"St Petersburg College": "St Petersburg",
"Saint Leo University": "St Leo",
"Pasco-Hernando State College": "PH State",
"University of South Florida-St Petersburg": "USF",
"Eckerd College": "Eckherd",
"Florida College": "",
"Trinity College of Florida": "",
"": "", // Multi-College Towns
"University of California-Santa Barbara": "UC Santa Barbara",
"Texas Tech University": "Texas Tech",
"University of Florida": "University of Florida",
"Santa Fe College": "Santa Fe",
// Strong College Towns
"Purdue University-Main Campus": "Purdue University",
"University of Arizona": "University of Arizona",
"Oregon State University": "Oregon State",
"University of Georgia": "University of Georgia",
// Weak College Towns
"Washburn University": "Washburn",
"West Texas A & M University": "W TX A&M",
"McNeese State University": "McNeese St",
"University of North Alabama": "U N AL",
*/
})
// Move all nodes that have a specified location above to their desinations:
for (let node of root.descendants()) {
if (nodeLocations[node.data.name]) {
let dest = nodeLocations[node.data.name];
move_circle_group(node, dest[0], dest[1]);
size_circle_group(node, dest[2]);
}
}
function shoulddisplay(d) {
if (nodeLocations.hasOwnProperty(d.data.name)) {
return true
} else if (d.parent) {
return shoulddisplay(d.parent) // recursive loop
} else {
return false
}
}
// Alters the radii of the largest circles so that they are proportional by number of students
// calculate_new_radii(root.children)
// Spreads the second-largest circles out within the largest circles
// spread_child_circles(root.children)
// Sets up the svg
const svg = d3.create("svg")
.attr("xmlns", "http://www.w3.org/2000/svg")
.attr("xmlns:xlink", "http://www.w3.org/1999/xlink")
.attr("viewBox", `0 0 ${width} ${height}`)
// .attr("viewBox", `-${width / 2} -${height / 2} ${width} ${height}`)
.style("display", "block")
.style("margin", "0 -14px")
.style("background", "white")
.style("cursor", "pointer")
// .on("click", () => zoom(root)); // event handler, f(x) () is an empty function with no parameters
// Creates the groups and circles within them
const node = svg.append("g")
.selectAll("circle")
.data(root.descendants().slice(1)) // all of the descendants of the root, except the root slice(1) start at index 1 instead of index 0
.join("circle") // let me tell you what
.style("display", d => shoulddisplay(d) ? "inline" : "none")
.attr("fill", d => d.children ? depth_color(d.depth) : item_color(d.data) ) // does the data point have children? if yes, color according to depth, otherwise color it according to the color function below
.attr("stroke", d => d.children ? "#C2CBD3" : null ) // does the data point have children? if yes, outline
.attr("stroke-width", 0.7) // does the data point have children? if yes, set width, if not, set width to zero (helps deal with mouseover and mouseout issues with the HEIs, but breaks the mouseover for the HEIs)
// can I define a different mouseover and mouseout action for the HEIs at the smallest level?
// .attr("fill-opacity", d => d.parent === root ? .5 : 1) // makes second highest level circles transparent
// .attr("pointer-events", d => !d.children ? "none" : null)
.on("mouseover", function(d) {
if (d.children) {
d3.select(this).attr("stroke", "#687186");
} else {
d3.select(this).attr("stroke", "white");
}
}) // data and index(when you care about order)
// .on("mouseout", function() { d3.select(this).attr("stroke", "#C2CBD3"); }) // set this to same as color above
.on("mouseout", function(d) {
d3.select(this).attr("stroke", d => d.children ? "#C2CBD3" : null);
}) // data and index(when you care about order)
// .on("click", d => focus !== d && d.children && (zoom(d), d3.event.stopPropagation()));
// Adds a title element for mouseover text
svg.selectAll("circle").join("circle").append("title")
//.text(d => d.data.name + "\nStudents: " + Math.floor(d.value).toLocaleString());
.text(function(d){
if (d.parent.data.name == "top") {
return d.data.name + "\nStudents: " + Math.floor(d.value).toLocaleString()
} else if (d.parent.parent.data.name == "top"){
return d.data.name + "\nStudents: " + Math.floor(d.value).toLocaleString() + "\n" + d.parent.data.name
} else {
return d.data.name + "\nStudents: " + Math.floor(d.value).toLocaleString() + "\n" + d.parent.data.name + "\n" + d.parent.parent.data.name
}
})
// Adds paths around circles for labels
const edge_paths = svg.append("g")
.selectAll("path")
.data(root.descendants().slice(1))
.join("path")
.attr("stroke", "none")
.attr("fill", "none")
.attr("id", d => "path_" + d.data.name)
.attr("d", d => path_data_for_edge_of_circle(d.x, d.y, d.r*1.02));
// Adds labels
const edge_text = svg.append("g").append("text")
.style("font", "28px 'Gill Sans', 'Gill Sans MT', sans-serif")
.attr("text-anchor", "middle")
.selectAll("textPath")
.data(root.descendants().slice(1))
.join("textPath")
.style("display", d => nodeLocations.hasOwnProperty(d.data.name) ? "inline" : "none")
.attr("startOffset", "50%")
.attr("xlink:href", d => "#path_" + d.data.name)
.text(d => nodeLocations.hasOwnProperty(d.data.name) && nodeLocations[d.data.name].length >= 4 ? nodeLocations[d.data.name][3] : d.data.name)
// Original label code, as separate objects
const label = svg.append("g")
.style("font", "12px 'Gill Sans', 'Gill Sans MT', sans-serif")
.style("fill", "white")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle") // vertical orientation
.selectAll("text")
.data(root.descendants())
.join("text")
.style("fill-opacity", d => heiacronyms.hasOwnProperty(d.data.name) ? 1 : 0)
.style("display", d => heiacronyms.hasOwnProperty(d.data.name) ? "inline" : "none")
.text(d => heiacronyms[d.data.name])
;
// Make a group for the title, subtitle, legend
const headergroup = svg.append("g") // creates a group, appends to svg, stores as variable
// function add_text(container, text, left, top, fontsize, fontweight, anchor)
const supc = add_text(headergroup,"Super Centers",(width*0.5),(height*0.05),40,"","middle")
const majc = add_text(headergroup,"Major Centers",475,870,40,"","middle")
const minc = add_text(headergroup,"Minor Centers",475,1325,40,"","middle")
const mct = add_text(headergroup,"Multi-College Towns",1500,870,40,"","middle")
const sct = add_text(headergroup,"Strong College Towns",1500,1325,40,"","middle")
const wct = add_text(headergroup,"Weak College Towns",1500,1750,40,"","middle")
// const subtitletext = "Higher education institutions within metros within type"
// Add legend
// add_legend(container,title,cccolor,x_position_relative_to_center,y_position_relative_to_center)
// add_legend2col(container, title, colorkey, numcolumns, columnwidth, left, top)
const legendtitle = "Carnegie Class of Higher Education Institutions"
const legend = add_legend2col(headergroup,legendtitle,cccolor,2,400,100,1725)
// Add credit
const credittext = "Ehlenz & Mawhorter 2020"
const credit = add_text(svg,credittext,(width*0.98),(height*0.98),20,undefined,"end")
function zoomTo(v) {
const k = 1.00*((width) / v[2]); // multiplying k by 0.75 zooms out a bit
view = v;
label.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`);
edge_paths.attr("d", d => path_data_for_edge_of_circle((d.x - v[0])*k, (d.y - v[1])*k, (d.r2 ? d.r2 : d.r) * k+7));
node.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`);
node.attr("r", d => (d.r2 ? d.r2 : d.r) * k); // uses r2 if it's available ? is if statement, values that are undefined are false, : acts as else
}
zoomTo([0, 0, width]);
function zoom(d) {
const focus0 = focus;
focus = d;
// legend.style("display", focus === root ? "block" : "none" );
const transition = svg.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", d => {
const i = d3.interpolateZoom(view, [focus.x, focus.y, (focus.r2 ? focus.r2 : focus.r) * 2]);
return t => zoomTo(i(t));
});
headergroup.transition(transition)
.style("fill-opacity", root === focus ? 1 : 0)
.on("start", function() { if (root === focus) this.style.display = "inline"; })
.on("end", function() { if (root !== focus) this.style.display = "none"; });
/* // want to say the parent is the focus, and the parent of the parent is the root (and is &&)
edge_text
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.transition(transition)
.style("fill-opacity", d => d.parent === focus ? 1 : 0)
.on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
*/
}
return svg.node();
}