Unlisted
Edited
Oct 21, 2023
Insert cell
Insert cell
Insert cell
Insert cell
dashboard = {
const bar = barchart();
const bar2 = barchart2();
const map = va_school_map;
const splot = brushScatter();
const mapLegend = Legend(d3.scaleSequential([1, 35], d3.interpolateCividis), {
title: "Map Scale: EMI Rank in Virginia"
});

d3.select(splot).on("input", () => {
bar.update(splot.value);
bar2.update(splot.value);
});

bar.update(splot.value);
bar2.update(splot.value);

let dash = html`<table><tr><td>${bar}</br>${bar2}</br>${splot}</td><td>${map}</br>${mapLegend}</td></table>`;
return dash;
}
Insert cell
Insert cell
va_school_map = {
//Tooltip testing https://mappingwithd3.com/tutorials/basics/tooltip/

d3.select("#tooltip");

const height = totalHeight;
const svg = d3.select(DOM.svg(width, height));

const g = svg.append("g");

//Making another empty placeholder to use for the university location data
const univ = svg.append("g");

const tooltip = d3
.select("#tooltip")
.append("div")
.attr("class", "tooltip")
.style("font-size", "15px");

//Underlying map code stays the same
const map = g
.selectAll("path")
.data(counties.features)
.enter()
.append("path")
.attr("fill", "#e3e3e3")
.attr("stroke", "white")
.attr("stroke-width", 0.5)
.attr("d", va_geopath);

//Place the dots on top of the map
//Filtering geoJSON file - https://stackoverflow.com/questions/47962084/d3-filter-geojson-by-geometry

const unis = univ
.selectAll("path")
.data(all_geojson.features)
.enter()
.append("path")
.filter(function (d) {
return `${d.properties.stateVA}` === "Virginia";
})
.attr("d", va_geopath)
.attr("fill", (d) => continuousColor(`${d.properties.emi_rank_va}`))

.attr("opacity", 0.8)
.attr("stroke", "#103650");

//Adding tooltip code for school data
const univText = unis
.append("title")
.text((d) => `${d.properties.Institution}\n`);

univText
.append("subtitle")
.text(
(d) =>
`EMI Rank in VA: ${d.properties.emi_rank_va}\nEMI Rank Nationally: ${d.properties.emi_rank}\nTotal Undergraduates: ${d.properties.tot_ug}\nControl: ${d.properties.Control}\nCarnegie Classification 2018: ${d.properties.BASIC2018}\nCarnegie Locale: ${d.properties.LOCALE_ABBV}`
);

unis
.on("mouseover", function () {
d3.select(this)
.attr("fill", "#EC008B")
.attr("stroke-width", 1)
.attr("opacity", 1);
})
.on("mouseout", function () {
d3.select(this)
.attr("fill", (d) => continuousColor(`${d.properties.emi_rank_va}`))
.attr("stroke", "#103650")
.attr("stroke-width", 1)
.attr("opacity", 0.7);
});

return svg.node();
}
Insert cell
Insert cell
function barchart() {
const barWidth = scatter_sq;
const barHeight = 50;
//outer svg
const svg = d3
.create("svg")
.attr("width", barWidth + marginbar.left + marginbar.right)
.attr("height", barHeight + marginbar.top + marginbar.bottom);

const g = svg
.append("g")
.attr("transform", `translate(${marginbar.left}, ${marginbar.top})`);

//x scale (bottom axis)
const x = d3
.scaleLinear()
.domain([0, d3.max(controlCount.values())])
.range([0, barWidth])
.nice();

//y scale - standard
const y = d3
.scaleBand()
.domain(control_order)
.range([0, barHeight])
.padding(0.2);

//Time for axes

const xAxis = d3.axisBottom(x);
const xAxisGroup = g
.append("g")
.attr("transform", `translate(0, ${barHeight})`);

//gridlines
xAxisGroup
.call(xAxis)
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("stroke", palegrey)
.attr("y1", -barHeight)
.attr("y2", 0)
);

