Akk / 1st.html
Yousef1231243135's picture
2nd.html
cad8d47 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Data Analysis</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { max-width: 100vw; }
body {
font-family: 'Inter', sans-serif;
background-color: #ffffff;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
header {
background: linear-gradient(to right, #4299e1, #63b3ed);
padding: 24px 30px;
display: flex; justify-content: space-between; align-items: center;
box-shadow: 0 4px 8px rgba(0,0,0,0.08);
flex-shrink: 0;
min-height: 90px;
}
.filename {
color: white;
font-size: 1.65rem;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 60vw;
letter-spacing: 0.3px;
}
.menu-buttons { display: flex; gap: 14px; }
.menu-buttons button {
background: linear-gradient(to bottom right, #63b3ed, #4299e1);
color: white;
font-size: 1.15rem;
padding: 12px 24px;
border-radius: 8px;
border: none;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
letter-spacing: 0.3px;
}
.menu-buttons button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.menu-buttons button.active {
background: white !important;
color: #2b6cb0 !important;
border: 2px solid white;
box-shadow: 0 2px 8px rgba(0,0,0,0.12);
}
main { padding: 0; flex: 1; overflow: auto; display: flex; flex-direction: column; background-color: #ffffff; }
.table-wrap { width: 100vw; height: 100%; overflow: auto; padding: 24px; background-color: #ffffff; }
.hidden { display: none; }
.box {
text-align: center; padding: 36px; background: white; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05); margin: 36px auto; width: min(720px, 92vw);
}
.status { font-weight: 600; }
.status.loading { color: #22577A; }
.status.error { color: #e53e3e; }
/* Table */
.csv-table {
border-collapse: collapse; border: 3px solid #000; table-layout: fixed;
width: max-content; min-width: calc(100vw - 48px); background-color: #ffffff;
}
.csv-table thead { background: #ffffff; border-bottom: 3px solid #000; position: sticky; top: 0; z-index: 5; }
.csv-table th, .csv-table td {
padding: 10px 12px; border: 2px solid #000; font-size: 0.92rem; color: #2d3748;
white-space: nowrap; width: 9cm; min-width: 9cm; max-width: 9cm; overflow: hidden; text-overflow: ellipsis; background: #ffffff;
}
.csv-table tbody tr:hover td { background-color: #f9f9f9; }
/* Filter row styling */
.csv-table .filter-row td {
background: #f7fafc;
padding: 0 !important;
}
/* Spacer row styling - 5.3cm height */
.csv-table .spacer-row td {
height: 5.3cm !important;
padding: 8px !important;
vertical-align: middle;
background: #fff;
position: relative;
}
/* Chart container styling */
.chart-container {
width: 100%;
height: 100%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.chart-container canvas {
max-width: 100% !important;
max-height: 100% !important;
}
/* Filter trigger */
.dd-trigger {
display: block;
width: 100%;
height: 100%;
padding: 10px 32px 10px 12px;
font-size: 0.92rem;
border: 0;
border-radius: 0;
background: #fff;
outline: none;
cursor: pointer;
text-align: left;
box-sizing: border-box;
position: relative;
}
/* Down arrow icon */
.dd-trigger::after {
content: "▼";
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
font-size: 0.7rem;
color: #333;
pointer-events: none;
}
/* Floating menu */
.float-menu {
position: fixed;
background: #fff;
border: 2px solid #000;
border-radius: 6px;
max-height: 260px;
overflow: auto;
overscroll-behavior: contain;
z-index: 999999;
box-shadow: 0 10px 24px rgba(0,0,0,0.12);
display: none;
min-width: 200px;
}
.float-option {
padding: 8px 10px;
cursor: pointer;
white-space: nowrap;
user-select: none;
border-bottom: 1px solid #000;
}
.float-option:last-child { border-bottom: none; }
.float-option:hover { background: #8A8AFF; }
/* Searchable dropdown styles */
.searchable-dropdown-wrapper {
position: relative;
flex: 1;
}
.searchable-dropdown-input {
width: 100%;
padding: 12px 24px 12px 12px;
font-size: 1rem;
border: 2px solid #cbd5e0;
border-radius: 8px;
background: white;
cursor: pointer;
outline: none;
}
.searchable-dropdown-input::placeholder {
color: #718096;
}
.searchable-dropdown-input:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.searchable-dropdown-arrow {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
font-size: 0.6rem;
color: #4a5568;
transition: transform 0.2s;
}
.searchable-dropdown-arrow.open {
transform: translateY(-50%) rotate(180deg);
}
.searchable-dropdown-menu {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
z-index: 1000;
display: none;
max-height: 300px;
overflow: hidden;
}
.searchable-dropdown-menu.open {
display: block;
}
.searchable-dropdown-search {
padding: 10px;
border-bottom: 1px solid #e2e8f0;
background: #f7fafc;
}
.searchable-dropdown-search input {
width: 100%;
padding: 8px 12px;
border: 1px solid #cbd5e0;
border-radius: 4px;
font-size: 0.9rem;
outline: none;
}
.searchable-dropdown-search input:focus {
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
}
.searchable-dropdown-options {
max-height: 240px;
overflow-y: auto;
overscroll-behavior: contain;
}
.searchable-dropdown-option {
padding: 10px 12px;
cursor: pointer;
transition: background 0.2s;
border-bottom: 1px solid #e2e8f0;
}
.searchable-dropdown-option:last-child {
border-bottom: none;
}
.searchable-dropdown-option:hover {
background: #8A8AFF;
color: white;
}
.searchable-dropdown-option.selected {
background: #e6f3ff;
font-weight: 600;
}
.searchable-dropdown-option.hidden {
display: none;
}
.searchable-dropdown-no-results {
padding: 20px;
text-align: center;
color: #718096;
font-style: italic;
}
/* Comparison chart area */
.comparison-chart-area {
padding: 30px;
background: linear-gradient(135deg, #f7fafc 0%, #ffffff 100%);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
margin-left: 40px;
height: fit-content;
}
.chart-section {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.chart-title {
font-size: 1.5rem;
font-weight: 700;
color: #2d3748;
margin-bottom: 20px;
text-align: center;
}
#comparisonChart {
max-height: 500px;
}
.no-selection-message {
text-align: center;
color: #718096;
font-size: 1.1rem;
padding: 40px;
background: #f7fafc;
border-radius: 8px;
margin: 20px 0;
}
</style>
</head>
<body>
<header>
<div class="filename" id="filename">Loading…</div>
<div class="menu-buttons">
<button id="prepareBtn" class="active" onclick="showPrepare()">Prepare</button>
<button id="predictBtn" onclick="showPredict()">Predict</button>
<button id="reportBtn" onclick="showReport()">Report</button>
</div>
</header>
<main>
<section id="prepareView" class="table-wrap">
<div id="tableMount">
<div class="box">
<div class="status loading">Loading CSV data…</div>
</div>
</div>
</section>
<section id="predictView" class="table-wrap hidden">
<div style="padding: 36px; max-width: 100%; margin: 0; display: flex; gap: 40px;">
<div style="flex: 0 0 auto;">
<h2 style="color: #7c3aed; font-size: 2.5rem; font-weight: 700; margin-bottom: 20px; text-align: left; display: inline-block;">Compare your Data:</h2>
<hr style="border: none; height: 3px; background-color: #667eea; margin-bottom: 30px; width: 100%; max-width: 410px;">
<div id="dropdownContainer" style="display: flex; flex-direction: column; gap: 15px; max-width: 700px;">
<div style="display: flex; gap: 15px; align-items: center;">
<select class="compare-dropdown type-selector" style="padding: 12px; font-size: 1rem; border: 2px solid #cbd5e0; border-radius: 8px; background: white; cursor: pointer; width: 150px;">
<option value="rows">Rows</option>
<option value="columns">Columns</option>
</select>
<div class="searchable-dropdown-wrapper" style="flex: 1;">
<input type="text" class="searchable-dropdown-input" readonly placeholder="Select or search..." style="width: 100%; padding: 12px 24px 12px 12px; font-size: 1rem; border: 2px solid #cbd5e0; border-radius: 8px; background: white; cursor: pointer; outline: none;">
<span class="searchable-dropdown-arrow"></span>
<div class="searchable-dropdown-menu">
<div class="searchable-dropdown-search">
<input type="text" placeholder="Search..." class="search-input">
</div>
<div class="searchable-dropdown-options"></div>
</div>
</div>
<button class="delete-btn" style="width: 35px; height: 35px; border-radius: 50%; background: linear-gradient(135deg, #ff4444, #cc0000); color: white; border: none; cursor: pointer; font-size: 1.2rem; font-weight: bold; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(255, 68, 68, 0.3); transition: all 0.3s ease;">×</button>
</div>
<div style="display: flex; gap: 15px; align-items: center;">
<select class="compare-dropdown type-selector" style="padding: 12px; font-size: 1rem; border: 2px solid #cbd5e0; border-radius: 8px; background: white; cursor: pointer; width: 150px;">
<option value="columns">Columns</option>
<option value="rows">Rows</option>
</select>
<div class="searchable-dropdown-wrapper" style="flex: 1;">
<input type="text" class="searchable-dropdown-input" readonly placeholder="Select or search..." style="width: 100%; padding: 12px 24px 12px 12px; font-size: 1rem; border: 2px solid #cbd5e0; border-radius: 8px; background: white; cursor: pointer; outline: none;">
<span class="searchable-dropdown-arrow"></span>
<div class="searchable-dropdown-menu">
<div class="searchable-dropdown-search">
<input type="text" placeholder="Search..." class="search-input">
</div>
<div class="searchable-dropdown-options"></div>
</div>
</div>
<button class="delete-btn" style="width: 35px; height: 35px; border-radius: 50%; background: linear-gradient(135deg, #ff4444, #cc0000); color: white; border: none; cursor: pointer; font-size: 1.2rem; font-weight: bold; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(255, 68, 68, 0.3); transition: all 0.3s ease;">×</button>
</div>
</div>
<button id="addDropdownBtn" style="margin-top: 20px; background: linear-gradient(135deg, #97e614 0%, #97e614 100%); color: #000; border: none; padding: 12px 24px; font-size: 1.5rem; font-weight: 700; border-radius: 8px; cursor: pointer; box-shadow: 0 4px 12px rgba(151, 230, 20, 0.2); transition: all 0.3s ease;">
+
</button>
</div>
<div class="comparison-chart-area" id="comparisonChartArea" style="flex: 1; display: none;">
<div class="chart-section">
<div class="chart-title" id="chartTitle">Comparison Chart</div>
<canvas id="comparisonChart"></canvas>
</div>
</div>
</div>
</section>
<section id="reportView" class="table-wrap hidden"><div class="box">Report view</div></section>
</main>
<script>
// ========== Data Structures (Python-like organization) ==========
const DataProcessor = {
globalData: { headers: [], rows: [] },
chartInstances: {},
comparisonChartInstance: null,
// Color palettes - same as Python dict structure
colorPalettes: {
categorical: ['#E69F00', '#56B4E9', '#009E73', '#F0E442', '#0072B2', '#D55E00', '#CC79A7', '#000000'],
sequential: ['#FFEDA0', '#FED976', '#FEB24C', '#FD8D3C', '#FC4E2A', '#E31A1C', '#BD0026', '#800026'],
diverging: ['#053061', '#2166AC', '#4393C3', '#92C5DE', '#F7F7F7', '#FDDBC7', '#F4A582', '#D6604D', '#67001F'],
ordinal: ['#FFE6E6', '#FF9999', '#FF3333', '#CC0000'],
binary: ['#1B9E77', '#7570B3'],
comparison: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F']
},
axisColors: {
red: '#FF0000',
blue: '#0066FF',
green: '#00CC00',
purple: '#9900FF',
orange: '#FF6600',
cyan: '#00CCCC',
magenta: '#FF00FF',
yellow: '#FFD700'
}
};
// ========== CSV Parser (like pandas.read_csv) ==========
class CSVParser {
static parse(text) {
const rows = [];
let row = [], cell = '', i = 0, inside = false;
while (i < text.length) {
const ch = text[i];
if (inside) {
if (ch === '"') {
if (text[i+1] === '"') {
cell += '"';
i += 2;
continue;
}
inside = false;
i++;
continue;
}
cell += ch;
i++;
continue;
} else {
if (ch === '"') {
inside = true;
i++;
continue;
}
if (ch === ',') {
row.push(cell);
cell = '';
i++;
continue;
}
if (ch === '\n') {
row.push(cell);
rows.push(row);
row = [];
cell = '';
i++;
continue;
}
if (ch === '\r') {
i++;
continue;
}
cell += ch;
i++;
}
}
row.push(cell);
rows.push(row);
// Remove empty rows
while (rows.length && rows[rows.length-1].every(v => v.trim() === '')) {
rows.pop();
}
if (!rows.length) return { headers: [], rows: [] };
// Extract headers and create row objects
const headers = rows[0].map(h => h.trim());
const data = rows.slice(1).map(r => {
const obj = {};
headers.forEach((h, idx) => obj[h] = (r[idx] ?? '').trim());
return obj;
});
return { headers, rows: data };
}
}
// ========== Data Type Detection (like pandas dtype) ==========
class DataTypeDetector {
static detect(columnName, values) {
const nonEmpty = values.filter(v => v && v.trim() !== '');
if (nonEmpty.length === 0) return 'empty';
// Check if binary
const uniqueVals = [...new Set(nonEmpty)];
if (uniqueVals.length === 2) return 'binary';
// Check if numeric
const numericValues = nonEmpty.filter(v => !isNaN(parseFloat(v)) && isFinite(v));
if (numericValues.length > nonEmpty.length * 0.8) {
const nums = numericValues.map(v => parseFloat(v));
// Check if year
if (nums.every(n => n >= 1900 && n <= 2100 && Number.isInteger(n))) {
return 'time';
}
return 'numeric';
}
// Check if date/time
const datePatterns = [
/^\d{4}-\d{2}-\d{2}/,
/^\d{2}\/\d{2}\/\d{4}/,
/^\d{1,2}[/-]\d{1,2}[/-]\d{2,4}/
];
if (nonEmpty.some(v => datePatterns.some(p => p.test(v)))) {
return 'time';
}
// Check if ordinal
const ordinalPatterns = ['low', 'medium', 'high', 'small', 'large', 'poor', 'good', 'excellent'];
const lowerValues = uniqueVals.map(v => v.toLowerCase());
if (uniqueVals.length <= 10 &&
lowerValues.some(v => ordinalPatterns.some(p => v.includes(p)))) {
return 'ordinal';
}
return 'categorical';
}
}
// ========== Comparison Chart Generator ==========
class ComparisonChartGenerator {
// Generate high contrast colors with guaranteed uniqueness
static generateColor(index) {
// High contrast colors optimized for visibility and distinction
const highContrastColors = [
'#FF0000', // Pure Red
'#0000FF', // Pure Blue
'#00AA00', // Green
'#FF00FF', // Magenta
'#FF8800', // Orange
'#00CCCC', // Cyan
'#8B008B', // Dark Magenta
'#FFD700', // Gold
'#000080', // Navy Blue
'#DC143C', // Crimson
'#228B22', // Forest Green
'#4B0082', // Indigo
'#FF1493', // Deep Pink
'#006400', // Dark Green
'#FF4500', // Orange Red
'#9400D3', // Violet
'#00CED1', // Dark Turquoise
'#B22222', // Fire Brick
'#2F4F4F', // Dark Slate Gray
'#FF6347' // Tomato
];
// Ensure index is a valid number
const colorIndex = parseInt(index) || 0;
if (colorIndex < highContrastColors.length) {
return highContrastColors[colorIndex];
}
// Generate additional high contrast colors for indices beyond predefined
// Use a deterministic approach to ensure consistency
const baseHue = (colorIndex * 47) % 360; // Prime number for better distribution
const saturation = 100; // Maximum saturation for vivid colors
const lightness = 35 + (colorIndex % 3) * 15; // Vary between 35%, 50%, 65%
return `hsl(${baseHue}, ${saturation}%, ${lightness}%)`;
}
static generate(selections) {
// Filter out empty selections and ensure each has a unique index
const validSelections = selections
.map((sel, originalIndex) => ({ ...sel, originalIndex }))
.filter(sel => sel.type && sel.value);
if (validSelections.length === 0) {
return null;
}
// Determine the type of comparison
const types = validSelections.map(s => s.type);
const allColumns = types.every(t => t === 'columns');
const allRows = types.every(t => t === 'rows');
if (allColumns) {
return this.generateColumnComparison(validSelections);
} else if (allRows) {
return this.generateRowComparison(validSelections);
} else {
return this.generateMixedComparison(validSelections);
}
}
static generateColumnComparison(selectionsWithIndex) {
const datasets = [];
const columns = selectionsWithIndex.map(s => s.value);
// Determine if all columns are numeric
const columnTypes = {};
columns.forEach(col => {
const values = DataProcessor.globalData.rows.map(r => r[col] || '');
columnTypes[col] = DataTypeDetector.detect(col, values);
});
const allNumeric = columns.every(col => columnTypes[col] === 'numeric');
if (allNumeric) {
// For numeric columns, show values across data points
const labels = DataProcessor.globalData.rows.map((r, i) => `Point ${i + 1}`);
selectionsWithIndex.forEach((sel) => {
const col = sel.value;
const colorIndex = sel.originalIndex;
const data = DataProcessor.globalData.rows.map(r => {
const val = r[col];
return val && !isNaN(parseFloat(val)) ? parseFloat(val) : null;
});
const color = this.generateColor(colorIndex);
datasets.push({
label: col,
data: data,
borderColor: color,
backgroundColor: color,
tension: 0.2,
fill: false,
borderWidth: 3,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: '#ffffff',
pointBorderColor: color,
pointBorderWidth: 3,
spanGaps: true
});
});
return {
type: 'line',
data: {
labels: labels.slice(0, Math.min(50, labels.length)),
datasets: datasets.map(d => ({...d, data: d.data.slice(0, Math.min(50, d.data.length))}))
},
options: this.getLineChartOptions('Values', 'Data Points')
};
} else {
// For categorical or mixed columns, show distribution
const allValues = new Set();
const columnCounts = {};
columns.forEach(col => {
const values = DataProcessor.globalData.rows.map(r => r[col] || '');
columnCounts[col] = {};
if (columnTypes[col] === 'numeric') {
const numericVals = values.filter(v => v && !isNaN(parseFloat(v))).map(v => parseFloat(v));
if (numericVals.length > 0) {
const min = Math.min(...numericVals);
const max = Math.max(...numericVals);
const binCount = 5;
const binSize = (max - min) / binCount;
for (let i = 0; i < binCount; i++) {
const binMin = min + i * binSize;
const binMax = binMin + binSize;
const binLabel = `${binMin.toFixed(1)}-${binMax.toFixed(1)}`;
allValues.add(binLabel);
columnCounts[col][binLabel] = numericVals.filter(v =>
v >= binMin && (i === binCount - 1 ? v <= binMax : v < binMax)
).length;
}
}
} else {
values.forEach(v => {
if (v) {
allValues.add(v);
columnCounts[col][v] = (columnCounts[col][v] || 0) + 1;
}
});
}
});
const labels = Array.from(allValues).sort().slice(0, 20);
selectionsWithIndex.forEach((sel) => {
const col = sel.value;
const colorIndex = sel.originalIndex;
const color = this.generateColor(colorIndex);
datasets.push({
label: col,
data: labels.map(label => columnCounts[col][label] || 0),
borderColor: color,
backgroundColor: color,
tension: 0.2,
fill: false,
borderWidth: 3,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: '#ffffff',
pointBorderColor: color,
pointBorderWidth: 3
});
});
return {
type: 'line',
data: {
labels: labels,
datasets: datasets
},
options: this.getLineChartOptions('Count', 'Categories')
};
}
}
static generateRowComparison(selectionsWithIndex) {
const datasets = [];
selectionsWithIndex.forEach((sel) => {
const value = sel.value;
const colorIndex = sel.originalIndex;
const counts = DataProcessor.globalData.headers.map(header => {
return DataProcessor.globalData.rows.filter(row =>
row[header] === value
).length;
});
const color = this.generateColor(colorIndex);
datasets.push({
label: value,
data: counts,
borderColor: color,
backgroundColor: color,
tension: 0.2,
fill: false,
borderWidth: 3,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: '#ffffff',
pointBorderColor: color,
pointBorderWidth: 3
});
});
return {
type: 'line',
data: {
labels: DataProcessor.globalData.headers,
datasets: datasets
},
options: this.getLineChartOptions('Occurrences', 'Columns')
};
}
static generateMixedComparison(selectionsWithIndex) {
const datasets = [];
const labels = [];
const maxPoints = 20;
selectionsWithIndex.forEach((sel) => {
const colorIndex = sel.originalIndex;
const color = this.generateColor(colorIndex);
if (sel.type === 'columns') {
const values = DataProcessor.globalData.rows.map(r => r[sel.value] || '');
const dataType = DataTypeDetector.detect(sel.value, values);
if (dataType === 'numeric') {
const data = DataProcessor.globalData.rows.slice(0, maxPoints).map((r, i) => {
if (labels.length <= i) labels.push(`Point ${i + 1}`);
const val = r[sel.value];
return val && !isNaN(parseFloat(val)) ? parseFloat(val) : null;
});
datasets.push({
label: `${sel.value} (Column)`,
data: data,
borderColor: color,
backgroundColor: color,
tension: 0.2,
fill: false,
borderWidth: 3,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: '#ffffff',
pointBorderColor: color,
pointBorderWidth: 3,
spanGaps: true
});
} else {
const counts = {};
values.forEach(v => {
if (v) counts[v] = (counts[v] || 0) + 1;
});
const sortedValues = Object.keys(counts).sort((a, b) => counts[b] - counts[a]).slice(0, maxPoints);
sortedValues.forEach((v, i) => {
if (labels.length <= i) labels.push(v);
});
datasets.push({
label: `${sel.value} (Column)`,
data: labels.map(l => counts[l] || 0),
borderColor: color,
backgroundColor: color,
tension: 0.2,
fill: false,
borderWidth: 3,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: '#ffffff',
pointBorderColor: color,
pointBorderWidth: 3
});
}
} else {
const occurrences = DataProcessor.globalData.headers.slice(0, maxPoints).map((header, i) => {
if (labels.length <= i) labels.push(header);
return DataProcessor.globalData.rows.filter(row =>
row[header] === sel.value
).length;
});
datasets.push({
label: `"${sel.value}" (Row Value)`,
data: occurrences,
borderColor: color,
backgroundColor: color,
tension: 0.2,
fill: false,
borderWidth: 3,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: '#ffffff',
pointBorderColor: color,
pointBorderWidth: 3
});
}
});
return {
type: 'line',
data: {
labels: labels,
datasets: datasets
},
options: this.getLineChartOptions('Value', 'Data Points / Categories')
};
}
static getLineChartOptions(yLabel, xLabel) {
return {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: false
},
legend: {
position: 'top',
align: 'end',
labels: {
padding: 15,
font: { size: 11, weight: '600' }, // Bolder for better readability
color: '#2d3748', // Darker for contrast
usePointStyle: true,
pointStyle: 'circle',
boxWidth: 15
}
},
tooltip: {
mode: 'index',
intersect: false,
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#2d3748',
borderWidth: 2,
titleColor: '#1a202c',
bodyColor: '#2d3748',
titleFont: { size: 13, weight: '700' },
bodyFont: { size: 12, weight: '600' },
padding: 10,
cornerRadius: 6,
displayColors: true,
usePointStyle: true
}
},
scales: {
y: {
beginAtZero: true,
grid: {
color: '#e2e8f0',
drawBorder: true,
lineWidth: 1
},
ticks: {
color: '#2d3748', // Darker for better contrast
font: { size: 11, weight: '500' },
padding: 8
},
title: {
display: true,
text: yLabel,
color: '#2d3748',
font: { size: 12, weight: '600' }
},
border: {
display: true,
color: '#718096'
}
},
x: {
grid: {
color: '#e2e8f0',
drawBorder: true,
lineWidth: 1
},
ticks: {
color: '#2d3748',
maxRotation: 45,
minRotation: 0,
font: { size: 11, weight: '500' },
padding: 8,
autoSkip: true,
maxTicksLimit: 15
},
title: {
display: true,
text: xLabel,
color: '#2d3748',
font: { size: 12, weight: '600' }
},
border: {
display: true,
color: '#718096'
}
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
}
};
}
}
// ========== Chart Factory (like matplotlib/seaborn) ==========
class ChartFactory {
static create(canvas, column, dataType, data) {
const ctx = canvas.getContext('2d');
const values = data.map(r => r[column] || '');
// Calculate value counts (like pandas value_counts)
const counts = {};
values.forEach(v => {
if (v) counts[v] = (counts[v] || 0) + 1;
});
// Calculate percentages
const total = values.filter(v => v).length;
const percentages = {};
Object.keys(counts).forEach(k => {
percentages[k] = ((counts[k] / total) * 100).toFixed(1);
});
let chartConfig;
// Select chart type based on data type
switch(dataType) {
case 'numeric':
chartConfig = this.createHistogram(column, values, counts, percentages);
break;
case 'time':
chartConfig = this.createLineChart(column, values, counts, percentages);
break;
case 'binary':
chartConfig = this.createPieChart(column, counts, percentages);
break;
case 'ordinal':
chartConfig = this.createOrderedBar(column, counts, percentages);
break;
case 'categorical':
if (Object.keys(counts).length <= 5) {
chartConfig = this.createDonutChart(column, counts, percentages);
} else {
chartConfig = this.createBarChart(column, counts, percentages);
}
break;
default:
chartConfig = this.createBarChart(column, counts, percentages);
}
// Set default styles
Chart.defaults.color = '#000000';
Chart.defaults.font.weight = 'bold';
return new Chart(ctx, chartConfig);
}
static createHistogram(column, values, counts, percentages) {
const numericVals = values.filter(v => v && !isNaN(parseFloat(v))).map(v => parseFloat(v));
const min = Math.min(...numericVals);
const max = Math.max(...numericVals);
const binCount = Math.min(10, Math.ceil(Math.sqrt(numericVals.length)));
const binSize = (max - min) / binCount;
const bins = Array(binCount).fill(0);
const binLabels = [];
for (let i = 0; i < binCount; i++) {
const binMin = min + i * binSize;
const binMax = binMin + binSize;
binLabels.push(`${binMin.toFixed(1)}-${binMax.toFixed(1)}`);
numericVals.forEach(v => {
if (v >= binMin && (i === binCount - 1 ? v <= binMax : v < binMax)) {
bins[i]++;
}
});
}
const total = numericVals.length;
const binPercentages = bins.map(b => ((b / total) * 100).toFixed(1));
return {
type: 'bar',
data: {
labels: binLabels,
datasets: [{
label: `${column} Distribution`,
data: bins,
backgroundColor: DataProcessor.colorPalettes.sequential,
borderColor: '#333',
borderWidth: 1
}]
},
options: this.getChartOptions('histogram', binPercentages)
};
}
static createBarChart(column, counts, percentages) {
const sortedKeys = Object.keys(counts).sort((a, b) => counts[b] - counts[a]).slice(0, 10);
return {
type: 'bar',
data: {
labels: sortedKeys,
datasets: [{
label: column,
data: sortedKeys.map(k => counts[k]),
backgroundColor: DataProcessor.colorPalettes.categorical.slice(0, sortedKeys.length),
borderColor: '#333',
borderWidth: 1
}]
},
options: this.getChartOptions('bar', sortedKeys.map(k => percentages[k]))
};
}
static createPieChart(column, counts, percentages) {
const keys = Object.keys(counts);
return {
type: 'pie',
data: {
labels: keys,
datasets: [{
data: keys.map(k => counts[k]),
backgroundColor: DataProcessor.colorPalettes.binary,
borderColor: '#000',
borderWidth: 2
}]
},
options: this.getChartOptions('pie', keys.map(k => percentages[k]))
};
}
static createDonutChart(column, counts, percentages) {
const keys = Object.keys(counts);
return {
type: 'doughnut',
data: {
labels: keys,
datasets: [{
data: keys.map(k => counts[k]),
backgroundColor: DataProcessor.colorPalettes.categorical.slice(0, keys.length),
borderColor: '#000',
borderWidth: 2
}]
},
options: this.getChartOptions('donut', keys.map(k => percentages[k]))
};
}
static createLineChart(column, values, counts, percentages) {
const sortedKeys = Object.keys(counts).sort();
return {
type: 'line',
data: {
labels: sortedKeys,
datasets: [{
label: `${column} Trend`,
data: sortedKeys.map(k => counts[k]),
borderColor: '#0072B2',
backgroundColor: 'rgba(0, 114, 178, 0.1)',
tension: 0.4,
fill: true,
borderWidth: 3
}]
},
options: this.getChartOptions('line', sortedKeys.map(k => percentages[k]))
};
}
static createOrderedBar(column, counts, percentages) {
const ordinalOrder = ['low', 'medium', 'high', 'small', 'medium', 'large', 'poor', 'fair', 'good', 'excellent'];
const keys = Object.keys(counts);
const sortedKeys = keys.sort((a, b) => {
const aIdx = ordinalOrder.findIndex(o => a.toLowerCase().includes(o));
const bIdx = ordinalOrder.findIndex(o => b.toLowerCase().includes(o));
if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx;
return a.localeCompare(b);
});
return {
type: 'bar',
data: {
labels: sortedKeys,
datasets: [{
label: column,
data: sortedKeys.map(k => counts[k]),
backgroundColor: DataProcessor.colorPalettes.ordinal.slice(0, sortedKeys.length),
borderColor: '#333',
borderWidth: 1
}]
},
options: {
...this.getChartOptions('orderedBar', sortedKeys.map(k => percentages[k])),
indexAxis: 'y'
}
};
}
static getChartOptions(type, percentagesList) {
const baseOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: type === 'pie' || type === 'donut',
position: 'bottom',
labels: {
color: '#000000',
padding: 10,
font: {
weight: 'bold',
size: type === 'donut' ? 10 : 12
}
}
},
tooltip: {
callbacks: {
label: function(context) {
const index = context.dataIndex;
const count = type === 'pie' || type === 'donut' ?
context.parsed : context.parsed.y || context.parsed.x;
const percentage = percentagesList[index];
return `${count} (${percentage}%)`;
}
},
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#000000',
borderWidth: 2,
bodyColor: '#000000',
titleColor: '#000000',
bodyFont: { weight: 'bold', size: 14 },
titleFont: { weight: 'bold', size: 14 },
padding: 12,
cornerRadius: 6,
displayColors: false
}
}
};
if (type !== 'pie' && type !== 'donut') {
const yAxisColor = type === 'histogram' ? 'red' :
type === 'orderedBar' ? 'magenta' :
type === 'line' ? 'purple' : 'green';
const xAxisColor = type === 'histogram' ? 'blue' :
type === 'orderedBar' ? 'yellow' :
type === 'line' ? 'cyan' : 'orange';
baseOptions.scales = {
y: {
beginAtZero: true,
ticks: { color: '#000000', font: { weight: 'bold' } },
title: {
display: true,
text: type === 'orderedBar' ? 'Categories' : 'Count',
color: '#000000',
font: { weight: 'bold' }
},
grid: { color: DataProcessor.axisColors[yAxisColor], lineWidth: 0.5 },
border: { color: DataProcessor.axisColors[yAxisColor], width: 3 }
},
x: {
ticks: {
color: '#000000',
maxRotation: 45,
minRotation: 45,
font: { weight: 'bold' }
},
title: {
display: true,
text: type === 'histogram' ? 'Range' :
type === 'orderedBar' ? 'Count' :
type === 'line' ? 'Values' : 'Categories',
color: '#000000',
font: { weight: 'bold' }
},
grid: { color: DataProcessor.axisColors[xAxisColor], lineWidth: 0.5 },
border: { color: DataProcessor.axisColors[xAxisColor], width: 3 }
}
};
}
return baseOptions;
}
}
// ========== Table Renderer (like pandas DataFrame display) ==========
class TableRenderer {
static render(dataset, fileLabel = 'CSV') {
const { headers, rows } = dataset;
DataProcessor.globalData = dataset;
const mount = document.getElementById('tableMount');
const uniques = this.getUniqueValues(headers, rows);
let html = '<table class="csv-table"><thead><tr>';
headers.forEach(h => html += `<th>${h}</th>`);
html += '</tr></thead><tbody>';
// Filter row
html += '<tr class="filter-row">';
headers.forEach(h => {
html += `<td><button type="button" class="dd-trigger" data-col="${h}" data-value="All">`;
html += `<span style="font-weight: bold;">All</span></button></td>`;
});
html += '</tr>';
// Chart row
html += '<tr class="spacer-row">';
headers.forEach((h, idx) => {
html += `<td><div class="chart-container"><canvas id="chart-${idx}"></canvas></div></td>`;
});
html += '</tr>';
// Data rows
rows.forEach(r => {
html += '<tr class="data-row">';
headers.forEach(h => {
const val = r[h] ?? '';
html += `<td title="${val.replace(/"/g,'&quot;')}">${val}</td>`;
});
html += '</tr>';
});
html += '</tbody></table>';
mount.innerHTML = html;
document.getElementById('filename').textContent = fileLabel;
// Setup filters and charts
FilterManager.setup(headers, uniques);
ChartManager.updateAll();
}
static getUniqueValues(headers, rows) {
const uniqueMap = {};
headers.forEach(h => {
const values = new Set();
rows.forEach(r => {
const val = (r[h] ?? '').trim();
if (val !== '') values.add(val);
});
uniqueMap[h] = Array.from(values).sort((a, b) => a.localeCompare(b));
});
return uniqueMap;
}
}
// ========== Filter Manager (like pandas query/filter) ==========
class FilterManager {
static portal = null;
static portalOwner = null;
static setup(headers, uniques) {
// Create portal element
if (!this.portal) {
this.portal = document.createElement('div');
this.portal.className = 'float-menu';
document.body.appendChild(this.portal);
}
const triggers = Array.from(document.querySelectorAll('.dd-trigger'));
const optionsByCol = {};
headers.forEach(h => {
optionsByCol[h] = ['All', ...(uniques[h] || [])];
});
triggers.forEach(btn => {
const col = btn.getAttribute('data-col');
const labelSpan = btn.querySelector('span');
btn.addEventListener('mouseenter', () => {
if (this.portal.style.display === 'block') this.closePortal();
this.openPortal(btn, optionsByCol[col], btn.getAttribute('data-value'), (val) => {
btn.setAttribute('data-value', val);
labelSpan.textContent = val;
labelSpan.style.fontWeight = val === 'All' ? 'bold' : 'normal';
this.applyFilters();
ChartManager.updateAll();
});
});
});
}
static openPortal(triggerBtn, options, currentValue, onSelect) {
this.portal.innerHTML = options.map(v => {
const style = v === 'All' ? 'style="font-weight: bold;"' : '';
return `<div class="float-option" data-value="${v.replace(/"/g,'&quot;')}" ${style}>${v}</div>`;
}).join('');
const rect = triggerBtn.getBoundingClientRect();
this.portal.style.width = rect.width + 'px';
this.portal.style.minWidth = rect.width + 'px';
this.portal.style.left = rect.left + 'px';
this.portal.style.display = 'block';
const menuH = Math.min(this.portal.scrollHeight, 260);
let top = rect.bottom + 4;
if (top + menuH > window.innerHeight - 8) {
top = Math.max(8, rect.top - 4 - menuH);
}
this.portal.style.top = top + 'px';
this.portalOwner = { triggerBtn, onSelect };
Array.from(this.portal.querySelectorAll('.float-option')).forEach(el => {
el.addEventListener('click', () => {
const val = el.getAttribute('data-value');
this.closePortal();
onSelect(val);
});
});
this.portal.addEventListener('mouseleave', () => this.closePortal(), { once: true });
triggerBtn.addEventListener('mouseleave', () => {
setTimeout(() => {
if (!this.portal.matches(':hover') && !triggerBtn.matches(':hover')) {
this.closePortal();
}
}, 120);
});
setTimeout(() => {
window.addEventListener('mousedown', (e) => {
if (!this.portal.contains(e.target) && e.target !== triggerBtn) {
this.closePortal();
}
}, { once: true });
window.addEventListener('keydown', (e) => {
if (e.key === 'Escape') this.closePortal();
}, { once: true });
}, 0);
}
static closePortal() {
this.portal.style.display = 'none';
this.portalOwner = null;
this.portal.innerHTML = '';
}
static applyFilters() {
const headers = Array.from(document.querySelectorAll('thead th')).map(th => th.textContent);
const activeFilters = {};
document.querySelectorAll('.dd-trigger').forEach(btn => {
const col = btn.getAttribute('data-col');
const val = btn.getAttribute('data-value');
if (val && val !== 'All') activeFilters[col] = val;
});
const rows = Array.from(document.querySelectorAll('tr.data-row'));
rows.forEach(tr => {
const tds = Array.from(tr.querySelectorAll('td'));
let shouldShow = true;
for (const [col, val] of Object.entries(activeFilters)) {
const idx = headers.indexOf(col);
if (idx < 0) continue;
if ((tds[idx]?.textContent ?? '').trim() !== val) {
shouldShow = false;
break;
}
}
tr.style.display = shouldShow ? '' : 'none';
});
}
static getFilteredData() {
const activeFilters = {};
document.querySelectorAll('.dd-trigger').forEach(btn => {
const col = btn.getAttribute('data-col');
const val = btn.getAttribute('data-value');
if (val && val !== 'All') activeFilters[col] = val;
});
return DataProcessor.globalData.rows.filter(row => {
for (const [col, val] of Object.entries(activeFilters)) {
if ((row[col] || '').trim() !== val) return false;
}
return true;
});
}
}
// ========== Chart Manager ==========
class ChartManager {
static updateAll() {
const filteredData = FilterManager.getFilteredData();
DataProcessor.globalData.headers.forEach((header, idx) => {
const chartId = `chart-${idx}`;
// Destroy existing chart
if (DataProcessor.chartInstances[chartId]) {
DataProcessor.chartInstances[chartId].destroy();
delete DataProcessor.chartInstances[chartId];
}
// Create new chart
const canvas = document.getElementById(chartId);
if (canvas) {
const values = filteredData.map(r => r[header] || '');
const dataType = DataTypeDetector.detect(header, values);
DataProcessor.chartInstances[chartId] = ChartFactory.create(
canvas, header, dataType, filteredData
);
}
});
}
}
// ========== Searchable Dropdown Manager ==========
class SearchableDropdown {
constructor(wrapper) {
this.wrapper = wrapper;
this.input = wrapper.querySelector('.searchable-dropdown-input');
this.arrow = wrapper.querySelector('.searchable-dropdown-arrow');
this.menu = wrapper.querySelector('.searchable-dropdown-menu');
this.searchInput = wrapper.querySelector('.search-input');
this.optionsContainer = wrapper.querySelector('.searchable-dropdown-options');
this.typeSelector = wrapper.parentElement.querySelector('.type-selector');
this.selectedValue = '';
this.options = [];
this.isOpen = false;
this.init();
}
init() {
// Click on input to open dropdown
this.input.addEventListener('click', () => this.toggle());
// Search functionality
this.searchInput.addEventListener('input', (e) => this.filterOptions(e.target.value));
// Close when clicking outside
document.addEventListener('click', (e) => {
if (!this.wrapper.contains(e.target)) {
this.close();
}
});
// Prevent menu from closing when clicking inside
this.menu.addEventListener('click', (e) => e.stopPropagation());
// Type selector change
if (this.typeSelector) {
this.typeSelector.addEventListener('change', () => {
this.loadOptions();
this.selectedValue = '';
this.input.value = '';
// Trigger auto-generation
window.generateComparisonChart();
});
}
// Load initial options
this.loadOptions();
}
loadOptions() {
const selectedType = this.typeSelector ? this.typeSelector.value : 'columns';
this.options = [];
if (selectedType === 'columns') {
this.options = DataProcessor.globalData.headers;
} else if (selectedType === 'rows') {
const allValues = new Set();
DataProcessor.globalData.rows.forEach(row => {
DataProcessor.globalData.headers.forEach(header => {
const value = row[header];
if (value && value.trim() !== '') {
allValues.add(value.trim());
}
});
});
this.options = Array.from(allValues).sort((a, b) => a.localeCompare(b));
}
this.renderOptions();
}
renderOptions(filteredOptions = null) {
const optionsToRender = filteredOptions || this.options;
if (optionsToRender.length === 0) {
this.optionsContainer.innerHTML = '<div class="searchable-dropdown-no-results">No results found</div>';
return;
}
this.optionsContainer.innerHTML = optionsToRender.map(option => `
<div class="searchable-dropdown-option ${option === this.selectedValue ? 'selected' : ''}" data-value="${option}">
${option}
</div>
`).join('');
// Add click handlers to options
this.optionsContainer.querySelectorAll('.searchable-dropdown-option').forEach(optionEl => {
optionEl.addEventListener('click', () => {
this.selectOption(optionEl.dataset.value);
});
});
}
filterOptions(searchTerm) {
const filtered = this.options.filter(option =>
option.toLowerCase().includes(searchTerm.toLowerCase())
);
this.renderOptions(filtered);
}
selectOption(value) {
this.selectedValue = value;
this.input.value = value;
this.close();
// Trigger auto-generation
window.generateComparisonChart();
}
toggle() {
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
open() {
this.isOpen = true;
this.menu.classList.add('open');
this.arrow.classList.add('open');
this.searchInput.value = '';
this.searchInput.focus();
this.renderOptions();
}
close() {
this.isOpen = false;
this.menu.classList.remove('open');
this.arrow.classList.remove('open');
}
}
// ========== View Controllers ==========
function showPrepare() {
document.getElementById('prepareView').classList.remove('hidden');
document.getElementById('predictView').classList.add('hidden');
document.getElementById('reportView').classList.add('hidden');
setActive('prepareBtn');
}
function showPredict() {
document.getElementById('prepareView').classList.add('hidden');
document.getElementById('predictView').classList.remove('hidden');
document.getElementById('reportView').classList.add('hidden');
setActive('predictBtn');
}
function showReport() {
document.getElementById('prepareView').classList.add('hidden');
document.getElementById('predictView').classList.add('hidden');
document.getElementById('reportView').classList.remove('hidden');
setActive('reportBtn');
}
function setActive(btnId) {
document.querySelectorAll('.menu-buttons button').forEach(b => b.classList.remove('active'));
document.getElementById(btnId).classList.add('active');
}
// ========== Comparison Chart Generation ==========
window.generateComparisonChart = function() {
const selections = [];
const dropdownPairs = document.querySelectorAll('#dropdownContainer > div');
dropdownPairs.forEach(pair => {
const typeSelector = pair.querySelector('.type-selector');
const inputField = pair.querySelector('.searchable-dropdown-input');
if (typeSelector && inputField && inputField.value) {
selections.push({
type: typeSelector.value,
value: inputField.value
});
}
});
// Check if we have any valid selections
if (selections.length === 0) {
document.getElementById('comparisonChartArea').style.display = 'none';
return;
}
// Generate the chart config
const chartConfig = ComparisonChartGenerator.generate(selections);
if (!chartConfig) {
document.getElementById('comparisonChartArea').style.display = 'none';
return;
}
// Show the chart area
document.getElementById('comparisonChartArea').style.display = 'block';
// Destroy existing chart if exists
if (DataProcessor.comparisonChartInstance) {
DataProcessor.comparisonChartInstance.destroy();
DataProcessor.comparisonChartInstance = null;
}
// Create new chart
const canvas = document.getElementById('comparisonChart');
const ctx = canvas.getContext('2d');
DataProcessor.comparisonChartInstance = new Chart(ctx, chartConfig);
};
// ========== Main Initialization ==========
document.addEventListener('DOMContentLoaded', async () => {
let csvText = null, label = 'CSV';
try {
const ss = window.sessionStorage;
const cached = ss.getItem('csvData');
const name = ss.getItem('csvFileName');
if (cached) {
csvText = cached;
label = name || label;
}
} catch(e) {
console.error('SessionStorage error:', e);
}
const mount = document.getElementById('tableMount');
if (!csvText) {
mount.innerHTML = '<div class="box"><div class="status error">No CSV found</div></div>';
return;
}
const data = CSVParser.parse(csvText);
TableRenderer.render(data, label);
// Initialize searchable dropdowns
function initializeDropdownPair(container) {
const wrappers = container.querySelectorAll('.searchable-dropdown-wrapper');
wrappers.forEach(wrapper => {
if (!wrapper.hasAttribute('data-initialized')) {
wrapper.setAttribute('data-initialized', 'true');
new SearchableDropdown(wrapper);
}
});
// Setup delete buttons
const deleteButtons = container.querySelectorAll('.delete-btn');
deleteButtons.forEach(btn => {
if (!btn.hasAttribute('data-initialized')) {
btn.setAttribute('data-initialized', 'true');
btn.addEventListener('click', function() {
const dropdownPair = this.parentElement;
dropdownPair.remove();
// Re-generate chart after deletion
window.generateComparisonChart();
});
}
});
}
// Initialize existing dropdowns
const dropdownContainer = document.getElementById('dropdownContainer');
if (dropdownContainer) {
initializeDropdownPair(dropdownContainer);
}
// Add dropdown functionality for Predict tab
const addDropdownBtn = document.getElementById('addDropdownBtn');
if (addDropdownBtn) {
addDropdownBtn.addEventListener('click', () => {
const dropdownContainer = document.getElementById('dropdownContainer');
const existingDropdowns = dropdownContainer.querySelectorAll('.type-selector');
// Determine which option to show first based on the count
const defaultOption = existingDropdowns.length % 2 === 0 ? 'rows' : 'columns';
const alternateOption = defaultOption === 'rows' ? 'columns' : 'rows';
const newDropdownPair = document.createElement('div');
newDropdownPair.style.cssText = 'display: flex; gap: 15px; align-items: center;';
const typeDropdown = document.createElement('select');
typeDropdown.className = 'compare-dropdown type-selector';
typeDropdown.style.cssText = 'padding: 12px; font-size: 1rem; border: 2px solid #cbd5e0; border-radius: 8px; background: white; cursor: pointer; width: 150px;';
typeDropdown.innerHTML = `<option value="${defaultOption}">${defaultOption.charAt(0).toUpperCase() + defaultOption.slice(1)}</option><option value="${alternateOption}">${alternateOption.charAt(0).toUpperCase() + alternateOption.slice(1)}</option>`;
const dropdownWrapper = document.createElement('div');
dropdownWrapper.className = 'searchable-dropdown-wrapper';
dropdownWrapper.style.cssText = 'flex: 1;';
dropdownWrapper.innerHTML = `
<input type="text" class="searchable-dropdown-input" readonly placeholder="Select or search..." style="width: 100%; padding: 12px 24px 12px 12px; font-size: 1rem; border: 2px solid #cbd5e0; border-radius: 8px; background: white; cursor: pointer; outline: none;">
<span class="searchable-dropdown-arrow">▼</span>
<div class="searchable-dropdown-menu">
<div class="searchable-dropdown-search">
<input type="text" placeholder="Search..." class="search-input">
</div>
<div class="searchable-dropdown-options"></div>
</div>
`;
const deleteBtn = document.createElement('button');
deleteBtn.className = 'delete-btn';
deleteBtn.style.cssText = 'width: 35px; height: 35px; border-radius: 50%; background: linear-gradient(135deg, #ff4444, #cc0000); color: white; border: none; cursor: pointer; font-size: 1.2rem; font-weight: bold; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(255, 68, 68, 0.3); transition: all 0.3s ease;';
deleteBtn.innerHTML = '×';
newDropdownPair.appendChild(typeDropdown);
newDropdownPair.appendChild(dropdownWrapper);
newDropdownPair.appendChild(deleteBtn);
dropdownContainer.appendChild(newDropdownPair);
// Initialize the new dropdown
new SearchableDropdown(dropdownWrapper);
// Setup delete button
deleteBtn.addEventListener('click', function() {
newDropdownPair.remove();
window.generateComparisonChart();
});
});
}
});
</script>
</body>
</html>