matchup_chart_player = {
const team_select = chart_type ? 'off_team_name' : 'def_team_name';
const x_player_field = player_chart_type ? 'DEF_PLAYER_NAME' : 'OFF_PLAYER_NAME';
const x_player_field_headshot = player_chart_type ? 'DEF_PLAYER_HEADSHOT' : 'OFF_PLAYER_HEADSHOT';
const x_player_display = player_chart_type ? 'DEF_PLAYER_NAME_LAST' : 'OFF_PLAYER_NAME_LAST';
const player_type_select = player_chart_type ? 'OFF_PLAYER_NAME' : 'DEF_PLAYER_NAME';
const x_rect_start = player_chart_type ? 'matchup_min_start' : 'matchup_min_start_def';
const x_rect_end = player_chart_type ? 'matchup_min_end' : 'matchup_min_end_def';
const rect_opacity = player_chart_type ? 'pct_matchup_time_scaled' : 'pct_matchup_time_scaled_def';
const width = 928;
const marginTop = 30;
const marginRight = 10;
const marginBottom = 50;
const marginLeft = 150;
const display_cutoff = 0.75;
const selected_matchup_player = matchups.filter(d => selected_matchup_games.includes(d.game_id)
&& d[player_type_select] === selectedPlayer);
const off_team_color = selected_matchup_player[0].off_team_color;
const def_team_color = selected_matchup_player[0].def_team_color;
const team_fill_color = player_chart_type ? def_team_color :off_team_color;
const team_fill_color_lighter = lightenColor(team_fill_color, 25);
const game_list = selected_matchup_player.map(d => d.game_number).sort();
const bar_width_factor = 50;
const height = game_list[0].length * bar_width_factor + marginTop + marginBottom;
const x = d3.scaleLinear()
.domain([0, d3.max(selected_matchup_player, d => d[x_rect_end])])
.range([marginLeft, width - marginRight]);
const y = d3.scaleBand()
.domain(game_list)
.range([marginTop, height - marginBottom])
.padding(.65);
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
const tooltip = d3.select("body")
.append("div")
.attr("class", "toolTip")
.style("position", "absolute")
.style("visibility", "hidden")
.text("Placeholder")
.style("font-family", "Roboto, sans-serif")
.style("font-size", "11px")
.style("font-weight", "500")
.attr("class", "tooltip-box")
.style("background-color", "white")
.style("border", "1px solid black")
.style("border-radius", "5px")
.style("padding", "2px");
svg.append("text")
.attr("x", width / 2)
.attr("y", height - marginBottom / 2 + 5)
.attr("text-anchor", "middle")
.attr("fill", "black")
.style("font-family", "'Roboto', sans-serif")
.style("font-size", "14px")
.style("font-weight", "500")
.text("Minutes");
const groups = svg.append("g")
.selectAll("g")
.data(selected_matchup_player)
.join("g")
.attr("transform", d => `translate(${x(d[x_rect_start])}, ${y(d.game_number)})`);
groups.each(function(d) {
const group = d3.select(this)
.on("mouseover", function(event, d) {
tooltip
.style("visibility", "visible")
.html(`${d.DEF_PLAYER_NAME} defended ${(d.OFF_PLAYER_NAME)} for <b>${((d.MATCHUP_MIN).toFixed(1))} Minutes</b>. That made up <b>${(d.percentage_total_time_both_on*100).toFixed(1)}%</b> of the time both players were on`)
d3.selectAll('rect')
.attr("fill", '#909090');
d3.selectAll(`.rect-player-${d[x_player_field].replace(/\s+/g, '-')}`)
.attr("fill", team_fill_color)
.attr("opacity", 1);
svg.circles
.filter(x => x[x_player_field] !== d[x_player_field])
.select("image")
.attr("filter", "grayscale(0.9)")
svg.circles
.filter(x => x[x_player_field] !== d[x_player_field])
.select('circle')
.attr("stroke", '#404040')
d3.selectAll(`.text-player-${d[x_player_field].replace(/\s+/g, '-')}`)
.attr("fill", 'white');
d3.selectAll(`.circle-player-playerChart-${d.DEF_PLAYER_NAME.replace(/\s+/g, '-')}`)
.attr("fill", team_fill_color_lighter)
.attr("opacity", 1)
.attr("stroke", team_fill_color)
.attr("r", radius*1.5)
.raise();
d3.selectAll(`.headshot-player-playerChart-${d.DEF_PLAYER_NAME.replace(/\s+/g, '-')}`)
.attr("width", radius*2*1.5)
.attr("height", radius*2*1.5)
.attr("opacity", 1)
.attr("filter", "grayscale(0)")
.attr("x", -radius*1.5)
.attr("y", -radius*1.5*1.04)
.attr("clip-path", "circle(" + radius*1.5 + "px at " + radius*1.5 + "px " + radius*1.5 + "px)" )
.raise();
})
.on("mouseout", function(event, d) {
tooltip
.style("visibility", "hidden")
d3.selectAll('rect')
.attr("fill", team_fill_color)
.attr("opacity", d => d[rect_opacity]);
svg.circles
.select('circle')
.attr("fill", background_fill)
.attr("stroke", team_fill_color)
.attr('opacity', x => x.MATCHUP_MIN > display_cutoff ? 1 : 0)
.attr("r", radius);
svg.circles
.select("image")
.attr("width", radius*2)
.attr("height", radius*2)
.attr('opacity', x => x.MATCHUP_MIN > display_cutoff ? 1 : 0)
.attr("filter", "grayscale(0)")
.attr("x", -radius)
.attr("y", -radius*1.04)
.attr("clip-path", "circle(" + radius + "px at " + radius + "px " + radius + "px)" );
d3.selectAll(`.text-player-${d.DEF_PLAYER_NAME.replace(/\s+/g, '-')}`)
.attr("fill", d => d[rect_opacity] > 0.3 ? 'white' : 'white');
})
.on('mousemove', (event) => {
tooltip.style("top", (event.pageY - 50) + "px").style("left", (event.pageX + 10) + "px")}
);
group.append("rect")
.attr("class", `rect-player-${d[x_player_field].replace(/\s+/g, '-')}`)
.attr("height", y.bandwidth())
.attr("width", d => x(d.MATCHUP_MIN) - x(0))
.attr("fill", team_fill_color)
.attr("opacity", d => d[rect_opacity]);
if (d.MATCHUP_MIN > display_cutoff) {
group.append("text")
.attr("class", `text-player-${d[x_player_field].replace(/\s+/g, '-')}`)
.attr("x", d => (x(d.MATCHUP_MIN) - x(0)) / 2)
.attr("y", y.bandwidth() / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(d[x_player_display])
.attr("fill", d[rect_opacity] > 0.3 ? 'white' : 'white')
.style("font-size", "12px")
.style("font-weight", "700")
.style("font-family", "Roboto Slab");
}
});
svg.append("g")
.classed("circles", true)
svg.circles = svg.select("g.circles")
.selectAll(".node")
.data(selected_matchup_player, d => d[x_player_field])
.enter()
.append("g")
.attr("transform",
d => `translate(${x(d[x_rect_start] + d.MATCHUP_MIN/2)},
${y(d.game_number) - y.bandwidth()*.625})`);
const background_fill = "#ECECEC";
const radius = y.bandwidth()/2;
svg.circles.each(function (d) {
const currentNode = d3.select(this)
const labelText = d[x_player_field];
const headshot = d[x_player_field_headshot];
if(d.MATCHUP_MIN > display_cutoff){
currentNode.append('circle')
.attr("r", radius)
.attr('opacity', d.MATCHUP_MIN > display_cutoff ? 1 : 0)
.attr("fill", background_fill)
.attr("stroke-width", bar_width_factor*0.025)
.attr("stroke", team_fill_color)
.attr("class", d => `circle-player-playerChart-${d[x_player_field].replace(/\s+/g, '-')}`);
currentNode.append("image")
.attr("class", "headshot")
.attr("class", d => `headshot-player-playerChart-${d[x_player_field].replace(/\s+/g, '-')}`)
.attr("height", radius * 2)
.attr('opacity', d.MATCHUP_MIN > display_cutoff ? 1 : 0)
.attr("clip-path", d=> "circle(" + radius + "px at " + radius + "px " + radius + "px)" )
.attr("href", headshot)
.attr("x", -radius)
.attr("y", -radius*1.04)
.attr("width", radius * 2)
.attr("preserveAspectRatio", "xMidYMid slice");
}
});
const xAxis = svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x).ticks(width / 100, "s"));
xAxis.call(g => g.selectAll(".domain").remove());
xAxis.selectAll(".tick text")
.style("font-family", "'Roboto Mono', monospace")
.style("font-size", "12px");
const yAxis = svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y).tickSizeOuter(0));
yAxis.call(g => g.selectAll(".domain").remove());
yAxis.selectAll(".tick text")
.style("font-family", "'Roboto', sans-serif")
.style("font-weight", "700")
.style("font-size", "12px")
.style("fill", "#404040");
return Object.assign(svg.node());
}