//Y Axis
const yAxis = d3.axisLeft(y);
const yAxisGroup = g.append("g").call(yAxis);
yAxisGroup
.append("text")
//.attr("transform", "rotate(-90)") // now x, y flipped in terms of positioning
.attr("x", -margin.left)
.attr("y", -margin.top + 5)
.attr("fill", "black")
.attr("dominant-baseline", "hanging")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Control");

let barsGroup = g.append("g");

function update(data) {
const controlCount = d3.rollup(
data,
(group) => group.length,
(d) => d.Control
);

const t = svg.transition().ease(d3.easeLinear).duration(100);

barsGroup
.selectAll("rect")
.data(controlCount, ([Control, count]) => Control)
.join("rect")
.attr("fill", ([Control, count]) => colorFunc(Control))
//.attr("stroke", "black")
//.attr("stroke-width", 1)
.attr("height", y.bandwidth())
.attr("x", 0)
.attr("y", ([Control, count]) => y(Control))
.transition(t)
.attr("width", ([Control, count]) => x(count));
}

return Object.assign(svg.node(), { update });
}
Insert cell
function barchart2() {
const barWidth = scatter_sq;
const barHeight = 75;
//outer svg
const svg = d3
.create("svg")
.attr("width", barWidth + marginbar.left + marginbar.right)
.attr("height", barHeight + marginbar.top + marginbar.bottom);

const g = svg
.append("g")
.attr("transform", `translate(${marginbar.left}, ${marginbar.top})`);

//x scale (bottom axis)
const x = d3
.scaleLinear()
.domain([0, d3.max(controlCount.values())])
.range([0, barWidth])
.nice();

//y scale - standard
const y = d3
.scaleBand()
.domain(locale_order)
.range([0, barHeight])
.padding(0.2);

//Time for axes

const xAxis = d3.axisBottom(x);
const xAxisGroup = g
.append("g")
.attr("transform", `translate(0, ${barHeight})`);

//gridlines
xAxisGroup
.call(xAxis)
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("stroke", palegrey)
.attr("y1", -barHeight)
.attr("y2", 0)
);

//Y Axis
const yAxis = d3.axisLeft(y);
const yAxisGroup = g.append("g").call(yAxis);
yAxisGroup
.append("text")
//.attr("transform", "rotate(-90)") // now x, y flipped in terms of positioning
.attr("x", -margin.left)
.attr("y", -margin.top + 5)
.attr("fill", "black")
.attr("dominant-baseline", "hanging")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Carnegie Locale");

let barsGroup = g.append("g");

function update(data) {
const localeCount = d3.rollup(
data,
(group) => group.length,
(d) => d.LOCALE_ABBV
);

const t = svg.transition().ease(d3.easeLinear).duration(100);

barsGroup
.selectAll("rect")
.data(localeCount, ([LOCALE_ABBV, count]) => LOCALE_ABBV)
.join("rect")
.attr("fill", ([LOCALE_ABBV, count]) => localeColor(LOCALE_ABBV))
//.attr("stroke", "black")
//.attr("stroke-width", 1)
.attr("height", y.bandwidth())
.attr("x", 0)
.attr("y", ([LOCALE_ABBV, count]) => y(LOCALE_ABBV))
.transition(t)
.attr("width", ([LOCALE_ABBV, count]) => x(count));
}

