Public
Edited
Jun 11
Insert cell
Insert cell
// Create a realistic student survey dataset with mixed data types
studentData = [
{id: 1, major: "Computer Science", gpa: 3.7, credits_completed: 95, graduation_year: 2024, satisfaction_level: "Satisfied", study_hours_per_week: 25},
{id: 2, major: "Psychology", gpa: 3.9, credits_completed: 102, graduation_year: 2024, satisfaction_level: "Very Satisfied", study_hours_per_week: 20},
{id: 3, major: "Biology", gpa: 3.4, credits_completed: 88, graduation_year: 2025, satisfaction_level: "Neutral", study_hours_per_week: 30},
{id: 4, major: "Economics", gpa: 3.2, credits_completed: 76, graduation_year: 2025, satisfaction_level: "Satisfied", study_hours_per_week: 15},
{id: 5, major: "Art", gpa: 3.8, credits_completed: 92, graduation_year: 2024, satisfaction_level: "Very Satisfied", study_hours_per_week: 35},
{id: 6, major: "Computer Science", gpa: 2.9, credits_completed: 67, graduation_year: 2026, satisfaction_level: "Dissatisfied", study_hours_per_week: 12},
{id: 7, major: "Psychology", gpa: 3.6, credits_completed: 84, graduation_year: 2025, satisfaction_level: "Satisfied", study_hours_per_week: 22},
{id: 8, major: "Biology", gpa: 3.1, credits_completed: 71, graduation_year: 2026, satisfaction_level: "Neutral", study_hours_per_week: 28},
{id: 9, major: "Economics", gpa: 3.5, credits_completed: 89, graduation_year: 2024, satisfaction_level: "Satisfied", study_hours_per_week: 18},
{id: 10, major: "Art", gpa: 2.8, credits_completed: 63, graduation_year: 2027, satisfaction_level: "Dissatisfied", study_hours_per_week: 40},
{id: 11, major: "Computer Science", gpa: 4.0, credits_completed: 108, graduation_year: 2024, satisfaction_level: "Very Satisfied", study_hours_per_week: 30},
{id: 12, major: "Psychology", gpa: 3.3, credits_completed: 79, graduation_year: 2026, satisfaction_level: "Neutral", study_hours_per_week: 16},
{id: 13, major: "Biology", gpa: 3.7, credits_completed: 96, graduation_year: 2025, satisfaction_level: "Satisfied", study_hours_per_week: 26},
{id: 14, major: "Economics", gpa: 2.7, credits_completed: 58, graduation_year: 2027, satisfaction_level: "Very Dissatisfied", study_hours_per_week: 10},
{id: 15, major: "Art", gpa: 3.5, credits_completed: 86, graduation_year: 2025, satisfaction_level: "Satisfied", study_hours_per_week: 32},
{id: 16, major: "Computer Science", gpa: 3.1, credits_completed: 74, graduation_year: 2026, satisfaction_level: "Neutral", study_hours_per_week: 20},
{id: 17, major: "Psychology", gpa: 3.8, credits_completed: 99, graduation_year: 2024, satisfaction_level: "Very Satisfied", study_hours_per_week: 24},
{id: 18, major: "Biology", gpa: 2.9, credits_completed: 65, graduation_year: 2027, satisfaction_level: "Dissatisfied", study_hours_per_week: 33},
{id: 19, major: "Economics", gpa: 3.6, credits_completed: 91, graduation_year: 2025, satisfaction_level: "Satisfied", study_hours_per_week: 19},
{id: 20, major: "Art", gpa: 3.2, credits_completed: 77, graduation_year: 2026, satisfaction_level: "Neutral", study_hours_per_week: 38}
]
Insert cell
Insert cell
// Create dropdown controls for experimenting with different encodings
viewof xAxis = Inputs.select(
["major", "gpa", "credits_completed", "graduation_year", "satisfaction_level", "study_hours_per_week"],
{
label: "X-axis variable:",
value: "credits_completed"
}
)
Insert cell
viewof yAxis = Inputs.select(
["major", "gpa", "credits_completed", "graduation_year", "satisfaction_level", "study_hours_per_week"],
{
label: "Y-axis variable:",
value: "gpa"
}
)
Insert cell
viewof colorEncoding = Inputs.select(
["none", "major", "gpa", "credits_completed", "graduation_year", "satisfaction_level", "study_hours_per_week"],
{
label: "Color encoding:",
value: "major"
}
)
Insert cell
viewof sizeEncoding = Inputs.select(
["none", "gpa", "credits_completed", "study_hours_per_week"],
{
label: "Size encoding:",
value: "none"
}
)
Insert cell
{
// Set up dimensions and margins
const width = 800;
const height = 500;
const margin = ({top: 20, right: 120, bottom: 60, left: 80});

// Create SVG
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

// Helper function to determine if a variable is categorical
const isCategorical = (variable) => {
return ["major", "satisfaction_level"].includes(variable);
};

// Helper function to get unique values for categorical variables
const getUniqueValues = (data, variable) => {
return [...new Set(data.map(d => d[variable]))];
};

// Create scales based on data types
let xScale, yScale;

// X-axis scale
if (isCategorical(xAxis)) {
const xDomain = getUniqueValues(studentData, xAxis);
xScale = d3.scaleBand()
.domain(xDomain)
.range([margin.left, width - margin.right])
.padding(0.1);
} else {
xScale = d3.scaleLinear()
.domain(d3.extent(studentData, d => d[xAxis]))
.range([margin.left, width - margin.right])
.nice();
}

// Y-axis scale
if (isCategorical(yAxis)) {
const yDomain = getUniqueValues(studentData, yAxis);
yScale = d3.scaleBand()
.domain(yDomain)
.range([height - margin.bottom, margin.top])
.padding(0.1);
} else {
yScale = d3.scaleLinear()
.domain(d3.extent(studentData, d => d[yAxis]))
.range([height - margin.bottom, margin.top])
.nice();
}

// Color scale
let colorScale;
if (colorEncoding !== "none") {
if (isCategorical(colorEncoding)) {
const colorDomain = getUniqueValues(studentData, colorEncoding);
colorScale = d3.scaleOrdinal()
.domain(colorDomain)
.range(d3.schemeCategory10);
} else {
colorScale = d3.scaleSequential()
.domain(d3.extent(studentData, d => d[colorEncoding]))
.interpolator(d3.interpolateViridis);
}
}

// Size scale
let sizeScale;
if (sizeEncoding !== "none") {
sizeScale = d3.scaleLinear()
.domain(d3.extent(studentData, d => d[sizeEncoding]))
.range([4, 12]);
}

// Add axes
const xAxisGenerator = isCategorical(xAxis) ? d3.axisBottom(xScale) : d3.axisBottom(xScale);
const yAxisGenerator = isCategorical(yAxis) ? d3.axisLeft(yScale) : d3.axisLeft(yScale);

svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(xAxisGenerator)
.selectAll("text")
.style("text-anchor", isCategorical(xAxis) ? "middle" : "end")
.attr("dx", isCategorical(xAxis) ? "0em" : "-0.8em")
.attr("dy", isCategorical(xAxis) ? "1em" : "0.15em")
.attr("transform", isCategorical(xAxis) ? null : "rotate(-45)");

svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(yAxisGenerator);

// Add axis labels
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 20)
.attr("x", -(height / 2))
.attr("text-anchor", "middle")
.style("font-size", "12px")
.text(yAxis.replace(/_/g, " "));

