Percentile Visualization Tool

Percentile Visualization Tool

Summary Statistics

No data to display.

'; return; } const stats = [ { label: 'Count', value: data.length.toLocaleString() }, { label: 'Min', value: formatCurrency(d3.min(data)) }, { label: '10th Percentile', value: formatCurrency(d3.quantile(data, 0.10)) }, { label: '25th Percentile (Q1)', value: formatCurrency(d3.quantile(data, 0.25)) }, { label: 'Median (50th)', value: formatCurrency(d3.median(data)) }, { label: '75th Percentile (Q3)', value: formatCurrency(d3.quantile(data, 0.75)) }, { label: '90th Percentile', value: formatCurrency(d3.quantile(data, 0.90)) }, { label: 'Max', value: formatCurrency(d3.max(data)) }, { label: 'Mean', value: formatCurrency(d3.mean(data)) }, ]; summaryStatsEl.innerHTML = stats.map(stat => `
${stat.label} ${stat.value}
`).join(''); } function renderVisualization(data) { const svg = d3.select("#visualization-svg"); svg.selectAll("*").remove(); if (data.length === 0) return; const container = document.getElementById('visualization-container'); const width = container.clientWidth; const height = container.clientHeight; const margin = { top: 20, right: 50, bottom: 50, left: 80 }; const y = d3.scaleLinear() .domain(d3.extent(data)).nice() .range([height - margin.bottom, margin.top]); const yAxis = d3.axisLeft(y).tickFormat(d => `$${d/1000}k`); svg.append("g") .attr("transform", `translate(${margin.left},0)`) .call(yAxis) .call(g => g.select(".domain").remove()); const chartWidth = 150; const chartCenter = margin.left + (width - margin.left - margin.right) / 2; // Violin Plot const kde = kernelDensityEstimator(kernelEpanechnikov(7), y.ticks(40)); const density = kde(data); const x = d3.scaleLinear() .range([0, chartWidth / 2]) .domain([0, d3.max(density, d => d.y)]); svg.append("path") .datum(density) .attr("transform", `translate(${chartCenter},0)`) .attr("fill", "#bfdbfe") .attr("stroke", "#93c5fd") .attr("stroke-width", 1.5) .attr("d", d3.area() .x0(d => -x(d.y)) .x1(d => x(d.y)) .y(d => y(d.x0)) .curve(d3.curveCatmullRom) ); // Box Plot const q1 = d3.quantile(data, 0.25); const median = d3.quantile(data, 0.5); const q3 = d3.quantile(data, 0.75); const boxWidth = 40; svg.append("rect") .attr("x", chartCenter - boxWidth / 2) .attr("y", y(q3)) .attr("height", y(q1) - y(q3)) .attr("width", boxWidth) .attr("stroke", "#6b7280") .attr("fill", "#d1d5db"); svg.append("line") .attr("x1", chartCenter - boxWidth / 2) .attr("x2", chartCenter + boxWidth / 2) .attr("y1", y(median)) .attr("y2", y(median)) .attr("stroke", "#1f2937") .style("stroke-width", "2px"); // Jittered dots svg.selectAll("indPoints") .data(data) .enter() .append("circle") .attr("cy", d => y(d)) .attr("cx", d => chartCenter - chartWidth/4 + Math.random()*(chartWidth/2)) .attr("r", 3) .style("fill", "#3b82f6") .attr("fill-opacity", 0.3); } // Helper for Violin Plot function kernelDensityEstimator(kernel, X) { return function(V) { return X.map(function(x) { return [x, d3.mean(V, function(v) { return kernel(x - v); })]; }); }; } function kernelEpanechnikov(k) { return function(v) { return Math.abs(v /= k) <= 1 ? 0.75 * (1 - v * v) / k : 0; }; } window.updateDataAndVisualize = function() { currentData = parseCSV(dataInputEl.value); if (currentData.length === 0) { alert('No valid numeric data found. Please check your CSV.'); return; } currentData.sort(d3.ascending); updateSummary(currentData); renderVisualization(currentData); changeTab('dashboard'); } window.changeTab = function(tabName) { activeTab = tabName; Object.values(tabButtons).forEach(btn => btn.classList.remove('active')); Object.values(tabContents).forEach(content => content.style.display = 'none'); tabButtons[tabName].classList.add('active'); tabContents[tabName].style.display = 'block'; updateNavButtons(); if (tabName === 'dashboard') { setTimeout(() => renderVisualization(currentData), 10); } } function updateNavButtons() { navButtons.prev.style.visibility = (activeTab === 'dashboard') ? 'hidden' : 'visible'; navButtons.next.style.visibility = (activeTab === 'dataConfig') ? 'hidden' : 'visible'; } window.navigateTabs = function(direction) { if (direction === 'next' && activeTab === 'dashboard') changeTab('dataConfig'); else if (direction === 'prev' && activeTab === 'dataConfig') changeTab('dashboard'); } window.downloadPDF = function() { const { jsPDF } = window.jspdf; const dashboardContent = document.getElementById('dashboard-content'); const downloadBtn = document.getElementById('download-pdf-btn'); if (!dashboardContent) return; downloadBtn.style.visibility = 'hidden'; html2canvas(dashboardContent, { scale: 2, backgroundColor: '#ffffff' }).then(canvas => { const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'landscape', unit: 'px', format: [canvas.width, canvas.height] }); pdf.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height); pdf.save('percentile_visualization.pdf'); downloadBtn.style.visibility = 'visible'; }).catch(err => { console.error("PDF generation failed:", err); downloadBtn.style.visibility = 'visible'; }); } function initializeTool() { dataInputEl.value = sampleData; updateDataAndVisualize(); changeTab('dashboard'); window.addEventListener('resize', () => { if (activeTab === 'dashboard') { renderVisualization(currentData); } }); } initializeTool(); });
Scroll to Top