mirror of
https://github.com/Ry3yr/Fake-Social-MediaWriter.git
synced 2026-08-19 17:20:03 -04:00
0a543f16b0
Always siine fit
327 lines
10 KiB
HTML
327 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title>Posts Chart with Exact Sine Fit</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<style>
|
|
body {
|
|
font-family: sans-serif;
|
|
margin: 20px;
|
|
}
|
|
#controls {
|
|
margin-bottom: 15px;
|
|
}
|
|
#totalPosts {
|
|
margin-top: 10px;
|
|
font-weight: bold;
|
|
}
|
|
#sineFunction {
|
|
white-space: pre-wrap;
|
|
font-family: monospace;
|
|
background: #f4f4f4;
|
|
padding: 10px;
|
|
margin-top: 15px;
|
|
border-radius: 6px;
|
|
border: 1px solid #ccc;
|
|
min-height: 2em;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<p id="totalPosts">Total Posts: 0</p>
|
|
<div id="controls">
|
|
<input type="text" id="monthInput" placeholder="e.g. 06-2025" />
|
|
<input type="text" id="keywordInput" placeholder="Keyword filter (optional)" />
|
|
<button onclick="filterData()">Filter</button>
|
|
</div>
|
|
|
|
<canvas id="postsChart" width="600" height="300"></canvas>
|
|
<div id="sineFunction"></div>
|
|
|
|
<script>
|
|
let allData = [];
|
|
let chart;
|
|
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
const now = new Date();
|
|
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
const yyyy = now.getFullYear();
|
|
document.getElementById('monthInput').value = `${mm}-${yyyy}`;
|
|
});
|
|
|
|
function getQueryParam(key) {
|
|
const params = new URLSearchParams(window.location.search);
|
|
return params.get(key);
|
|
}
|
|
|
|
function matchesKeyword(entry, keyword) {
|
|
if (!keyword) return true;
|
|
const key = Object.keys(entry)[0];
|
|
const data = entry[key];
|
|
const keywordLower = keyword.toLowerCase();
|
|
return (data.hashtags && data.hashtags.toLowerCase().includes(keywordLower)) ||
|
|
(data.value && data.value.toLowerCase().includes(keywordLower));
|
|
}
|
|
|
|
function renderChart(monthYear = "", keyword = "") {
|
|
const postCounts = {};
|
|
let totalPosts = 0;
|
|
|
|
allData.forEach(entry => {
|
|
const rawDate = Object.keys(entry)[0];
|
|
const yyyy = rawDate.slice(0, 4);
|
|
const mm = rawDate.slice(4, 6);
|
|
const dd = rawDate.slice(6, 8);
|
|
const formatted = `${yyyy}-${mm}-${dd}`;
|
|
|
|
if (monthYear) {
|
|
const [filterMM, filterYYYY] = monthYear.split("-");
|
|
if (mm !== filterMM || yyyy !== filterYYYY) return;
|
|
}
|
|
|
|
if (!matchesKeyword(entry, keyword)) return;
|
|
|
|
if (!postCounts[formatted]) postCounts[formatted] = 0;
|
|
postCounts[formatted]++;
|
|
totalPosts++;
|
|
});
|
|
|
|
document.getElementById('totalPosts').textContent = `Total Posts: ${totalPosts}`;
|
|
|
|
const sortedDates = Object.keys(postCounts).sort();
|
|
if (sortedDates.length === 0) {
|
|
if(chart) chart.destroy();
|
|
document.getElementById('sineFunction').textContent = "No data for these filters.";
|
|
chart = new Chart(document.getElementById('postsChart'), {
|
|
type: 'line',
|
|
data: { labels: [], datasets: [] },
|
|
options: { responsive: true, scales: { y: { beginAtZero: true } } }
|
|
});
|
|
return;
|
|
}
|
|
|
|
const counts = sortedDates.map(date => postCounts[date]);
|
|
const N = sortedDates.length;
|
|
|
|
// If less than 3 points, no sine fit - just show raw data
|
|
if (N < 3) {
|
|
if (chart) chart.destroy();
|
|
chart = new Chart(document.getElementById('postsChart'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: sortedDates,
|
|
datasets: [{
|
|
label: 'Posts per Day',
|
|
data: counts,
|
|
borderColor: 'rgba(75, 192, 192, 1)',
|
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
fill: false,
|
|
tension: 0.3,
|
|
pointRadius: 4,
|
|
pointHoverRadius: 6,
|
|
yAxisID: 'y',
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } } },
|
|
onClick: (e, elements) => {
|
|
if (elements.length > 0) {
|
|
const index = elements[0].index;
|
|
const clickedDate = chart.data.labels[index];
|
|
const url = `https://alceawis.de/other/extra/scripts/fakesocialmedia/commentload.html?date=${clickedDate}`;
|
|
window.open(url, '_blank');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
document.getElementById('sineFunction').textContent = "Sine fit requires at least 3 data points.";
|
|
return;
|
|
}
|
|
|
|
// For N >= 3 do exact sine fit
|
|
const x = counts.map((_, i) => (i / N) * 2 * Math.PI);
|
|
const { A, maxFreq } = buildDesignMatrixExact(x);
|
|
|
|
const At = transpose(A);
|
|
const AtA = matMul(At, A);
|
|
const AtY = matMul(At, counts);
|
|
|
|
const coeffs = solveLinearSystem(AtA, AtY);
|
|
|
|
const fittedY = x.map(xi => {
|
|
let val = coeffs[0];
|
|
for (let k = 1; k <= maxFreq; k++) {
|
|
val += coeffs[2*k - 1] * Math.cos(k * xi) + coeffs[2*k] * Math.sin(k * xi);
|
|
}
|
|
return val;
|
|
});
|
|
|
|
if (chart) chart.destroy();
|
|
|
|
chart = new Chart(document.getElementById('postsChart'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: sortedDates,
|
|
datasets: [
|
|
{
|
|
label: 'Posts per Day',
|
|
data: counts,
|
|
borderColor: 'rgba(75, 192, 192, 1)',
|
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
fill: false,
|
|
tension: 0.3,
|
|
pointRadius: 4,
|
|
pointHoverRadius: 6,
|
|
yAxisID: 'y',
|
|
},
|
|
{
|
|
label: 'Exact Sine Fit',
|
|
data: fittedY,
|
|
borderColor: 'rgba(192, 75, 192, 1)',
|
|
backgroundColor: 'rgba(192, 75, 192, 0.2)',
|
|
fill: false,
|
|
tension: 0.3,
|
|
borderDash: [6, 4],
|
|
pointRadius: 0,
|
|
yAxisID: 'y',
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
y: { beginAtZero: true, ticks: { stepSize: 1 } }
|
|
},
|
|
onClick: (e, elements) => {
|
|
if (elements.length > 0) {
|
|
const index = elements[0].index;
|
|
const clickedDate = chart.data.labels[index];
|
|
const url = `https://alceawis.de/other/extra/scripts/fakesocialmedia/commentload.html?date=${clickedDate}`;
|
|
window.open(url, '_blank');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
document.getElementById('sineFunction').textContent = formatExactSine(coeffs, maxFreq);
|
|
}
|
|
|
|
function filterData() {
|
|
const monthInput = document.getElementById('monthInput').value.trim();
|
|
const keywordInput = document.getElementById('keywordInput').value.trim();
|
|
|
|
const validMonth = /^\d{2}-\d{4}$/.test(monthInput);
|
|
if (monthInput && !validMonth) {
|
|
alert('Please enter a valid month in MM-YYYY format (e.g. 06-2025)');
|
|
return;
|
|
}
|
|
|
|
if (monthInput && keywordInput) {
|
|
renderChart(monthInput, keywordInput);
|
|
} else if (monthInput) {
|
|
renderChart(monthInput);
|
|
} else if (keywordInput) {
|
|
renderChart("", keywordInput);
|
|
} else {
|
|
alert("Please enter either a valid month or keyword to filter.");
|
|
}
|
|
}
|
|
|
|
function transpose(matrix) {
|
|
return matrix[0].map((_, i) => matrix.map(row => row[i]));
|
|
}
|
|
|
|
function matMul(A, B) {
|
|
const rowsA = A.length, colsA = A[0].length;
|
|
const rowsB = B.length, colsB = Array.isArray(B[0]) ? B[0].length : 1;
|
|
if (colsA !== rowsB) throw new Error('Matrix size mismatch');
|
|
const result = Array(rowsA).fill(0).map(() => Array(colsB).fill(0));
|
|
for (let i=0; i < rowsA; i++) {
|
|
for (let j=0; j < colsB; j++) {
|
|
let sum = 0;
|
|
for (let k=0; k < colsA; k++) {
|
|
sum += A[i][k] * (colsB === 1 ? B[k] : B[k][j]);
|
|
}
|
|
result[i][j] = sum;
|
|
}
|
|
}
|
|
return colsB === 1 ? result.map(r => r[0]) : result;
|
|
}
|
|
|
|
function solveLinearSystem(AtA, AtY) {
|
|
const n = AtY.length;
|
|
AtA = AtA.map(row => row.slice());
|
|
AtY = AtY.slice();
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
let maxRow = i;
|
|
for (let k = i + 1; k < n; k++) {
|
|
if (Math.abs(AtA[k][i]) > Math.abs(AtA[maxRow][i])) maxRow = k;
|
|
}
|
|
if (maxRow !== i) {
|
|
[AtA[i], AtA[maxRow]] = [AtA[maxRow], AtA[i]];
|
|
[AtY[i], AtY[maxRow]] = [AtY[maxRow], AtY[i]];
|
|
}
|
|
if (Math.abs(AtA[i][i]) < 1e-14) continue;
|
|
|
|
for (let k = i + 1; k < n; k++) {
|
|
const factor = AtA[k][i] / AtA[i][i];
|
|
for (let j = i; j < n; j++) AtA[k][j] -= factor * AtA[i][j];
|
|
AtY[k] -= factor * AtY[i];
|
|
}
|
|
}
|
|
|
|
const x = Array(n).fill(0);
|
|
for (let i = n - 1; i >= 0; i--) {
|
|
let sum = AtY[i];
|
|
for (let j = i + 1; j < n; j++) sum -= AtA[i][j] * x[j];
|
|
x[i] = Math.abs(AtA[i][i]) > 1e-14 ? sum / AtA[i][i] : 0;
|
|
}
|
|
return x;
|
|
}
|
|
|
|
function buildDesignMatrixExact(x) {
|
|
const N = x.length;
|
|
const maxFreq = Math.floor((N - 1) / 2);
|
|
const M = 1 + 2 * maxFreq;
|
|
const A = Array(N).fill(0).map(() => Array(M).fill(0));
|
|
|
|
for (let i = 0; i < N; i++) {
|
|
A[i][0] = 1;
|
|
for (let k = 1; k <= maxFreq; k++) {
|
|
A[i][2*k - 1] = Math.cos(k * x[i]);
|
|
A[i][2*k] = Math.sin(k * x[i]);
|
|
}
|
|
}
|
|
return { A, maxFreq };
|
|
}
|
|
|
|
function formatExactSine(coeffs, maxFreq) {
|
|
let res = `f(x) = ${coeffs[0].toFixed(4)}`;
|
|
for (let k = 1; k <= maxFreq; k++) {
|
|
const a = coeffs[2*k - 1];
|
|
const b = coeffs[2*k];
|
|
const mag = Math.sqrt(a*a + b*b);
|
|
if (mag < 1e-3) continue;
|
|
const phase = Math.atan2(b, a);
|
|
res += ` + ${mag.toFixed(4)} * sin(${k} * x + ${phase.toFixed(3)})`;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
fetch('/other/extra/scripts/fakesocialmedia/data_alcea.json')
|
|
.then(res => res.json())
|
|
.then(json => {
|
|
allData = json;
|
|
const queryMonth = getQueryParam("month");
|
|
const queryKeyword = getQueryParam("keyword") || "";
|
|
document.getElementById('monthInput').value = queryMonth || document.getElementById('monthInput').value;
|
|
document.getElementById('keywordInput').value = queryKeyword;
|
|
renderChart(queryMonth, queryKeyword);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|