|
|
|
|
|
======= |
|
|
|
|
|
const premiumPlayers = { |
|
|
"top-scorer": { |
|
|
name: "Erling Haaland", |
|
|
team: "Manchester City", |
|
|
image: "http://static.photos/sport/200x200/101", |
|
|
stats: [ |
|
|
{ stat: 'Goals', value: 94, max: 100 }, |
|
|
{ stat: 'Assists', value: 31, max: 50 }, |
|
|
{ stat: 'Pass Accuracy', value: 85, max: 100 }, |
|
|
{ stat: 'Dribbles', value: 68, max: 100 }, |
|
|
{ stat: 'Tackles', value: 22, max: 50 }, |
|
|
{ stat: 'Duels Win %', value: 71, max: 100 }, |
|
|
{ stat: 'Expected Goals', value: 88, max: 100 }, |
|
|
{ stat: 'Shot Conversion', value: 92, max: 100 }, |
|
|
{ stat: 'Aerial Duels', value: 84, max: 100 } |
|
|
], |
|
|
marketValue: "β¬180M", |
|
|
form: "8.4/10", |
|
|
aiPrediction: "92% chance of Golden Boot" |
|
|
}, |
|
|
"top-assists": { |
|
|
name: "Kevin De Bruyne", |
|
|
team: "Manchester City", |
|
|
image: "http://static.photos/sport/200x200/102", |
|
|
stats: [ |
|
|
{ stat: 'Goals', value: 21, max: 100 }, |
|
|
{ stat: 'Assists', value: 49, max: 50 }, |
|
|
{ stat: 'Pass Accuracy', value: 93, max: 100 }, |
|
|
{ stat: 'Dribbles', value: 82, max: 100 }, |
|
|
{ stat: 'Tackles', value: 37, max: 50 }, |
|
|
{ stat: 'Duels Win %', value: 63, max: 100 }, |
|
|
{ stat: 'Key Passes', value: 95, max: 100 }, |
|
|
{ stat: 'Through Balls', value: 91, max: 100 }, |
|
|
{ stat: 'Chances Created', value: 89, max: 100 } |
|
|
], |
|
|
marketValue: "β¬90M", |
|
|
form: "8.7/10", |
|
|
aiPrediction: "Top 3 in Assists next season" |
|
|
}, |
|
|
"top-passer": { |
|
|
name: "Rodri", |
|
|
team: "Manchester City", |
|
|
image: "http://static.photos/sport/200x200/103", |
|
|
stats: [ |
|
|
{ stat: 'Goals', value: 15, max: 100 }, |
|
|
{ stat: 'Assists', value: 22, max: 50 }, |
|
|
{ stat: 'Pass Accuracy', value: 96, max: 100 }, |
|
|
{ stat: 'Dribbles', value: 86, max: 100 }, |
|
|
{ stat: 'Tackles', value: 72, max: 50 }, |
|
|
{ stat: 'Duels Win %', value: 79, max: 100 }, |
|
|
{ stat: 'Long Passes', value: 94, max: 100 }, |
|
|
{ stat: 'Passes Completed', value: 97, max: 100 }, |
|
|
{ stat: 'Possession Won', value: 88, max: 100 } |
|
|
], |
|
|
marketValue: "β¬110M", |
|
|
form: "8.9/10", |
|
|
aiPrediction: "Best Defensive Midfielder in Europe" |
|
|
}, |
|
|
"top-defender": { |
|
|
name: "Virgil van Dijk", |
|
|
team: "Liverpool", |
|
|
image: "http://static.photos/sport/200x200/104", |
|
|
stats: [ |
|
|
{ stat: 'Goals', value: 7, max: 100 }, |
|
|
{ stat: 'Assists', value: 5, max: 50 }, |
|
|
{ stat: 'Pass Accuracy', value: 92, max: 100 }, |
|
|
{ stat: 'Dribbles', value: 58, max: 100 }, |
|
|
{ stat: 'Tackles', value: 83, max: 50 }, |
|
|
{ stat: 'Duels Win %', value: 86, max: 100 }, |
|
|
{ stat: 'Aerial Duels', value: 95, max: 100 }, |
|
|
{ stat: 'Interceptions', value: 89, max: 100 }, |
|
|
{ stat: 'Clearances', value: 91, max: 100 }, |
|
|
{ stat: 'Blocks', value: 87, max: 100 }, |
|
|
{ stat: 'Recoveries', value: 93, max: 100 } |
|
|
], |
|
|
marketValue: "β¬75M", |
|
|
form: "8.6/10", |
|
|
aiPrediction: "Return to peak defensive form" |
|
|
}, |
|
|
"top-creator": { |
|
|
name: "Lionel Messi", |
|
|
team: "Inter Miami", |
|
|
image: "http://static.photos/sport/200x200/105", |
|
|
stats: [ |
|
|
{ stat: 'Goals', value: 28, max: 100 }, |
|
|
{ stat: 'Assists', value: 42, max: 50 }, |
|
|
{ stat: 'Pass Accuracy', value: 89, max: 100 }, |
|
|
{ stat: 'Dribbles', value: 95, max: 100 }, |
|
|
{ stat: 'Tackles', value: 26, max: 50 }, |
|
|
{ stat: 'Duels Win %', value: 72, max: 100 }, |
|
|
{ stat: 'Key Passes', value: 97, max: 100 }, |
|
|
{ stat: 'Chances Created', value: 94, max: 100 }, |
|
|
{ stat: 'Free Kicks', value: 93, max: 100 }, |
|
|
{ stat: 'Dribbles Success', value: 91, max: 100 } |
|
|
], |
|
|
marketValue: "β¬60M", |
|
|
form: "8.8/10", |
|
|
aiPrediction: "Top creator in MLS with 15+ assists" |
|
|
}, |
|
|
"top-goalkeeper": { |
|
|
name: "Alisson Becker", |
|
|
team: "Liverpool", |
|
|
image: "http://static.photos/sport/200x200/106", |
|
|
stats: [ |
|
|
{ stat: 'Clean Sheets', value: 78, max: 100 }, |
|
|
{ stat: 'Saves', value: 86, max: 100 }, |
|
|
{ stat: 'Pass Accuracy', value: 88, max: 100 }, |
|
|
{ stat: 'Goals Conceded', value: 24, max: 100 }, |
|
|
{ stat: 'Penalty Saves', value: 82, max: 100 }, |
|
|
{ stat: 'Claims', value: 91, max: 100 }, |
|
|
{ stat: 'Sweeper Actions', value: 87, max: 100 }, |
|
|
{ stat: 'Distribution', value: 89, max: 100 }, |
|
|
{ stat: 'Reflex Saves', value: 94, max: 100 }, |
|
|
{ stat: 'Aerial Duels', value: 92, max: 100 }, |
|
|
{ stat: 'Command Area', value: 95, max: 100 } |
|
|
], |
|
|
marketValue: "β¬70M", |
|
|
form: "8.5/10", |
|
|
aiPrediction: "Golden Glove contender in Premier League" |
|
|
} |
|
|
}; |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
loadPremiumPlayerData('top-scorer'); |
|
|
}); |
|
|
|
|
|
function loadPremiumPlayerData(playerType) { |
|
|
|
|
|
const placeholder = document.getElementById('chart-placeholder'); |
|
|
placeholder.innerHTML = '<div class="flex items-center gap-3"><div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div><span>Loading premium analytics...</span></div>'; |
|
|
placeholder.style.display = 'block'; |
|
|
|
|
|
|
|
|
const liveIndicator = document.querySelector('.flex.items-center.gap-2'); |
|
|
if (liveIndicator) { |
|
|
liveIndicator.classList.add('animate-pulse'); |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
placeholder.style.display = 'none'; |
|
|
|
|
|
|
|
|
const playerData = premiumPlayers[playerType]; |
|
|
|
|
|
|
|
|
drawPremiumRadarChart(playerData); |
|
|
|
|
|
|
|
|
updatePlayerInfo(playerData); |
|
|
}, 600); |
|
|
} |
|
|
|
|
|
function updatePlayerInfo(playerData) { |
|
|
|
|
|
const infoCard = document.querySelector('.player-info-card'); |
|
|
if (infoCard) { |
|
|
infoCard.innerHTML = ` |
|
|
<div class="flex items-center gap-4 mb-4"> |
|
|
<img src="${playerData.image}" alt="${playerData.name}" class="w-16 h-16 rounded-xl object-cover"> |
|
|
<div> |
|
|
<h3 class="text-xl font-bold">${playerData.name}</h3> |
|
|
<p class="text-gray-600">${playerData.team}</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="grid grid-cols-2 gap-4 text-sm"> |
|
|
<div class="bg-blue-50 p-3 rounded-lg"> |
|
|
<div class="text-blue-600 font-semibold">Market Value</div> |
|
|
<div class="text-gray-800">${playerData.marketValue}</div> |
|
|
<div class="bg-green-50 p-3 rounded-lg"> |
|
|
<div class="text-green-600 font-semibold">Current Form</div> |
|
|
<div class="text-gray-800">${playerData.form}</div> |
|
|
</div> |
|
|
<div class="mt-4 bg-purple-50 p-3 rounded-lg"> |
|
|
<div class="text-purple-600 font-semibold">AI Prediction</div> |
|
|
<div class="text-gray-800">${playerData.aiPrediction}</div> |
|
|
`; |
|
|
} |
|
|
} |
|
|
|
|
|
function drawPremiumRadarChart(playerData) { |
|
|
|
|
|
d3.select("#player-radar-chart svg").remove(); |
|
|
|
|
|
const data = playerData.stats; |
|
|
const playerName = playerData.name; |
|
|
const playerTeam = playerData.team; |
|
|
|
|
|
|
|
|
const margin = { top: 120, right: 120, bottom: 120, left: 120 }; |
|
|
const width = Math.min(800, window.innerWidth - 80) - margin.left - margin.right; |
|
|
const height = Math.min(width, window.innerHeight - margin.top - margin.bottom - 40); |
|
|
const radius = Math.min(width, height) / 2; |
|
|
|
|
|
|
|
|
const svg = d3.select("#player-radar-chart") |
|
|
.append("svg") |
|
|
.attr("width", width + margin.left + margin.right) |
|
|
.attr("height", height + margin.top + margin.bottom) |
|
|
.append("g") |
|
|
.attr("transform", `translate(${margin.left + width/2}, ${margin.top + height/2})`); |
|
|
|
|
|
|
|
|
const levels = 6; |
|
|
const angleSlice = Math.PI * 2 / data.length; |
|
|
|
|
|
|
|
|
const rScale = d3.scaleLinear() |
|
|
.range([0, radius]) |
|
|
.domain([0, 100]); |
|
|
|
|
|
|
|
|
for (let level = 1; level <= levels; level++) { |
|
|
const levelFactor = radius * level / levels; |
|
|
|
|
|
|
|
|
svg.selectAll(".premium-levels") |
|
|
.data(data) |
|
|
.enter() |
|
|
.append("line") |
|
|
.attr("x1", (d, i) => levelFactor * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.attr("y1", (d, i) => levelFactor * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.attr("x2", (d, i) => levelFactor * Math.cos(angleSlice * (i + 1) - Math.PI / 2)) |
|
|
.attr("y2", (d, i) => levelFactor * Math.sin(angleSlice * (i + 1) - Math.PI / 2)) |
|
|
.attr("stroke", level === levels ? "#3b82f6" : "#e2e8f0") |
|
|
.attr("stroke-width", level === levels ? 2 : 1) |
|
|
.attr("stroke-opacity", 0.6); |
|
|
|
|
|
|
|
|
svg.append("circle") |
|
|
.attr("cx", 0) |
|
|
.attr("cy", 0) |
|
|
.attr("r", levelFactor) |
|
|
.attr("fill", "none") |
|
|
.attr("stroke", level === levels ? "#3b82f6" : "#e2e8f0") |
|
|
.attr("stroke-width", level === levels ? 2 : 0.5) |
|
|
.attr("stroke-opacity", 0.4); |
|
|
} |
|
|
|
|
|
|
|
|
const axis = svg.selectAll(".premium-axis") |
|
|
.data(data) |
|
|
.enter() |
|
|
.append("g") |
|
|
.attr("class", "premium-axis"); |
|
|
|
|
|
axis.append("line") |
|
|
.attr("x1", 0) |
|
|
.attr("y1", 0) |
|
|
.attr("x2", (d, i) => radius * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.attr("y2", (d, i) => radius * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.attr("stroke", "#94a3b8") |
|
|
.attr("stroke-width", 1) |
|
|
.attr("stroke-opacity", 0.5); |
|
|
|
|
|
|
|
|
axis.append("text") |
|
|
.attr("class", "premium-axis-label") |
|
|
.attr("x", (d, i) => (radius + 30) * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.attr("y", (d, i) => (radius + 30) * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.attr("dy", "0.35em") |
|
|
.attr("text-anchor", "middle") |
|
|
.text(d => d.stat) |
|
|
.attr("fill", "#475569") |
|
|
.attr("font-size", "13px") |
|
|
.attr("font-weight", "600"); |
|
|
|
|
|
|
|
|
const radarLine = d3.line() |
|
|
.x((d, i) => rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.y((d, i) => rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.curve(d3.curveCatmullRomClosed); |
|
|
|
|
|
|
|
|
const radarGroup = svg.append("g") |
|
|
.attr("class", "premium-radar-group"); |
|
|
|
|
|
|
|
|
radarGroup.append("path") |
|
|
.datum(data) |
|
|
.attr("class", "premium-radar-area") |
|
|
.attr("d", radarLine) |
|
|
.attr("fill", "url(#radar-gradient)") |
|
|
.attr("stroke", "url(#radar-line-gradient)") |
|
|
.attr("stroke-width", 3); |
|
|
|
|
|
|
|
|
radarGroup.selectAll(".premium-radar-circle") |
|
|
.data(data) |
|
|
.enter() |
|
|
.append("circle") |
|
|
.attr("class", "premium-radar-circle") |
|
|
.attr("cx", (d, i) => rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.attr("cy", (d, i) => rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.attr("r", 6) |
|
|
.attr("fill", "url(#point-gradient)") |
|
|
.attr("stroke", "#ffffff") |
|
|
.attr("stroke-width", 2); |
|
|
|
|
|
|
|
|
svg.append("text") |
|
|
.attr("class", "premium-player-name") |
|
|
.attr("x", 0) |
|
|
.attr("y", -height/2 + 40) |
|
|
.attr("text-anchor", "middle") |
|
|
.text(`${playerName} | ${playerTeam}") |
|
|
.attr("fill", "#1e293b") |
|
|
.attr("font-size", "22px") |
|
|
.attr("font-weight", "bold"); |
|
|
|
|
|
// Enhanced value labels |
|
|
radarGroup.selectAll(".premium-value-label") |
|
|
.data(data) |
|
|
.enter() |
|
|
.append("text") |
|
|
.attr("class", "premium-value-label") |
|
|
.attr("x", (d, i) => (rScale(d.value) + 20) * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.attr("y", (d, i) => (rScale(d.value) + 20) * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.attr("dy", "0.35em") |
|
|
.attr("text-anchor", "middle") |
|
|
.text(d => d.value) |
|
|
.attr("fill", "#3b82f6") |
|
|
.attr("font-size", "14px") |
|
|
.attr("font-weight", "bold"); |
|
|
} |
|
|
|
|
|
function initializePremiumCharts() { |
|
|
// Initialize the performance trend chart |
|
|
const ctx = document.getElementById('performanceChart'); |
|
|
if (ctx) { |
|
|
const chartLoader = document.getElementById('chartLoader'); |
|
|
if (chartLoader) chartLoader.style.display = 'none'; |
|
|
|
|
|
new Chart(ctx, { |
|
|
type: 'line', |
|
|
data: { |
|
|
labels: ['Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb'], |
|
|
datasets: [{ |
|
|
label: 'Form Rating', |
|
|
data: [7.8, 8.2, 8.6, 8.9, 8.7, 8.8, 9.1], |
|
|
borderColor: '#3b82f6', |
|
|
backgroundColor: 'rgba(59, 130, 246, 0.1), |
|
|
tension: 0.4, |
|
|
fill: true, |
|
|
pointBackgroundColor: '#3b82f6', |
|
|
pointBorderColor: '#ffffff', |
|
|
pointBorderWidth: 2, |
|
|
pointRadius: 4, |
|
|
pointHoverRadius: 6 |
|
|
}] |
|
|
}, |
|
|
options: { |
|
|
responsive: true, |
|
|
plugins: { |
|
|
legend: { |
|
|
display: false |
|
|
}, |
|
|
tooltip: { |
|
|
backgroundColor: 'rgba(0, 0, 0, 0.8), |
|
|
titleColor: '#ffffff', |
|
|
bodyColor: '#ffffff' |
|
|
} |
|
|
}, |
|
|
scales: { |
|
|
y: { |
|
|
beginAtZero: false, |
|
|
min: 7, |
|
|
max: 10, |
|
|
grid: { |
|
|
color: 'rgba(0, 0, 0, 0.05), |
|
|
border: { |
|
|
dash: [4, 4] |
|
|
} |
|
|
}, |
|
|
x: { |
|
|
grid: { |
|
|
display: false |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
} |
|
|
function drawRadarChart(playerData) { |
|
|
// Clear previous chart |
|
|
d3.select("#player-radar-chart svg").remove(); |
|
|
|
|
|
const data = playerData.stats; |
|
|
const playerName = playerData.name; |
|
|
const playerTeam = playerData.team; |
|
|
|
|
|
// Set up dimensions |
|
|
const margin = { top: 100, right: 100, bottom: 100, left: 100 }; |
|
|
const width = Math.min(700, window.innerWidth - 40) - margin.left - margin.right; |
|
|
const height = Math.min(width, window.innerHeight - margin.top - margin.bottom - 20); |
|
|
const radius = Math.min(width, height) / 2; |
|
|
|
|
|
// Create SVG |
|
|
const svg = d3.select("#player-radar-chart") |
|
|
.append("svg") |
|
|
.attr("width", width + margin.left + margin.right) |
|
|
.attr("height", height + margin.top + margin.bottom) |
|
|
.append("g") |
|
|
.attr("transform", `translate(${margin.left + width/2}, ${margin.top + height/2})`); |
|
|
|
|
|
// Levels and axes |
|
|
const levels = 5; |
|
|
const angleSlice = Math.PI * 2 / data.length; |
|
|
|
|
|
// Scale for the radius |
|
|
const rScale = d3.scaleLinear() |
|
|
.range([0, radius]) |
|
|
.domain([0, 100]); |
|
|
|
|
|
// Draw circular grid lines |
|
|
for (let level = 1; level <= levels; level++) { |
|
|
const levelFactor = radius * level / levels; |
|
|
|
|
|
svg.selectAll(".levels") |
|
|
.data(data) |
|
|
.enter() |
|
|
.append("line") |
|
|
.attr("x1", (d, i) => levelFactor * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.attr("y1", (d, i) => levelFactor * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.attr("x2", (d, i) => levelFactor * Math.cos(angleSlice * (i + 1) - Math.PI / 2)) |
|
|
.attr("y2", (d, i) => levelFactor * Math.sin(angleSlice * (i + 1) - Math.PI / 2)) |
|
|
.attr("stroke", "#e2e8f0") |
|
|
.attr("stroke-width", 1); |
|
|
|
|
|
// Draw level circles |
|
|
svg.append("circle") |
|
|
.attr("cx", 0) |
|
|
.attr("cy", 0) |
|
|
.attr("r", levelFactor) |
|
|
.attr("fill", "none") |
|
|
.attr("stroke", "#e2e8f0") |
|
|
.attr("stroke-width", 0.5); |
|
|
} |
|
|
|
|
|
// Draw axes |
|
|
const axis = svg.selectAll(".axis") |
|
|
.data(data) |
|
|
.enter() |
|
|
.append("g") |
|
|
.attr("class", "axis"); |
|
|
|
|
|
axis.append("line") |
|
|
.attr("x1", 0) |
|
|
.attr("y1", 0) |
|
|
.attr("x2", (d, i) => radius * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.attr("y2", (d, i) => radius * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.attr("stroke", "#94a3b8") |
|
|
.attr("stroke-width", 1); |
|
|
|
|
|
// Draw axis labels |
|
|
axis.append("text") |
|
|
.attr("class", "axis-label") |
|
|
.attr("x", (d, i) => (radius + 20) * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.attr("y", (d, i) => (radius + 20) * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.attr("dy", "0.35em") |
|
|
.attr("text-anchor", "middle") |
|
|
.text(d => d.stat) |
|
|
.attr("fill", "#64748b") |
|
|
.attr("font-size", "12px"); |
|
|
|
|
|
// Draw radar area |
|
|
const radarLine = d3.line() |
|
|
.x((d, i) => rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.y((d, i) => rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.curve(d3.curveLinearClosed); |
|
|
|
|
|
// Create a group for the radar |
|
|
const radarGroup = svg.append("g") |
|
|
.attr("class", "radar-group"); |
|
|
|
|
|
// Draw the radar area |
|
|
radarGroup.append("path") |
|
|
.datum(data) |
|
|
.attr("class", "radar-area") |
|
|
.attr("d", radarLine) |
|
|
.attr("fill", "rgba(59, 130, 246, 0.4)") |
|
|
.attr("stroke", "rgba(59, 130, 246, 1)") |
|
|
.attr("stroke-width", 2); |
|
|
|
|
|
// Draw data points |
|
|
radarGroup.selectAll(".radar-circle") |
|
|
.data(data) |
|
|
.enter() |
|
|
.append("circle") |
|
|
.attr("class", "radar-circle") |
|
|
.attr("cx", (d, i) => rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.attr("cy", (d, i) => rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.attr("r", 4) |
|
|
.attr("fill", "rgba(59, 130, 246, 1)") |
|
|
.attr("stroke", "#fff") |
|
|
.attr("stroke-width", 1); |
|
|
|
|
|
// Add player name |
|
|
svg.append("text") |
|
|
.attr("class", "player-name") |
|
|
.attr("x", 0) |
|
|
.attr("y", -height/2 + 30) |
|
|
.attr("text-anchor", "middle") |
|
|
.text(`${playerName} - ${playerTeam}`) |
|
|
.attr("fill", "#1e293b") |
|
|
.attr("font-size", "18px") |
|
|
.attr("font-weight", "bold"); |
|
|
|
|
|
// Add value labels |
|
|
radarGroup.selectAll(".value-label") |
|
|
.data(data) |
|
|
.enter() |
|
|
.append("text") |
|
|
.attr("class", "value-label") |
|
|
.attr("x", (d, i) => (rScale(d.value) + 15) * Math.cos(angleSlice * i - Math.PI / 2)) |
|
|
.attr("y", (d, i) => (rScale(d.value) + 15) * Math.sin(angleSlice * i - Math.PI / 2)) |
|
|
.attr("dy", "0.35em") |
|
|
.attr("text-anchor", "middle") |
|
|
.text(d => d.value) |
|
|
.attr("fill", "#64748b") |
|
|
.attr("font-size", "11px") |
|
|
.attr("font-weight", "bold"); |
|
|
} |
|
|
|