svg.append("text")
.attr("transform", `translate(${width / 2}, ${height - 10})`)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.text(xAxis.replace(/_/g, " "));

// Add data points
const circles = svg.selectAll("circle")
.data(studentData)
.join("circle")
.attr("cx", d => {
if (isCategorical(xAxis)) {
return xScale(d[xAxis]) + xScale.bandwidth() / 2;
} else {
return xScale(d[xAxis]);
}
})
.attr("cy", d => {
if (isCategorical(yAxis)) {
return yScale(d[yAxis]) + yScale.bandwidth() / 2;
} else {
return yScale(d[yAxis]);
}
})
.attr("r", d => sizeEncoding !== "none" ? sizeScale(d[sizeEncoding]) : 6)
.attr("fill", d => {
if (colorEncoding !== "none") {
return colorScale(d[colorEncoding]);
} else {
return "steelblue";
}
})
.attr("opacity", 0.7)
.attr("stroke", "white")
.attr("stroke-width", 1);

// Add tooltips
circles.append("title")
.text(d => `Student ${d.id}
Major: ${d.major}
GPA: ${d.gpa}
Credits: ${d.credits_completed}
Graduation: ${d.graduation_year}
Satisfaction: ${d.satisfaction_level}
Study Hours/Week: ${d.study_hours_per_week}`);