return Object.assign(svg.node(), { update });
}
Insert cell
Insert cell
scatter_sq = width * 0.4
Insert cell
function brushScatter() {
const visWidth = scatter_sq;
const visHeight = 250;

const xCol = "li_pep_pct",
xLabel = "Low Income Price to Earning Ratio";
const yCol = "emi",
yLabel = "Economic Mobility Percentile Rank";

const initialValue = schoolData;

const svg = d3
.create("svg")
.attr("width", visWidth + margin_sc.left + margin_sc.right)
.attr("height", visHeight + margin_sc.top + margin_sc.bottom)
.property("value", initialValue);

const g = svg
.append("g")
.attr("transform", `translate(${margin_sc.left}, ${margin_sc.top})`);

//Scales
const x = d3
.scaleLinear()
.domain([0, d3.max(schoolData, (d) => d[xCol])])
.nice()
.range([0, visWidth]);

const y = d3
.scaleLinear()
.domain([0, d3.max(schoolData, (d) => d[yCol])])
.nice()
.range([visHeight, 0]);

//x axis
const xAxis = d3.axisBottom(x).tickFormat(format_pct);
const xAxisGroup = g
.append("g")
.attr("transform", `translate(0, ${visHeight})`);

//Grid Lines; https://observablehq.com/@d3/connected-scatterplot
xAxisGroup
.call(xAxis)
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("stroke", "lightgrey")
.attr("y1", -visHeight)
.attr("y2", 0)
)
.append("text")
.attr("x", visWidth / 2)
.attr("y", 26)
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.text(xLabel);

const yAxis = d3.axisLeft(y).tickFormat(format_pct);
const yAxisGroup = g.append("g");

yAxisGroup
.call(yAxis)
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("stroke", "lightgrey")
.attr("x1", 0)
.attr("x2", visWidth)
)
.append("text")
.attr("transform", "rotate(-90)") //Flipping X and Y
.attr("x", -1 * (visHeight / 2))
.attr("y", -50)
.attr("fill", "black")
.attr("dominant-baseline", "middle")
.attr("font-weight", "bold")
.text("Economic Mobility Percentile");

//Adding Brush
const brush = d3
.brush()
//Size of brush area can't go outside plot
.extent([
[0, 0],
[visWidth, visHeight]
])
.on("brush", onBrush)
.on("end", onEnd);

g.append("g").call(brush);

//Drawing points
const radius = 3;

//National Data
const dots = svg
.append("g")
.attr("transform", `translate(${margin_sc.left}, ${margin_sc.top})`)
.selectAll("circle")
.data(schoolData)
.join("circle")
.attr("cx", (d) => x(d[xCol]))
.attr("cy", (d) => y(d[yCol]))
.attr("r", radius)
//.attr("opacity", 0.7)
.attr("fill", UIpaleDarkCyan)
.attr("stroke", "white")
.attr("stroke-width", 0.1);
//Fill function

//VA institutions popout
const dotsVA = svg
.append("g")
.attr("transform", `translate(${margin_sc.left}, ${margin_sc.top})`)
.selectAll("circle")
.data(
schoolData.filter(function (d) {
return d.stateVA === "Virginia";
})
)
.join("circle")
.attr("cx", (d) => x(d.li_pep_pct))
.attr("cy", (d) => y(d.emi))
.attr("r", 3.5)
.attr("fill", (d) => colorFunc(d.Control))
.attr("opacity", 1)
.attr("stroke", "black")
.attr("stroke-width", 0.5);

const dotsVAtext = dotsVA.append("title").text((d) => `${d.Institution}\n`);

dotsVAtext
.append("subtitle")
.text(
(d) =>
`EMI Rank in VA: ${d.emi_rank_va}\nEMI Rank Nationally: ${d.emi_rank}\nTotal Undergraduates: ${d.tot_ug}\nControl: ${d.Control}\nCarnegie Classification 2018: ${d.BASIC2018}\nCarnegie Locale: ${d.LOCALE_ABBV}`
);

dotsVA
.on("mouseover", function () {
d3.select(this)
.attr("fill", "#EC008B")
.attr("stroke", "black")
.attr("stroke-width", 1);
})

.on("mouseout", function () {
d3.select(this)
.attr("fill", (d) => colorFunc(d.Control))
.attr("stroke", "black")
.attr("stroke-width", 0.5);
});

