Public
Edited
Apr 2, 2024
2 forks
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const target = html`<svg>`;
const svg = d3.select(target).attr("viewBox", [0, 0, width, height]);

// Await the URL of the image
const backgroundImageUrl = await FileAttachment("background@1.PNG").url();

// svg
// .append("image")
// .attr("width", width)
// .attr("height", height)
// .attr("href", backgroundImageUrl)
// .attr("opacity", "0.4");

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

return target;
}
Insert cell
swatches({
color: d3.scaleOrdinal(
[
"Wholesale and Retail Trade",
"Manufacturing",
"Leisure and hospitality",
"Business services",
"Construction",
"Education and Health",
"Government",
"Finance",
"Self-employed",
"Other"
],
d3.schemeTableau10
),
columns: "280px",
swatchSize: 20
})
Insert cell
update = {
const g = d3.select(chart).select(".gDrawing");

// Check if both Home and Road are selected
const isBothSelected =
Home_Road.includes("Home") && Home_Road.includes("Road");

let filteredData;
if (isBothSelected) {
// If both are selected, first filter data to include both Home and Road, then average the metrics
const tempFiltered = data.filter(
(d) => d.Season === currSeason && Home_Road.includes(d.TEAM_GAME_LOCATION)
);

// Group data by TEAM_NAME (or another identifier) and calculate average of xAttr and yAttr
const groupedData = d3.group(tempFiltered, (d) => d.TEAM_NAME);
filteredData = Array.from(groupedData, ([team, values]) => {
// Create a new object for the team's data
let teamData = {
TEAM_NAME: team,
img: values[0].img
};

teamData[xAttr] = d3.mean(values, (v) => v[xAttr]);
teamData[yAttr] = d3.mean(values, (v) => v[yAttr]);

return teamData;
});
} else {
// Otherwise, filter normally
filteredData = data.filter(
(d) => d.Season === currSeason && Home_Road.includes(d.TEAM_GAME_LOCATION)
);
}

// let filteredData = data.filter(
// (d) => d.Season === currSeason && d.TEAM_GAME_LOCATION === Home_Road[0]
// );

const x = d3
.scaleLinear()
.domain([
d3.min(filteredData, (d) => d[xAttr]),
d3.max(filteredData, (d) => d[xAttr])
])
.range([0, width - margin.left - margin.right])
.nice();

const y = d3
.scaleLinear()
.domain([
d3.min(filteredData, (d) => d[yAttr]),
d3.max(filteredData, (d) => d[yAttr])
])
.range([height - margin.top - margin.bottom, 0])
.nice();

const medianX = (x.domain()[0] + x.domain()[1]) / 2;

const medianY = (y.domain()[0] + y.domain()[1]) / 2;

const xAxisYPosition = y(medianY);
const yAxisXPosition = x(medianX);

// Remove any existing axes before redrawing them
g.selectAll(".xAxis").remove();
g.selectAll(".yAxis").remove();

g.append("g")
.attr("class", "xAxis")
.attr("transform", `translate(0, ${xAxisYPosition})`)
.append("text")
.attr("class", "axisLabel")
.style("fill", "black")
.attr("transform", `translate(${width - margin.left - margin.right}, 30)`)
.style("text-anchor", "end");

g.append("g")
.attr("class", "yAxis")
.attr("transform", `translate(${yAxisXPosition}, 0)`)
.append("text")
.attr("class", "axisLabel")
.style("fill", "black")
.attr("transform", `translate(0, -10)`)
.style("text-anchor", "middle");

g.select(".xAxis").call(d3.axisBottom(x)).select(".axisLabel").text(xAttr);
g.select(".yAxis").call(d3.axisLeft(y)).select(".axisLabel").text(yAttr);

const t = g.transition().duration(3000);

const move = (sel) =>
sel
.attr("x", (d) => x(d[xAttr]) - imgWidth / 2)
.attr("y", (d) => y(d[yAttr]) - imgHeight / 2);

const rows = g
.selectAll(".rows")
.data(filteredData, (d) => d.TEAM_NAME)
.join(
(enter) =>
enter
.append("image")
.attr("class", "rows")
.attr("href", (d) => d.img)
.attr("width", imgWidth)
.attr("height", imgHeight)
.call((enter) => enter.transition(t)),
(update) => update.call((update) => update.transition(t).call(move)),
(exit) =>
exit.call((exit) => exit.transition(t).style("opacity", 0).remove())
);

// tooltipBehavior(g.selectAll(".rows"), tooltip);
rows
.on("mouseover", (event, d) => {
tooltip.style("opacity", 1);
d3.select(event.currentTarget)
.raise() // This brings the current element to the front
.transition()
.duration(200)
.style("stroke", "black")
.style("stroke-width", 2);
})
.on("mousemove", (event, d) => {
tooltip
.html(
`<b>Team:</b> ${d.TEAM_NAME}<br><b>${xAttr}:</b> ${d[xAttr]}<br><b>${yAttr}:</b> ${d[yAttr]}`
)
.style("left", event.pageX + 10 + "px")
.style("top", event.pageY - 28 + "px");
})
.on("mouseout", () => {
tooltip.style("opacity", 0);
d3.selectAll("image")
.transition()
.duration(200)
.style("stroke", "none")
.style("stroke-width", 0);
});
}
Insert cell
// // Define the tooltip behavior
// function tooltipBehavior(selectionGroup, tooltipDiv) {
// selectionGroup.each(function () {
// d3.select(this)
// .on("mouseover.tooltip", handleMouseover)
// .on("mousemove.tooltip", handleMousemove)
// .on("mouseleave.tooltip", handleMouseleave);
// });

