Public
Edited
Apr 22
Insert cell
Insert cell
import {allPlayers, advancedSingleSelector, formatCurrency, advancedPlayerFinder, teamFinanceMonitor} from "55ec80ca780b07ed"
Insert cell
viewof filteredLeague = advancedSingleSelector("league");
Insert cell
viewof selectedTeam = advancedSingleSelector("club");
Insert cell
selectedClub = selectedTeam[0]["club"]
Insert cell
viewof playersToSell = Inputs.table(
allPlayers.filter(p =>
p.club === selectedClub
),
{
width: "100%",
multiple: true,
maxHeight: 400,
columns: ["name", "overall", "value", "wage"]
}
)
Insert cell
viewof transferTargets = advancedPlayerFinder({selectedLeagues:filteredLeague, selectedTeam: selectedTeam[0]["club"]});
Insert cell
viewof financeDashboard = teamFinanceMonitor({
roster: selectedClub ? allPlayers.filter(p => p.club === selectedClub) : [],
sellList: playersToSell,
purchaseList: transferTargets
})
Insert cell
d3 = require("d3")
Insert cell
{
const preRoster = allPlayers.filter(p => p.club === selectedClub)
const remainingPlayers = preRoster.filter(p => !playersToSell.includes(p));
const roster = [...remainingPlayers, ...transferTargets];

// Check if roster data exists
if (!roster || roster.length === 0) {
return html`<div class="no-data">No roster data available</div>`;
}

// Sort players by value (descending) and take top 20 for better visibility
const sortedPlayers = [...roster]
.sort((a, b) => (b.value || b.marketValue || 0) - (a.value || a.marketValue || 0))
.slice(0, 20);

// Set up dimensions
const width = 800;
const height = 500;
const margin = {top: 40, right: 30, bottom: 100, left: 60};
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;

// Create SVG container
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; background: #f8f9fa;");

// Create chart group
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

// Create scales
const x = d3.scaleBand()
.domain(sortedPlayers.map(p => p.name))
.range([0, innerWidth])
.padding(0.2);

const yValue = d3.scaleLinear()
.domain([0, d3.max(sortedPlayers, p => p.value || p.marketValue || 0) * 1.1])
.range([innerHeight, 0]);

const yWage = d3.scaleLinear()
.domain([0, d3.max(sortedPlayers, p => p.wage || p.salary || 0) * 1.1])
.range([innerHeight, 0]);

// Draw value bars
g.selectAll(".value-bar")
.data(sortedPlayers)
.join("rect")
.attr("class", "value-bar")
.attr("x", d => x(d.name))
.attr("y", d => yValue(d.value || d.marketValue || 0))
.attr("width", x.bandwidth() / 2)
.attr("height", d => innerHeight - yValue(d.value || d.marketValue || 0))
.attr("fill", "#4e79a7")
.append("title")
.text(d => `${d.name}: $${((d.value || d.marketValue || 0)/1000000).toFixed(2)}M`);

// Draw wage bars (positioned next to value bars)
g.selectAll(".wage-bar")
.data(sortedPlayers)
.join("rect")
.attr("class", "wage-bar")
.attr("x", d => x(d.name) + x.bandwidth() / 2)
.attr("y", d => yWage(d.wage || d.salary || 0))
.attr("width", x.bandwidth() / 2)
.attr("height", d => innerHeight - yWage(d.wage || d.salary || 0))
.attr("fill", "#e15759")
.append("title")
.text(d => `${d.name}: $${((d.wage || d.salary || 0)/1000).toFixed(0)}k/wk`);

// Add x-axis with player names
g.append("g")
.attr("transform", `translate(0,${innerHeight})`)
.call(d3.axisBottom(x))
.selectAll("text")
.attr("y", 10)
.attr("x", -5)
.attr("text-anchor", "end")
.attr("transform", "rotate(-45)");

// Add left y-axis for value
g.append("g")
.call(d3.axisLeft(yValue).tickFormat(d => `$${(d/1000000).toFixed(1)}M`))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("x", -innerHeight/2)
.attr("text-anchor", "middle")
.text("Market Value");

// Add right y-axis for wage
g.append("g")
.attr("transform", `translate(${innerWidth},0)`)
.call(d3.axisRight(yWage).tickFormat(d => `$${(d/1000).toFixed(0)}k`))
.append("text")
.attr("transform", "rotate(90)")
.attr("y", 50)
.attr("x", innerHeight/2)
.attr("text-anchor", "middle")
.text("Weekly Wage");

// Add title
svg.append("text")
.attr("x", width/2)
.attr("y", 20)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.text("Player Values and Wages");

// Add legend
const legend = svg.append("g")
.attr("transform", `translate(${width - 180},30)`);

legend.append("rect")
.attr("width", 15)
.attr("height", 15)
.attr("fill", "#4e79a7");

legend.append("text")
.attr("x", 20)
.attr("y", 12)
.text("Market Value");

legend.append("rect")
.attr("y", 20)
.attr("width", 15)
.attr("height", 15)
.attr("fill", "#e15759");

legend.append("text")
.attr("x", 20)
.attr("y", 32)
.text("Weekly Wage");

return svg.node();
}
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