//Event gives the coordinates of the brush box
function onBrush(event) {
const [[x1, y1], [x2, y2]] = event.selection;

//True if dot in brush box, false otherwise

function isBrushed(d) {
const cx = x(d[xCol]);
const cy = y(d[yCol]);
return cx >= x1 && cx <= x2 && cy >= y1 && cy <= y2;
}

//Dot color
dots.attr("fill", (d) => (isBrushed(d) ? UIpaleCyan : palegrey));
dotsVA
.attr("fill", (d) =>
isBrushed(d) ? colorFunc(d.Control) : colorFunc(d.Control)
)
.attr("opacity", (d) => (isBrushed(d) ? 1 : 0.5))
.attr("stroke-width", (d) => (isBrushed(d) ? 1 : 0))
.attr("stroke", (d) => (isBrushed ? "black" : palegrey));

//Updating the bar chart (I hope)
svg.property("value", schoolData.filter(isBrushed)).dispatch("input");
}

//Finishing the Brushing
function onEnd(event) {
if (event.selection === null) {
dots.attr("fill", UIpaleDarkCyan);
dotsVA
.attr("stroke-width", 0.5)
.attr("stroke", "black")
.attr("opacity", 1)
.attr("fill", (d) => colorFunc(d.Control));
svg.property("value", initialValue).dispatch("input");
}
}

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
//emi_us = FileAttachment("emi_national_state.csv").csv()
Insert cell
//Fix `emi_us` column data types
//emi_us.forEach((d) => {
//(d.emi = +d.emi),
//(d.p_earning = +d.p_earning),
//(d.li_pep_pct = +d.li_pep_pct),
//(d.pct_pell = +d.pct_pell);
//})
Insert cell
all_geojson = FileAttachment("LongLatEMIall@3.geojson").json()
Insert cell
all_emi_loc = FileAttachment("LongLatEMIall@2.csv").csv()
Insert cell
//Fixing new datatypes

all_emi_loc.forEach((d) => {
(d.emi = +d.emi),
(d.p_earning = +d.p_earning),
(d.Longitude = +d.Longitude),
(d.Latitude = +d.Latitude),
(d.li_pep_pct = +d.li_pep_pct),
(d.tot_ug = +d.tot_ug),
(d.pct_pell = +d.pct_pell);
})
Insert cell
Insert cell
//viewof colorScheme = Inputs.radio(
//new Map([
// ["Control of Institution", colorFunc()],
// ["Carnegie Locale Code", localeColor()]
// ]),
// {
// value: localeColor(),
// label: "Select Scatterplot Color Scheme:"
// }
//)
Insert cell
//function clicked(event, d) {
Insert cell
Insert cell
Insert cell
Insert cell
emiColor = d3.scaleOrdinal().range([UIpaleCyan, "#EC008B"])
Insert cell
colorScatter = d3.scaleOrdinal().range([UIpaleDarkCyan, "#EC008B"])
Insert cell
VAscatter = d3.scaleOrdinal().range(["palegrey", "#eb99c2"])
Insert cell
UIpaleCyan = d3.color("rgba(22, 150, 210, 0.3)")
Insert cell
UIpaleDarkCyan = d3.color("rgba(18, 113, 158, 0.2)")
Insert cell
Insert cell
Insert cell
schoolData = d3
.groups(all_geojson.features, (d) => d.properties.Institution)
.map(([, [{ properties }]]) => properties)
Insert cell
columns = Object.keys(schoolData[0])
Insert cell
viewof emi_univ = brushScatter()
Insert cell
controlCount = d3.rollup(
schoolData,
(group) => group.length,
(d) => d.Control
)
Insert cell
control_order = ["Private, non-profit", "Public", "For-profit"]
Insert cell
localeCount = d3.rollup(
schoolData,
(group) => group.length,
(d) => d.LOCALE_ABBV
)
Insert cell
locale_order = ["City", "Suburb", "Town", "Rural"]
Insert cell
colorFunc = d3
.scaleOrdinal()
.domain(control_order)
.range(["#1696d2", "#FDBF11", "#CFE8F3"])
Insert cell
localeColor = d3
.scaleOrdinal()
.domain(locale_order)
.range(["#0a4c6a", "#e88e2d", "#98cf90", "#d5d5d4"])
Insert cell
emi_rank_vaRange = d3.extent(schoolData, (d) => d.emi_rank_va)
Insert cell
continuousColor = d3
.scaleSequential(d3.interpolateCividis)
.domain(emi_rank_vaRange)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more