// function handleMouseover(d) {
// showTooltip();
// setContents(d, tooltipDiv);
// setStyle(d3.select(this));
// }

// function handleMousemove(event) {
// const [mouseX, mouseY] = d3.pointer(event);
// setPosition(mouseX, mouseY);
// }

// function handleMouseleave() {
// hideTooltip();
// resetStyle(d3.select(this));
// }

// function showTooltip() {
// tooltipDiv.style("display", "block");
// }

// function hideTooltip() {
// tooltipDiv.style("display", "none");
// }

// function setContents(data, tooltipDiv) {
// tooltipDiv.html(
// `<b>Team:</b> ${data.TEAM_NAME}<br><b>${xAttr}:</b> ${data[xAttr]}<br><b>${yAttr}:</b> ${data[yAttr]}`
// );
// }

// function setStyle(selection) {
// selection.raise().style("stroke", "black").style("stroke-width", 2);
// }

// function resetStyle(selection) {
// selection.style("stroke", "none").style("stroke-width", 0);
// }

// function setPosition(mouseX, mouseY) {
// tooltipDiv
// .style("top", mouseY + MOUSE_POS_OFFSET + "px")
// .style("left", mouseX + MOUSE_POS_OFFSET + "px");

// // Adjust the tooltip position to prevent it from going off the screen
// const tooltipRect = tooltipDiv.node().getBoundingClientRect();
// if (tooltipRect.right > window.innerWidth) {
// tooltipDiv.style(
// "left",
// window.innerWidth - tooltipRect.width - MOUSE_POS_OFFSET + "px"
// );
// }
// if (tooltipRect.bottom > window.innerHeight) {
// tooltipDiv.style(
// "top",
// window.innerHeight - tooltipRect.height - MOUSE_POS_OFFSET + "px"
// );
// }
// }
// }
Insert cell
// MOUSE_POS_OFFSET = 1 // You can adjust the value as needed for your layout
Insert cell
Insert cell
imgWidth = 30;
Insert cell
imgHeight = 30
Insert cell
Team@1.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
quants = attrs.filter(a => typeof data[0][a] === "number")
Insert cell
attrs = Object.keys(data[0])
Insert cell
margin = ({ left: 40, top: 20, bottom: 50, right: 20 })
Insert cell
height = 800
Insert cell
seasons = new Set(data.map((d) => d.Season))
Insert cell
data = FileAttachment("Team@1.csv")
.text()
.then((text) => {
// Parse the CSV without auto-typing
const rows = d3.csvParse(text);

// Process each row
rows.forEach((row) => {
// Parse the 'Season' column as a string explicitly
row.Season = row.Season.toString();

// Now auto-type the rest of the columns
for (const [key, value] of Object.entries(row)) {
if (key !== "Season") {
// Skip the 'Season' column
row[key] = d3.autoType({ [key]: value })[key];
}
}
});

return rows;
})
Insert cell
import { Scrubber } from "@mbostock/scrubber"
Insert cell
import { swatches } from "@d3/color-legend@704"
Insert cell
import { slider, select } from "@jashkenas/inputs"
Insert cell
d3 = require("d3@6")
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