// Add legend for color encoding if applicable
if (colorEncoding !== "none") {
const legend = svg.append("g")
.attr("transform", `translate(${width - 100}, 30)`);
if (isCategorical(colorEncoding)) {
// Categorical legend (existing code)
const colorDomain = getUniqueValues(studentData, colorEncoding);
legend.selectAll("rect")
.data(colorDomain)
.join("rect")
.attr("x", 0)
.attr("y", (d, i) => i * 20)
.attr("width", 15)
.attr("height", 15)
.attr("fill", d => colorScale(d));
legend.selectAll("text")
.data(colorDomain)
.join("text")
.attr("x", 20)
.attr("y", (d, i) => i * 20 + 12)
.style("font-size", "12px")
.text(d => d);
legend.append("text")
.attr("x", 0)
.attr("y", -5)
.style("font-size", "12px")
.style("font-weight", "bold")
.text(colorEncoding.replace(/_/g, " "));
} else {
// Numerical legend (new code)
const [minVal, maxVal] = d3.extent(studentData, d => d[colorEncoding]);
const legendHeight = 100;
// Create gradient definition
const defs = svg.append("defs");
const gradient = defs.append("linearGradient")
.attr("id", "color-legend-gradient")
.attr("x1", "0%")
.attr("y1", "100%")
.attr("x2", "0%")
.attr("y2", "0%");
// Add color stops to gradient
const numStops = 10;
for (let i = 0; i <= numStops; i++) {
const value = minVal + (maxVal - minVal) * (i / numStops);
gradient.append("stop")
.attr("offset", `${(i / numStops) * 100}%`)
.attr("stop-color", colorScale(value));
}
// Add gradient rectangle
legend.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 20)
.attr("height", legendHeight)
.style("fill", "url(#color-legend-gradient)")
.style("stroke", "#000")
.style("stroke-width", 1);
// Add legend labels
legend.append("text")
.attr("x", 25)
.attr("y", 5)
.style("font-size", "10px")
.text(maxVal.toFixed(1));
legend.append("text")
.attr("x", 25)
.attr("y", legendHeight + 5)
.style("font-size", "10px")
.text(minVal.toFixed(1));
// Add legend title
legend.append("text")
.attr("x", 0)
.attr("y", -5)
.style("font-size", "12px")
.style("font-weight", "bold")
.text(colorEncoding.replace(/_/g, " "));
}
}

return svg.node();
}
Insert cell
// Current Encoding Summary - Fixed as pure JavaScript
{
// Helper function to identify temporal variables
const isTemporalVariable = (variable) => {
return ["graduation_year"].includes(variable);
};

// Helper function to check if variable is categorical
const isCategorical = (variable) => {
return ["major", "satisfaction_level"].includes(variable);
};

// Helper function to get data type label
const getDataType = (variable) => {
if (isCategorical(variable)) return "Categorical";
if (isTemporalVariable(variable)) return "Temporal";
return "Numerical";
};

// Build the summary markdown
let summary = `### Current Visualization Settings:

**X-axis (${getDataType(xAxis)}):** ${xAxis.replace(/_/g, " ")}
**Y-axis (${getDataType(yAxis)}):** ${yAxis.replace(/_/g, " ")}
**Color:** ${colorEncoding === "none" ? "None" : colorEncoding.replace(/_/g, " ")} ${colorEncoding !== "none" ? `(${getDataType(colorEncoding)})` : ""}
**Size:** ${sizeEncoding === "none" ? "None" : sizeEncoding.replace(/_/g, " ")} ${sizeEncoding !== "none" ? "(Numerical)" : ""}

`;

// Add warnings and notices
if (xAxis === yAxis) {
summary += "⚠️ **Warning:** You're using the same variable for both axes. Try selecting different variables to see meaningful relationships!\n\n";
}

if (colorEncoding === xAxis || colorEncoding === yAxis) {
summary += "💡 **Notice:** You're encoding the same variable in multiple ways. This is redundant but can emphasize important patterns.\n\n";
}

return md`${summary}`;
}
Insert cell
Insert cell
Insert cell
// Display the first few rows of data for reference
Inputs.table(studentData.slice(0, 10), {
columns: ["id", "major", "gpa", "credits_completed", "graduation_year", "satisfaction_level", "study_hours_per_week"]
})
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more