STE-Nexus's picture
Develop very good app time in second, min, hours option and stepsize option using code "import numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy.constants import N_A # Avogadro's number # ============================================= # PARAMETERS (24-hour simulation) # ============================================= initial_cells = 1e4 # Initial T-cell count (10,000 cells) growth_rate = 0.15 # Growth rate (0.15 per hour) blood_volume = 5 # Blood volume (5 liters) noise_level = 0.05 # 5% multiplicative noise total_seconds = 86400 # 24 hours in seconds step_size = 1 # 1-second resolution # ============================================= # TIME GRID (1-second steps) # ============================================= time_seconds = np.arange(0, total_seconds + 1, step_size) time_hours = time_seconds / 3600 # Convert to hours # ============================================= # GROWTH MODEL CALCULATIONS # ============================================= # Convert growth rate to per-second growth_rate_per_second = growth_rate / 3600 # Exponential growth model ideal_counts = initial_cells * np.exp(growth_rate_per_second * time_seconds) # Add log-normal noise (preserves positivity) np.random.seed(42) # For reproducibility noise = np.random.lognormal(mean=0, sigma=noise_level, size=len(time_seconds)) noisy_counts = ideal_counts * noise # Convert to molar concentration molar_conversion = 1 / (N_A * blood_volume) molar_ideal = ideal_counts * molar_conversion molar_noisy = noisy_counts * molar_conversion # ============================================= # CREATE DATAFRAME (86,401 rows) # ============================================= data = { 'time_seconds': time_seconds, 'time_hours': np.round(time_hours, 6), 'ideal_cell_count': np.round(ideal_counts, 2), 'noisy_cell_count': np.round(noisy_counts, 2), 'molar_ideal': molar_ideal, 'molar_noisy': molar_noisy } df = pd.DataFrame(data) print(f"Generated {len(df)} data points (1-second resolution)") print(df.head()) print(df.tail()) # ============================================= # VISUALIZATION (Sampled for clarity) # ============================================= sample_interval = 300 # Show every 5 minutes fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8)) # Linear-scale plot (sampled) ax1.plot(time_seconds[::sample_interval], molar_ideal[::sample_interval], 'b--', label='Ideal Growth') ax1.plot(time_seconds[::sample_interval], molar_noisy[::sample_interval], 'r-', alpha=0.7, label='Noisy Measurements') ax1.set_xlabel('Time (seconds)') ax1.set_ylabel('Concentration (M)') ax1.set_title('T-cell Growth (Linear Scale)') ax1.legend() ax1.grid(True) # Log-scale plot (sampled) ax2.semilogy(time_seconds[::sample_interval], molar_ideal[::sample_interval], 'b--', label='Ideal Growth') ax2.semilogy(time_seconds[::sample_interval], molar_noisy[::sample_interval], 'r-', alpha=0.7, label='Noisy Measurements') ax2.set_xlabel('Time (seconds)') ax2.set_ylabel('Concentration (M)') ax2.set_title('T-cell Growth (Log Scale)') ax2.legend() ax2.grid(True, which="both", ls="--") plt.tight_layout() plt.show() # ============================================= # DATA EXPORT # ============================================= df.to_csv('tcell_growth_1sec_resolution_24h.csv', index=False) print("Data saved to 'tcell_growth_1sec_resolution_24h.csv'")" - Initial Deployment
029dabb verified
Raw History Blame Contribute Delete
31 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>T-cell Growth Visualization</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/numeric@1.2.6/numeric.min.js"></script>
<style>
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 200px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -100px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.chart-container {
position: relative;
height: 300px;
width: 100%;
}
@media (min-width: 768px) {
.chart-container {
height: 400px;
}
}
input[type="range"] {
-webkit-appearance: none;
height: 8px;
background: #ddd;
border-radius: 5px;
background-image: linear-gradient(#3b82f6, #3b82f6);
background-size: 0% 100%;
background-repeat: no-repeat;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 20px;
width: 20px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
box-shadow: 0 0 2px 0 #555;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8 text-center">
<h1 class="text-3xl md:text-4xl font-bold text-gray-800 mb-2">T-cell Growth Visualizer</h1>
<p class="text-gray-600 max-w-2xl mx-auto">Simulate and visualize T-cell population growth over time with customizable parameters</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Control Panel -->
<div class="bg-white rounded-lg shadow-md p-6 h-fit lg:sticky lg:top-4">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Simulation Parameters</h2>
<div class="space-y-6">
<div>
<label for="initialCells" class="block text-sm font-medium text-gray-700 mb-1">
Initial T-cell Count (cells)
<span class="tooltip">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 inline ml-1 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="tooltiptext">Starting number of T-cells in the simulation</span>
</span>
</label>
<input type="number" id="initialCells" value="10000" min="1" step="100" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
</div>
<div>
<label for="growthRate" class="block text-sm font-medium text-gray-700 mb-1">
Growth Rate (per hour)
<span class="tooltip">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 inline ml-1 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="tooltiptext">Rate at which T-cells multiply per hour</span>
</span>
</label>
<input type="number" id="growthRate" value="0.15" min="0.01" step="0.01" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
</div>
<div>
<label for="bloodVolume" class="block text-sm font-medium text-gray-700 mb-1">
Blood Volume (liters)
<span class="tooltip">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 inline ml-1 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="tooltiptext">Total blood volume for concentration calculation</span>
</span>
</label>
<input type="number" id="bloodVolume" value="5" min="1" step="0.5" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
</div>
<div>
<label for="noiseLevel" class="block text-sm font-medium text-gray-700 mb-1">
Noise Level (%)
<span class="tooltip">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 inline ml-1 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="tooltiptext">Amount of random variation in the measurements</span>
</span>
</label>
<input type="number" id="noiseLevel" value="5" min="0" max="100" step="1" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
</div>
<div>
<label for="totalTime" class="block text-sm font-medium text-gray-700 mb-1">
Simulation Duration
<span class="tooltip">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 inline ml-1 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="tooltiptext">Total time period for the simulation</span>
</span>
</label>
<div class="flex space-x-2">
<input type="number" id="totalTimeValue" value="24" min="1" step="1" class="mt-1 block w-3/4 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<select id="totalTimeUnit" class="mt-1 block w-1/4 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="seconds">seconds</option>
<option value="minutes">minutes</option>
<option value="hours" selected>hours</option>
</select>
</div>
</div>
<div>
<label for="stepSize" class="block text-sm font-medium text-gray-700 mb-1">
Step Size
<span class="tooltip">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 inline ml-1 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="tooltiptext">Time interval between data points</span>
</span>
</label>
<div class="flex space-x-2">
<input type="number" id="stepSizeValue" value="1" min="1" step="1" class="mt-1 block w-3/4 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<select id="stepSizeUnit" class="mt-1 block w-1/4 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="seconds" selected>seconds</option>
<option value="minutes">minutes</option>
<option value="hours">hours</option>
</select>
</div>
</div>
<div>
<label for="displayUnit" class="block text-sm font-medium text-gray-700 mb-1">
Display Time Unit
<span class="tooltip">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 inline ml-1 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="tooltiptext">Time unit for the x-axis in charts</span>
</span>
</label>
<select id="displayUnit" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="seconds">Seconds</option>
<option value="minutes">Minutes</option>
<option value="hours" selected>Hours</option>
</select>
</div>
</div>
<div class="mt-6 flex justify-between">
<button id="resetBtn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500">
Reset Defaults
</button>
<button id="runSimulation" class="px-4 py-2 border border-transparent rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
Run Simulation
</button>
</div>
</div>
<!-- Results Section -->
<div class="lg:col-span-2 space-y-6">
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Simulation Results</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="bg-blue-50 p-4 rounded-lg">
<p class="text-sm text-blue-600 font-medium">Initial Cell Count</p>
<p id="initialCountDisplay" class="text-2xl font-bold text-blue-800">10,000 cells</p>
</div>
<div class="bg-green-50 p-4 rounded-lg">
<p class="text-sm text-green-600 font-medium">Final Cell Count</p>
<p id="finalCountDisplay" class="text-2xl font-bold text-green-800">0 cells</p>
</div>
<div class="bg-purple-50 p-4 rounded-lg">
<p class="text-sm text-purple-600 font-medium">Growth Rate</p>
<p id="growthRateDisplay" class="text-2xl font-bold text-purple-800">0.15 /hour</p>
</div>
<div class="bg-yellow-50 p-4 rounded-lg">
<p class="text-sm text-yellow-600 font-medium">Data Points</p>
<p id="dataPointsDisplay" class="text-2xl font-bold text-yellow-800">0</p>
</div>
</div>
<div class="flex space-x-2 mb-4">
<button id="exportBtn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500">
Export Data (CSV)
</button>
<button id="sampleIntervalBtn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500">
Sample Every 5 Minutes
</button>
</div>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Growth Visualization (Linear Scale)</h2>
<div class="chart-container">
<canvas id="linearChart"></canvas>
</div>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Growth Visualization (Log Scale)</h2>
<div class="chart-container">
<canvas id="logChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script>
// Constants
const AVOGADRO = 6.02214076e23;
// DOM Elements
const initialCellsInput = document.getElementById('initialCells');
const growthRateInput = document.getElementById('growthRate');
const bloodVolumeInput = document.getElementById('bloodVolume');
const noiseLevelInput = document.getElementById('noiseLevel');
const totalTimeValueInput = document.getElementById('totalTimeValue');
const totalTimeUnitSelect = document.getElementById('totalTimeUnit');
const stepSizeValueInput = document.getElementById('stepSizeValue');
const stepSizeUnitSelect = document.getElementById('stepSizeUnit');
const displayUnitSelect = document.getElementById('displayUnit');
const runSimulationBtn = document.getElementById('runSimulation');
const resetBtn = document.getElementById('resetBtn');
const exportBtn = document.getElementById('exportBtn');
const sampleIntervalBtn = document.getElementById('sampleIntervalBtn');
const initialCountDisplay = document.getElementById('initialCountDisplay');
const finalCountDisplay = document.getElementById('finalCountDisplay');
const growthRateDisplay = document.getElementById('growthRateDisplay');
const dataPointsDisplay = document.getElementById('dataPointsDisplay');
// Chart instances
let linearChartInstance = null;
let logChartInstance = null;
// Initialize the app
document.addEventListener('DOMContentLoaded', function() {
// Set up event listeners
runSimulationBtn.addEventListener('click', runSimulation);
resetBtn.addEventListener('click', resetDefaults);
exportBtn.addEventListener('click', exportData);
sampleIntervalBtn.addEventListener('click', applySampleInterval);
// Run initial simulation
runSimulation();
});
// Main simulation function
function runSimulation() {
// Get input values
const initialCells = parseFloat(initialCellsInput.value);
const growthRate = parseFloat(growthRateInput.value);
const bloodVolume = parseFloat(bloodVolumeInput.value);
const noiseLevel = parseFloat(noiseLevelInput.value) / 100;
const totalTimeValue = parseFloat(totalTimeValueInput.value);
const totalTimeUnit = totalTimeUnitSelect.value;
const stepSizeValue = parseFloat(stepSizeValueInput.value);
const stepSizeUnit = stepSizeUnitSelect.value;
const displayUnit = displayUnitSelect.value;
// Convert time units to seconds
const totalSeconds = convertToSeconds(totalTimeValue, totalTimeUnit);
const stepSizeSeconds = convertToSeconds(stepSizeValue, stepSizeUnit);
// Validate inputs
if (stepSizeSeconds <= 0 || stepSizeSeconds > totalSeconds) {
alert('Step size must be positive and smaller than total duration');
return;
}
// Calculate time points
const timeSeconds = numeric.linspace(0, totalSeconds, Math.floor(totalSeconds / stepSizeSeconds) + 1);
// Convert growth rate to per-second
const growthRatePerSecond = growthRate / 3600;
// Calculate ideal growth
const idealCounts = timeSeconds.map(t => initialCells * Math.exp(growthRatePerSecond * t));
// Add log-normal noise
const noisyCounts = addLogNormalNoise(idealCounts, noiseLevel);
// Convert to molar concentration
const molarConversion = 1 / (AVOGADRO * bloodVolume);
const molarIdeal = idealCounts.map(count => count * molarConversion);
const molarNoisy = noisyCounts.map(count => count * molarConversion);
// Convert time to display unit
const displayTime = convertTimeUnit(timeSeconds, displayUnit);
// Update results display
updateResultsDisplay(initialCells, noisyCounts[noisyCounts.length - 1], growthRate, timeSeconds.length);
// Create and update charts
updateCharts(displayTime, molarIdeal, molarNoisy, displayUnit);
// Store data for export
window.simulationData = {
timeSeconds: timeSeconds,
timeHours: timeSeconds.map(t => t / 3600),
idealCounts: idealCounts,
noisyCounts: noisyCounts,
molarIdeal: molarIdeal,
molarNoisy: molarNoisy,
displayUnit: displayUnit
};
}
// Helper function to convert time to seconds
function convertToSeconds(value, unit) {
switch(unit) {
case 'seconds': return value;
case 'minutes': return value * 60;
case 'hours': return value * 3600;
default: return value;
}
}
// Helper function to convert time to display unit
function convertTimeUnit(timeSeconds, displayUnit) {
switch(displayUnit) {
case 'seconds': return timeSeconds;
case 'minutes': return timeSeconds.map(t => t / 60);
case 'hours': return timeSeconds.map(t => t / 3600);
default: return timeSeconds;
}
}
// Helper function to add log-normal noise
function addLogNormalNoise(data, noiseLevel) {
// Set seed for reproducibility (based on current time)
const seed = Date.now() % 1000000;
const random = new Math.seedrandom(seed.toString());
return data.map(value => {
// Generate log-normal noise
const noise = Math.exp(random.normal(0, noiseLevel));
return value * noise;
});
}
// Helper function to update results display
function updateResultsDisplay(initialCells, finalCells, growthRate, dataPoints) {
initialCountDisplay.textContent = initialCells.toLocaleString() + ' cells';
finalCountDisplay.textContent = Math.round(finalCells).toLocaleString() + ' cells';
growthRateDisplay.textContent = growthRate.toFixed(2) + ' /hour';
dataPointsDisplay.textContent = dataPoints.toLocaleString();
}
// Function to create or update charts
function updateCharts(time, ideal, noisy, displayUnit) {
// Get display unit label
const unitLabel = getTimeUnitLabel(displayUnit);
// Prepare data for charts
const chartData = {
labels: time,
datasets: [
{
label: 'Ideal Growth',
data: ideal,
borderColor: 'rgba(59, 130, 246, 1)',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderDash: [5, 5],
borderWidth: 2,
pointRadius: 0,
fill: false
},
{
label: 'Noisy Measurements',
data: noisy,
borderColor: 'rgba(239, 68, 68, 1)',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
borderWidth: 1,
pointRadius: 0,
fill: false
}
]
};
// Common chart options
const commonOptions = {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
title: {
display: true,
text: `Time (${unitLabel})`
},
grid: {
display: true
}
},
y: {
title: {
display: true,
text: 'Concentration (M)'
},
grid: {
display: true
}
}
},
plugins: {
legend: {
position: 'top',
},
tooltip: {
mode: 'index',
intersect: false,
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
}
};
// Linear scale chart
const linearCtx = document.getElementById('linearChart').getContext('2d');
if (linearChartInstance) {
linearChartInstance.data = chartData;
linearChartInstance.options.scales.x.title.text = `Time (${unitLabel})`;
linearChartInstance.update();
} else {
linearChartInstance = new Chart(linearCtx, {
type: 'line',
data: chartData,
options: commonOptions
});
}
// Log scale chart
const logOptions = JSON.parse(JSON.stringify(commonOptions));
logOptions.scales.y.type = 'logarithmic';
logOptions.scales.y.min = Math.min(...ideal.filter(v => v > 0)) * 0.8;
logOptions.scales.y.max = Math.max(...ideal) * 1.2;
const logCtx = document.getElementById('logChart').getContext('2d');
if (logChartInstance) {
logChartInstance.data = chartData;
logChartInstance.options.scales.x.title.text = `Time (${unitLabel})`;
logChartInstance.update();
} else {
logChartInstance = new Chart(logCtx, {
type: 'line',
data: chartData,
options: logOptions
});
}
}
// Helper function to get time unit label
function getTimeUnitLabel(unit) {
switch(unit) {
case 'seconds': return 'sec';
case 'minutes': return 'min';
case 'hours': return 'hr';
default: return '';
}
}
// Reset to defaults
function resetDefaults() {
initialCellsInput.value = 10000;
growthRateInput.value = 0.15;
bloodVolumeInput.value = 5;
noiseLevelInput.value = 5;
totalTimeValueInput.value = 24;
totalTimeUnitSelect.value = 'hours';
stepSizeValueInput.value = 1;
stepSizeUnitSelect.value = 'seconds';
displayUnitSelect.value = 'hours';
runSimulation();
}
// Export data as CSV
function exportData() {
if (!window.simulationData) {
alert('No simulation data to export');
return;
}
const data = window.simulationData;
const headers = [
'Time (seconds)',
'Time (hours)',
'Ideal Cell Count',
'Noisy Cell Count',
'Ideal Concentration (M)',
'Noisy Concentration (M)'
];
let csvContent = headers.join(',') + '\n';
for (let i = 0; i < data.timeSeconds.length; i++) {
const row = [
data.timeSeconds[i],
data.timeHours[i],
data.idealCounts[i],
data.noisyCounts[i],
data.molarIdeal[i],
data.molarNoisy[i]
];
csvContent += row.join(',') + '\n';
}
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', 'tcell_growth_simulation.csv');
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Apply sample interval (5 minutes) to charts
function applySampleInterval() {
if (!window.simulationData) {
alert('Run a simulation first');
return;
}
const data = window.simulationData;
const sampleInterval = 300; // 5 minutes in seconds
const stepSize = convertToSeconds(
parseFloat(stepSizeValueInput.value),
stepSizeUnitSelect.value
);
const intervalInSteps = Math.round(sampleInterval / stepSize);
if (intervalInSteps < 1) {
alert('Step size is too large for 5-minute sampling');
return;
}
const sampledIndices = [];
for (let i = 0; i < data.timeSeconds.length; i += intervalInSteps) {
sampledIndices.push(i);
}
// If last point isn't included, add it
if (sampledIndices[sampledIndices.length - 1] !== data.timeSeconds.length - 1) {
sampledIndices.push(data.timeSeconds.length - 1);
}
// Create sampled data
const sampledTime = sampledIndices.map(i => convertTimeUnit([data.timeSeconds[i]], data.displayUnit)[0]);
const sampledIdeal = sampledIndices.map(i => data.molarIdeal[i]);
const sampledNoisy = sampledIndices.map(i => data.molarNoisy[i]);
// Update charts
updateCharts(sampledTime, sampledIdeal, sampledNoisy, data.displayUnit);
// Update data points display
dataPointsDisplay.textContent = sampledIndices.length.toLocaleString();
}
// Simple seedable random number generator
// From https://github.com/davidbau/seedrandom
Math.seedrandom = function(seed) {
let value = 0;
for (let i = 0; i < seed.length; i++) {
value = value * 32 + seed.charCodeAt(i);
}
return function() {
value = (value * 1664525 + 1013904223) % 4294967296;
return value / 4294967296;
};
};
// Add normal distribution to Math.seedrandom
Math.seedrandom.normal = function(random) {
let u = 0, v = 0;
while(u === 0) u = random();
while(v === 0) v = random();
return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
};
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=STE-Nexus/t-cell-data-visualization-app" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>