Refactor dashboard SVG charts and animations
- Removed the old SVG file for the dashboard chart and replaced it with a new implementation directly in the PHP file. - Updated the SVG structure to improve accessibility and styling, including the use of CSS classes for dynamic theming. - Enhanced the bar charts, line graph, and pie chart with new gradients and animations. - Adjusted the enqueue script for the dashboard animator to include versioning based on file modification time.
This commit is contained in:
@@ -2602,137 +2602,32 @@ p:last-child { margin-bottom: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* ── Dashboard Chart Animations ────────────────────────── */
|
||||
/* ── Dashboard Chart ───────────────────────────────────── */
|
||||
.dashboard-chart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 1.5rem;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
will-change: opacity;
|
||||
data-dashboard-container: true;
|
||||
background: var(--card-bg, rgba(255,255,255,.04));
|
||||
border: 1px solid var(--color-border, #333);
|
||||
border-radius: var(--radius-lg, 20px);
|
||||
box-shadow: var(--shadow-md, 0 4px 24px rgba(0,0,0,.12));
|
||||
}
|
||||
|
||||
.dashboard-chart {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
font-family: var(--font-sans);
|
||||
user-select: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Bar animation - smooth height transitions */
|
||||
.dashboard-chart .bar {
|
||||
fill: url(#barGradient);
|
||||
will-change: height;
|
||||
transition: height 0.3s ease-out;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
/* Line path - draw animation */
|
||||
.dashboard-chart .line-path {
|
||||
stroke: var(--color-accent);
|
||||
fill: none;
|
||||
stroke-width: 2.5;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
will-change: stroke-dashoffset, d;
|
||||
transition: stroke 0.3s ease;
|
||||
}
|
||||
|
||||
.dashboard-chart .line-fill {
|
||||
fill: url(#lineGradient);
|
||||
will-change: d;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Pie chart segment animations */
|
||||
.dashboard-chart .pie-segment {
|
||||
will-change: transform;
|
||||
transform-origin: center;
|
||||
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
/* Chart title styling */
|
||||
.dashboard-chart .chart-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
fill: var(--color-heading);
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.dashboard-chart .chart-label {
|
||||
font-size: 12px;
|
||||
fill: var(--color-text-muted);
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.dashboard-chart .chart-value {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
fill: var(--color-text);
|
||||
transition: fill 0.2s ease;
|
||||
}
|
||||
|
||||
/* Dark mode support */
|
||||
[data-theme="dark"] .dashboard-chart {
|
||||
--text-primary: #E0E0E0;
|
||||
--text-secondary: #9E9E9E;
|
||||
--primary-color: #66BB6A;
|
||||
--accent-color: #4CAF50;
|
||||
--border-color: #333333;
|
||||
}
|
||||
|
||||
[data-theme="light"] .dashboard-chart {
|
||||
--text-primary: #1a1a1a;
|
||||
--text-secondary: #666;
|
||||
--primary-color: #3b82f6;
|
||||
--accent-color: #10b981;
|
||||
--border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
/* Performance: disable animations on reduced motion preference */
|
||||
/* Reduced-motion: JS already checks the media query, belt-and-suspenders */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.dashboard-chart .bar,
|
||||
.dashboard-chart .line-path,
|
||||
.dashboard-chart .line-fill,
|
||||
.dashboard-chart .pie-segment {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
.dashboard-chart .pie-segment { transition: none !important; }
|
||||
}
|
||||
|
||||
/* Responsive: Adjust for smaller screens */
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-chart-container {
|
||||
padding: 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.dashboard-chart .chart-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.dashboard-chart .chart-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.dashboard-chart .chart-value {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Touch devices: No hover effects */
|
||||
@media (hover: none) {
|
||||
.dashboard-chart-container:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.dashboard-chart-container { padding: 1rem; border-radius: var(--radius-md, 12px); }
|
||||
}
|
||||
|
||||
.cta-banner {
|
||||
|
||||
@@ -1,311 +1,160 @@
|
||||
/**
|
||||
* Dashboard Chart Animator
|
||||
* Continuously animates bar charts, line graphs, and pie charts
|
||||
* Performance-optimized with IntersectionObserver and requestAnimationFrame
|
||||
* Continuously animates SVG bar charts, line graph, and pie chart.
|
||||
* Uses requestAnimationFrame, pauses off-screen via IntersectionObserver.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
class DashboardAnimator {
|
||||
constructor(svgElement) {
|
||||
this.svg = svgElement;
|
||||
this.animationId = null;
|
||||
this.isPaused = false;
|
||||
this.isReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
this.startTime = Date.now();
|
||||
|
||||
// Chart dimensions
|
||||
this.barChartHeight = 120;
|
||||
this.chartUpdateSpeed = 1500; // Faster: 1.5s cycle for constant movement
|
||||
|
||||
// Cache DOM elements - try multiple selector methods for robustness
|
||||
this.barsGroup1 = this.svg.querySelectorAll('#bars-group-1 rect.bar');
|
||||
this.barsGroup2 = this.svg.querySelectorAll('#bars-group-2 rect.bar');
|
||||
this.valuesGroup1 = this.svg.querySelectorAll('#values-group-1 text.chart-value');
|
||||
this.valuesGroup2 = this.svg.querySelectorAll('#values-group-2 text.chart-value');
|
||||
this.linePath = this.svg.querySelector('#line-path');
|
||||
this.lineFill = this.svg.querySelector('#line-fill');
|
||||
this.pieSegments = this.svg.querySelectorAll('g.pie-segment');
|
||||
|
||||
// Debug: Check if elements were found
|
||||
const hasElements = this.barsGroup1.length > 0 || this.linePath || this.pieSegments.length > 0;
|
||||
if (!hasElements) {
|
||||
console.warn('[DashboardAnimator] No chart elements found. Debugging info:');
|
||||
console.warn(' SVG element:', this.svg);
|
||||
console.warn(' bars-group-1:', this.barsGroup1.length);
|
||||
console.warn(' line-path:', !!this.linePath);
|
||||
console.warn(' pie-segment:', this.pieSegments.length);
|
||||
// Try direct getElementById as fallback
|
||||
console.warn(' Testing getElementById on document:', document.getElementById('bars-group-1'));
|
||||
} else {
|
||||
console.log('[DashboardAnimator] ✓ Found elements - Bars:', this.barsGroup1.length, 'Line:', !!this.linePath, 'Pie:', this.pieSegments.length);
|
||||
}
|
||||
|
||||
// Noise generators for smooth, realistic data
|
||||
this.noisePhase1 = Math.random() * Math.PI * 2;
|
||||
this.noisePhase2 = Math.random() * Math.PI * 2;
|
||||
this.noisePhase3 = Math.random() * Math.PI * 2;
|
||||
this.noisePhase4 = Math.random() * Math.PI * 2;
|
||||
this.noisePhase5 = Math.random() * Math.PI * 2;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.isReducedMotion && (this.barsGroup1.length || this.linePath || this.pieSegments.length)) {
|
||||
console.log('[DashboardAnimator] ✓ Starting animation loop');
|
||||
// Visual indicator that animator started
|
||||
this.svg.style.cssText = 'border: 2px solid green; border-radius: 4px;';
|
||||
setTimeout(() => {
|
||||
this.svg.style.border = '';
|
||||
}, 500);
|
||||
this.animate();
|
||||
this.attachHoverListeners();
|
||||
} else if (this.isReducedMotion) {
|
||||
console.log('[DashboardAnimator] Motion preference: reduced');
|
||||
} else {
|
||||
console.log('[DashboardAnimator] ✗ No animatable elements found');
|
||||
this.svg.style.cssText = 'border: 2px solid red; border-radius: 4px;';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perlin-like noise for smooth, natural-looking data
|
||||
* Uses sine waves at different frequencies
|
||||
*/
|
||||
generateNoise(phase, frequency = 0.5) {
|
||||
return Math.sin(phase * frequency) * 0.5 + 0.5; // Normalize to 0-1
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate smooth, continuous data values
|
||||
* Uses combination of sine waves for organic motion
|
||||
*/
|
||||
generateValue(basePhase, index, max = 100) {
|
||||
const time = (Date.now() - this.startTime) / this.chartUpdateSpeed;
|
||||
const phase = basePhase + time * 0.3 + index * 0.4;
|
||||
|
||||
// Multi-frequency sine wave for natural variation
|
||||
const noise1 = Math.sin(phase * 0.7) * 0.4;
|
||||
const noise2 = Math.sin(phase * 1.3) * 0.3;
|
||||
const noise3 = Math.sin(phase * 0.3) * 0.2;
|
||||
const base = 0.5 + noise1 + noise2 + noise3;
|
||||
|
||||
// Ensure value stays in range
|
||||
return Math.max(10, Math.min(max, base * max * 0.9));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bar chart with smooth animation
|
||||
*/
|
||||
updateBars(barsElements, valuesElements, maxValue = 100, isPercent = true) {
|
||||
if (!barsElements || barsElements.length === 0) return;
|
||||
|
||||
barsElements.forEach((bar, index) => {
|
||||
const value = this.generateValue(this.noisePhase1, index, maxValue);
|
||||
const heightPercent = (value / maxValue) * 100;
|
||||
const height = (heightPercent / 100) * this.barChartHeight;
|
||||
|
||||
// For SVG bars to grow upward, adjust y position inversely
|
||||
const y = this.barChartHeight - height;
|
||||
|
||||
bar.setAttribute('height', height);
|
||||
bar.setAttribute('y', y);
|
||||
|
||||
if (valuesElements && valuesElements[index]) {
|
||||
valuesElements[index].textContent = isPercent ?
|
||||
Math.round(value) + '%' :
|
||||
Math.round(value);
|
||||
valuesElements[index].setAttribute('data-value', value.toFixed(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update line graph with smooth curve
|
||||
*/
|
||||
updateLineGraph() {
|
||||
if (!this.linePath || !this.lineFill) return;
|
||||
|
||||
const time = (Date.now() - this.startTime) / this.chartUpdateSpeed;
|
||||
const points = [];
|
||||
|
||||
// Generate 3 points for quadratic bezier curve
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const x = i * 100;
|
||||
const basePhase = this.noisePhase3 + time * 0.2 + i * 0.5;
|
||||
const noise = Math.sin(basePhase) * 0.3 + Math.sin(basePhase * 0.5) * 0.2;
|
||||
const y = 80 + noise * 60 - i * 15;
|
||||
points.push({ x, y });
|
||||
}
|
||||
|
||||
// Build quadratic bezier path
|
||||
const pathData = `M${points[0].x},${points[0].y} Q${points[1].x},${points[1].y} ${points[2].x},${points[2].y}`;
|
||||
this.linePath.setAttribute('d', pathData);
|
||||
this.lineFill.setAttribute('d', `${pathData} L200,160 Q100,140 0,160 Z`);
|
||||
|
||||
// Animate stroke-dasharray for drawing effect
|
||||
const strokeLength = 200;
|
||||
const offset = ((time * 50) % strokeLength);
|
||||
this.linePath.setAttribute('stroke-dashoffset', offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update pie chart with smooth segment rotation
|
||||
*/
|
||||
updatePieChart() {
|
||||
if (!this.pieSegments || this.pieSegments.length === 0) return;
|
||||
|
||||
const time = (Date.now() - this.startTime) / (this.chartUpdateSpeed * 0.5);
|
||||
const baseRotation = (time * 15) % 360; // Slow rotation
|
||||
|
||||
this.pieSegments.forEach((segment, index) => {
|
||||
// Add wobble effect to each segment
|
||||
const wobble = Math.sin(time * 0.5 + index * Math.PI / 2) * 3;
|
||||
segment.setAttribute('transform', `rotate(${baseRotation + index * 90 + wobble})`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Main animation loop using requestAnimationFrame
|
||||
*/
|
||||
animate = () => {
|
||||
if (!this.isPaused) {
|
||||
try {
|
||||
this.updateBars(this.barsGroup1, this.valuesGroup1, 100, true);
|
||||
this.updateBars(this.barsGroup2, this.valuesGroup2, 5000, false);
|
||||
this.updateLineGraph();
|
||||
this.updatePieChart();
|
||||
} catch (err) {
|
||||
console.error('[DashboardAnimator] Animation error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
this.animationId = requestAnimationFrame(this.animate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach hover event listeners to pause/resume animation
|
||||
*/
|
||||
attachHoverListeners() {
|
||||
const container = this.svg.closest('[data-dashboard-container]') || this.svg.parentElement;
|
||||
|
||||
if (container) {
|
||||
container.addEventListener('mouseenter', () => this.pause());
|
||||
container.addEventListener('mouseleave', () => this.resume());
|
||||
|
||||
// Touch support: pause on touch
|
||||
container.addEventListener('touchstart', () => this.pause());
|
||||
container.addEventListener('touchend', () => this.resume());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause animation (on hover)
|
||||
*/
|
||||
pause() {
|
||||
this.isPaused = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume animation (after hover)
|
||||
*/
|
||||
resume() {
|
||||
this.isPaused = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop animation and clean up
|
||||
*/
|
||||
destroy() {
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
this.animationId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ── Configuration ──────────────────────────────────── */
|
||||
var SPEED = 0.0025; // phase increment per ms (~4-6 s visible cycle)
|
||||
var BAR_H = 120; // max bar height in SVG units
|
||||
var BAR_MIN = 0.12; // minimum bar height ratio (12 %)
|
||||
var LINE_PTS = 8; // number of points on the line graph
|
||||
|
||||
/**
|
||||
* Initialize dashboard animation when element enters viewport
|
||||
*/
|
||||
function initDashboardAnimation() {
|
||||
const dashboardCharts = document.querySelectorAll('.dashboard-chart');
|
||||
console.log('[initDashboardAnimation] Found', dashboardCharts.length, 'dashboard charts');
|
||||
|
||||
if (!dashboardCharts.length) return;
|
||||
|
||||
dashboardCharts.forEach((chart, idx) => {
|
||||
// Check if element is likely in viewport
|
||||
const rect = chart.getBoundingClientRect();
|
||||
const isVisible = rect.top < window.innerHeight && rect.bottom > 0;
|
||||
console.log('[initDashboardAnimation] Chart', idx, 'visible:', isVisible, 'rect:', rect);
|
||||
|
||||
if (isVisible || !chart._dashboardAnimator) {
|
||||
// Start animation immediately if visible or if not set up yet
|
||||
if (!chart._dashboardAnimator) {
|
||||
console.log('[initDashboardAnimation] Creating DashboardAnimator for chart', idx);
|
||||
try {
|
||||
chart._dashboardAnimator = new DashboardAnimator(chart);
|
||||
console.log('[initDashboardAnimation] ✓ DashboardAnimator created successfully');
|
||||
} catch (err) {
|
||||
console.error('[initDashboardAnimation] ✗ Failed to create DashboardAnimator:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Also set up IntersectionObserver for when elements come into view
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observerOptions = {
|
||||
root: null,
|
||||
rootMargin: '100px',
|
||||
threshold: 0.05
|
||||
/* ── Colour sets (matches theme tokens) ─────────────── */
|
||||
var DARK = { text: '#E0E0E0', muted: '#9E9E9E', border: '#333', center: '#222' };
|
||||
var LIGHT = { text: '#333333', muted: '#666666', border: '#E0E0E0', center: '#fff' };
|
||||
|
||||
function isDark() {
|
||||
return document.documentElement.getAttribute('data-theme') === 'dark';
|
||||
}
|
||||
function palette() { return isDark() ? DARK : LIGHT; }
|
||||
|
||||
/** Stacked sine waves => smooth organic value in 0..1 range */
|
||||
function wave(t, offset) {
|
||||
return Math.max(0, Math.min(1,
|
||||
0.55 +
|
||||
Math.sin(t * 1.0 + offset) * 0.30 +
|
||||
Math.sin(t * 2.3 + offset * 1.4) * 0.25 +
|
||||
Math.sin(t * 0.7 + offset * 0.6) * 0.20
|
||||
));
|
||||
}
|
||||
|
||||
/* ── Per-chart state ────────────────────────────────── */
|
||||
function makeState(svg) {
|
||||
return {
|
||||
svg: svg,
|
||||
bars1: svg.querySelectorAll('#bars-group-1 .bar'),
|
||||
bars2: svg.querySelectorAll('#bars-group-2 .bar'),
|
||||
vals1: svg.querySelectorAll('#values-group-1 text'),
|
||||
vals2: svg.querySelectorAll('#values-group-2 text'),
|
||||
linePath: svg.querySelector('#line-path'),
|
||||
lineFill: svg.querySelector('#line-fill'),
|
||||
pieSegs: svg.querySelectorAll('.pie-segment'),
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
paused: false,
|
||||
raf: null,
|
||||
themeN: 0
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
console.log('[IntersectionObserver]', entry.isIntersecting ? 'Intersecting' : 'Not intersecting');
|
||||
if (entry.isIntersecting) {
|
||||
if (!entry.target._dashboardAnimator) {
|
||||
console.log('[IntersectionObserver] Creating new DashboardAnimator');
|
||||
try {
|
||||
entry.target._dashboardAnimator = new DashboardAnimator(entry.target);
|
||||
} catch (err) {
|
||||
console.error('[IntersectionObserver] Failed:', err);
|
||||
}
|
||||
} else {
|
||||
entry.target._dashboardAnimator.resume();
|
||||
}
|
||||
} else {
|
||||
if (entry.target._dashboardAnimator) {
|
||||
entry.target._dashboardAnimator.pause();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
dashboardCharts.forEach(chart => observer.observe(chart));
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-initialize when DOM is ready
|
||||
console.log('[Dashboard Animator] Script loaded, readyState:', document.readyState);
|
||||
/* ── Update routines ────────────────────────────────── */
|
||||
function updateBars(bars, vals, st, pct) {
|
||||
var i, v, h;
|
||||
for (i = 0; i < bars.length; i++) {
|
||||
v = wave(st.phase, i * 1.1);
|
||||
v = Math.max(BAR_MIN, v);
|
||||
h = v * BAR_H;
|
||||
bars[i].setAttribute('height', h);
|
||||
bars[i].setAttribute('y', BAR_H - h);
|
||||
}
|
||||
for (i = 0; i < Math.min(bars.length, vals.length); i++) {
|
||||
v = wave(st.phase, i * 1.1);
|
||||
v = Math.max(BAR_MIN, v);
|
||||
vals[i].textContent = pct ? Math.round(v * 100) + '%' : Math.round(v * 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function tryInitialize() {
|
||||
console.log('[Dashboard Animator] tryInitialize called');
|
||||
const charts = document.querySelectorAll('.dashboard-chart');
|
||||
console.log('[Dashboard Animator] Found', charts.length, 'dashboard charts on page');
|
||||
if (charts.length > 0) {
|
||||
console.log('[Dashboard Animator] Found charts, initializing now...');
|
||||
initDashboardAnimation();
|
||||
function updateLine(st) {
|
||||
if (!st.linePath) return;
|
||||
var d = 'M', i, x, y, v;
|
||||
for (i = 0; i < LINE_PTS; i++) {
|
||||
x = (i / (LINE_PTS - 1)) * 200;
|
||||
v = wave(st.phase * 0.8, i * 0.9);
|
||||
y = 25 + (1 - v) * 110;
|
||||
d += (i ? ' L' : '') + x.toFixed(1) + ',' + y.toFixed(1);
|
||||
}
|
||||
st.linePath.setAttribute('d', d);
|
||||
if (st.lineFill) st.lineFill.setAttribute('d', d + ' L200,145 L0,145 Z');
|
||||
}
|
||||
|
||||
function updatePie(st) {
|
||||
if (!st.pieSegs.length) return;
|
||||
var base = (st.phase * 40) % 360;
|
||||
for (var i = 0; i < st.pieSegs.length; i++) {
|
||||
var w = Math.sin(st.phase * 0.6 + i * 1.5) * 4;
|
||||
st.pieSegs[i].setAttribute('transform', 'rotate(' + (base + i * 90 + w) + ')');
|
||||
}
|
||||
}
|
||||
|
||||
function applyTheme(st) {
|
||||
var c = palette();
|
||||
var q = st.svg.querySelectorAll.bind(st.svg);
|
||||
var all, j;
|
||||
|
||||
all = q('.ct'); for (j = 0; j < all.length; j++) all[j].setAttribute('fill', c.text);
|
||||
all = q('.cl'); for (j = 0; j < all.length; j++) all[j].setAttribute('fill', c.muted);
|
||||
all = q('.cv'); for (j = 0; j < all.length; j++) all[j].setAttribute('fill', c.text);
|
||||
all = q('.grid-line'); for (j = 0; j < all.length; j++) all[j].setAttribute('stroke', c.border);
|
||||
|
||||
var cc = st.svg.querySelector('#pie-center');
|
||||
if (cc) { cc.setAttribute('fill', c.center); cc.setAttribute('stroke', c.border); }
|
||||
var pt = st.svg.querySelector('#pie-center-text');
|
||||
if (pt) pt.setAttribute('fill', c.text);
|
||||
}
|
||||
|
||||
/* ── Animation loop ─────────────────────────────────── */
|
||||
function tick(st) {
|
||||
if (!st.paused) {
|
||||
st.phase += SPEED * 16; // ~16 ms per frame at 60 fps
|
||||
updateBars(st.bars1, st.vals1, st, true);
|
||||
updateBars(st.bars2, st.vals2, st, false);
|
||||
updateLine(st);
|
||||
updatePie(st);
|
||||
|
||||
// Re-apply theme colours every ~30 frames (~0.5 s)
|
||||
if (++st.themeN > 30) { st.themeN = 0; applyTheme(st); }
|
||||
}
|
||||
st.raf = requestAnimationFrame(function () { tick(st); });
|
||||
}
|
||||
|
||||
/* ── Hover pause / resume ───────────────────────────── */
|
||||
function attachHover(st) {
|
||||
var el = st.svg.closest('[data-dashboard-container]') || st.svg.parentElement;
|
||||
if (!el) return;
|
||||
el.addEventListener('mouseenter', function () { st.paused = true; });
|
||||
el.addEventListener('mouseleave', function () { st.paused = false; });
|
||||
el.addEventListener('touchstart', function () { st.paused = true; }, { passive: true });
|
||||
el.addEventListener('touchend', function () { st.paused = false; }, { passive: true });
|
||||
}
|
||||
|
||||
/* ── IntersectionObserver (pause off-screen) ────────── */
|
||||
function observe(st) {
|
||||
if (!('IntersectionObserver' in window)) return;
|
||||
new IntersectionObserver(function (entries) {
|
||||
for (var i = 0; i < entries.length; i++) st.paused = !entries[i].isIntersecting;
|
||||
}, { rootMargin: '200px', threshold: 0.05 }).observe(st.svg);
|
||||
}
|
||||
|
||||
/* ── Bootstrap ──────────────────────────────────────── */
|
||||
function boot() {
|
||||
var svgs = document.querySelectorAll('.dashboard-chart');
|
||||
if (!svgs.length) return;
|
||||
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
for (var i = 0; i < svgs.length; i++) {
|
||||
if (svgs[i]._dbAnim) continue;
|
||||
var st = makeState(svgs[i]);
|
||||
svgs[i]._dbAnim = st;
|
||||
applyTheme(st);
|
||||
if (!reduced) { tick(st); attachHover(st); observe(st); }
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', boot);
|
||||
} else {
|
||||
console.log('[Dashboard Animator] No charts found yet, retrying in 100ms...');
|
||||
setTimeout(tryInitialize, 100);
|
||||
boot();
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
console.log('[Dashboard Animator] DOM still loading, waiting for DOMContentLoaded');
|
||||
document.addEventListener('DOMContentLoaded', tryInitialize);
|
||||
} else {
|
||||
console.log('[Dashboard Animator] DOM already loaded, initializing immediately');
|
||||
tryInitialize();
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
<svg viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" class="dashboard-chart">
|
||||
<defs>
|
||||
<style>
|
||||
.dashboard-chart { background: transparent; }
|
||||
.chart-title { font-size: 14px; font-weight: 600; fill: var(--text-primary, #1a1a1a); }
|
||||
.chart-label { font-size: 12px; fill: var(--text-secondary, #666); }
|
||||
.chart-value { font-size: 13px; font-weight: 500; fill: var(--text-primary, #1a1a1a); }
|
||||
.bar { fill: url(#barGradient); transition: height 0.3s ease; transform-origin: bottom; }
|
||||
.line-path { stroke: var(--accent-color, #10b981); fill: none; stroke-width: 2.5; stroke-linecap: round; }
|
||||
.pie-segment { transition: transform 0.4s ease; }
|
||||
</style>
|
||||
|
||||
<!-- Gradients -->
|
||||
<linearGradient id="barGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:var(--primary-color, #3b82f6);stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:var(--accent-color, #10b981);stop-opacity:0.7" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:var(--accent-color, #10b981);stop-opacity:0.3" />
|
||||
<stop offset="100%" style="stop-color:var(--accent-color, #10b981);stop-opacity:0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Left: Bar Charts -->
|
||||
<g id="bar-charts" transform="translate(10, 10)">
|
||||
<!-- Bar Chart 1 -->
|
||||
<text x="0" y="0" class="chart-title">Performance</text>
|
||||
<g id="bars-group-1" transform="translate(0, 25)">
|
||||
<rect class="bar" x="0" y="0" width="18" height="0" data-index="0" data-label="API"/>
|
||||
<rect class="bar" x="25" y="0" width="18" height="0" data-index="1" data-label="Cache"/>
|
||||
<rect class="bar" x="50" y="0" width="18" height="0" data-index="2" data-label="DB"/>
|
||||
<rect class="bar" x="75" y="0" width="18" height="0" data-index="3" data-label="Queue"/>
|
||||
<rect class="bar" x="100" y="0" width="18" height="0" data-index="4" data-label="Worker"/>
|
||||
</g>
|
||||
<g transform="translate(0, 145)">
|
||||
<text x="0" y="0" class="chart-label">API</text>
|
||||
<text x="25" y="0" class="chart-label">Cache</text>
|
||||
<text x="50" y="0" class="chart-label">DB</text>
|
||||
<text x="75" y="0" class="chart-label">Queue</text>
|
||||
<text x="100" y="0" class="chart-label">Worker</text>
|
||||
</g>
|
||||
<g id="values-group-1" transform="translate(0, 160)">
|
||||
<text x="9" y="0" class="chart-value" text-anchor="middle" data-value="0">0%</text>
|
||||
<text x="34" y="0" class="chart-value" text-anchor="middle" data-value="0">0%</text>
|
||||
<text x="59" y="0" class="chart-value" text-anchor="middle" data-value="0">0%</text>
|
||||
<text x="84" y="0" class="chart-value" text-anchor="middle" data-value="0">0%</text>
|
||||
<text x="109" y="0" class="chart-value" text-anchor="middle" data-value="0">0%</text>
|
||||
</g>
|
||||
|
||||
<!-- Bar Chart 2 -->
|
||||
<text x="150" y="0" class="chart-title">Requests/sec</text>
|
||||
<g id="bars-group-2" transform="translate(150, 25)">
|
||||
<rect class="bar" x="0" y="0" width="18" height="0" data-index="0" data-label="Read"/>
|
||||
<rect class="bar" x="25" y="0" width="18" height="0" data-index="1" data-label="Write"/>
|
||||
<rect class="bar" x="50" y="0" width="18" height="0" data-index="2" data-label="Update"/>
|
||||
<rect class="bar" x="75" y="0" width="18" height="0" data-index="3" data-label="Delete"/>
|
||||
</g>
|
||||
<g transform="translate(150, 145)">
|
||||
<text x="0" y="0" class="chart-label">Read</text>
|
||||
<text x="25" y="0" class="chart-label">Write</text>
|
||||
<text x="50" y="0" class="chart-label">Update</text>
|
||||
<text x="75" y="0" class="chart-label">Delete</text>
|
||||
</g>
|
||||
<g id="values-group-2" transform="translate(150, 160)">
|
||||
<text x="9" y="0" class="chart-value" text-anchor="middle" data-value="0">0</text>
|
||||
<text x="34" y="0" class="chart-value" text-anchor="middle" data-value="0">0</text>
|
||||
<text x="59" y="0" class="chart-value" text-anchor="middle" data-value="0">0</text>
|
||||
<text x="84" y="0" class="chart-value" text-anchor="middle" data-value="0">0</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Middle: Line Graph -->
|
||||
<g id="line-graph" transform="translate(320, 10)">
|
||||
<text x="0" y="0" class="chart-title">Traffic Trend</text>
|
||||
<g transform="translate(0, 25)">
|
||||
<!-- Background grid -->
|
||||
<line x1="0" y1="0" x2="200" y2="0" stroke="var(--border-color, #e5e7eb)" stroke-width="0.5"/>
|
||||
<line x1="0" y1="40" x2="200" y2="40" stroke="var(--border-color, #e5e7eb)" stroke-width="0.5"/>
|
||||
<line x1="0" y1="80" x2="200" y2="80" stroke="var(--border-color, #e5e7eb)" stroke-width="0.5"/>
|
||||
<line x1="0" y1="120" x2="200" y2="120" stroke="var(--border-color, #e5e7eb)" stroke-width="0.5"/>
|
||||
</g>
|
||||
<path id="line-path" class="line-path" d="M0,80 Q50,60 100,70 T200,50" stroke-dasharray="200" stroke-dashoffset="200"/>
|
||||
<path id="line-fill" d="M0,80 Q50,60 100,70 T200,50 L200,160 Q100,140 0,160 Z" fill="url(#lineGradient)"/>
|
||||
</g>
|
||||
|
||||
<!-- Right: Pie Chart -->
|
||||
<g id="pie-chart" transform="translate(580, 10)">
|
||||
<text x="60" y="0" class="chart-title" text-anchor="middle">Distribution</text>
|
||||
<g transform="translate(60, 80)">
|
||||
<!-- Pie segments with animation -->
|
||||
<g class="pie-segment" id="pie-seg-1" transform="rotate(0)">
|
||||
<path d="M 0,0 L 0,-45 A 45,45 0 0,1 31.82,-31.82 Z" fill="var(--primary-color, #3b82f6)" opacity="0.9"/>
|
||||
</g>
|
||||
<g class="pie-segment" id="pie-seg-2" transform="rotate(90)">
|
||||
<path d="M 0,0 L 0,-45 A 45,45 0 0,1 31.82,-31.82 Z" fill="var(--accent-color, #10b981)" opacity="0.8"/>
|
||||
</g>
|
||||
<g class="pie-segment" id="pie-seg-3" transform="rotate(180)">
|
||||
<path d="M 0,0 L 0,-45 A 45,45 0 0,1 31.82,-31.82 Z" fill="#f59e0b" opacity="0.7"/>
|
||||
</g>
|
||||
<g class="pie-segment" id="pie-seg-4" transform="rotate(270)">
|
||||
<path d="M 0,0 L 0,-45 A 45,45 0 0,1 31.82,-31.82 Z" fill="#ef4444" opacity="0.7"/>
|
||||
</g>
|
||||
<!-- Center circle -->
|
||||
<circle cx="0" cy="0" r="18" fill="white" stroke="var(--border-color, #e5e7eb)" stroke-width="1"/>
|
||||
<text x="0" y="5" text-anchor="middle" class="chart-value">100%</text>
|
||||
</g>
|
||||
<!-- Legend -->
|
||||
<g transform="translate(0, 210)">
|
||||
<rect x="0" y="0" width="8" height="8" fill="var(--primary-color, #3b82f6)"/>
|
||||
<text x="12" y="7" class="chart-label">Service A</text>
|
||||
|
||||
<rect x="0" y="15" width="8" height="8" fill="var(--accent-color, #10b981)"/>
|
||||
<text x="12" y="22" class="chart-label">Service B</text>
|
||||
|
||||
<rect x="100" y="0" width="8" height="8" fill="#f59e0b"/>
|
||||
<text x="112" y="7" class="chart-label">Service C</text>
|
||||
|
||||
<rect x="100" y="15" width="8" height="8" fill="#ef4444"/>
|
||||
<text x="112" y="22" class="chart-label">Service D</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.7 KiB |
@@ -1344,102 +1344,118 @@ function oribi_render_platform_row( $a ) {
|
||||
$is_dashboard = ! empty( $a['isDashboard'] ) || stripos( $heading_text, 'dashboard' ) !== false || stripos( $heading_text, 'data' ) !== false;
|
||||
|
||||
if ( $is_dashboard ) {
|
||||
// Render dashboard chart animation
|
||||
$visual_html = '<div class="dashboard-chart-container" data-dashboard-container="true"><svg viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" class="dashboard-chart" aria-label="Real-time dashboard with animated charts">
|
||||
<defs>
|
||||
<linearGradient id="barGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#004225;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#4CAF50;stop-opacity:0.8" />
|
||||
</linearGradient>
|
||||
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#4CAF50;stop-opacity:0.3" />
|
||||
<stop offset="100%" style="stop-color:#4CAF50;stop-opacity:0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="bar-charts" transform="translate(10, 10)">
|
||||
<text x="0" y="0" style="font-size:14px;font-weight:600;fill:#333333">Performance</text>
|
||||
<g id="bars-group-1" transform="translate(0, 25)">
|
||||
<rect class="bar" x="0" y="0" width="18" height="0" fill="url(#barGradient)" data-index="0" data-label="API"/>
|
||||
<rect class="bar" x="25" y="0" width="18" height="0" fill="url(#barGradient)" data-index="1" data-label="Cache"/>
|
||||
<rect class="bar" x="50" y="0" width="18" height="0" fill="url(#barGradient)" data-index="2" data-label="DB"/>
|
||||
<rect class="bar" x="75" y="0" width="18" height="0" fill="url(#barGradient)" data-index="3" data-label="Queue"/>
|
||||
<rect class="bar" x="100" y="0" width="18" height="0" fill="url(#barGradient)" data-index="4" data-label="Worker"/>
|
||||
</g>
|
||||
<g transform="translate(0, 145)">
|
||||
<text x="0" y="0" style="font-size:12px;fill:#666666">API</text>
|
||||
<text x="25" y="0" style="font-size:12px;fill:#666666">Cache</text>
|
||||
<text x="50" y="0" style="font-size:12px;fill:#666666">DB</text>
|
||||
<text x="75" y="0" style="font-size:12px;fill:#666666">Queue</text>
|
||||
<text x="100" y="0" style="font-size:12px;fill:#666666">Worker</text>
|
||||
</g>
|
||||
<g id="values-group-1" transform="translate(0, 160)">
|
||||
<text x="9" y="0" style="font-size:13px;font-weight:500;fill:#333333" text-anchor="middle" data-value="0">0%</text>
|
||||
<text x="34" y="0" style="font-size:13px;font-weight:500;fill:#333333" text-anchor="middle" data-value="0">0%</text>
|
||||
<text x="59" y="0" style="font-size:13px;font-weight:500;fill:#333333" text-anchor="middle" data-value="0">0%</text>
|
||||
<text x="84" y="0" style="font-size:13px;font-weight:500;fill:#333333" text-anchor="middle" data-value="0">0%</text>
|
||||
<text x="109" y="0" style="font-size:13px;font-weight:500;fill:#333333" text-anchor="middle" data-value="0">0%</text>
|
||||
</g>
|
||||
<text x="150" y="0" style="font-size:14px;font-weight:600;fill:#333333">Requests/sec</text>
|
||||
<g id="bars-group-2" transform="translate(150, 25)">
|
||||
<rect class="bar" x="0" y="0" width="18" height="0" fill="url(#barGradient)" data-index="0" data-label="Read"/>
|
||||
<rect class="bar" x="25" y="0" width="18" height="0" fill="url(#barGradient)" data-index="1" data-label="Write"/>
|
||||
<rect class="bar" x="50" y="0" width="18" height="0" fill="url(#barGradient)" data-index="2" data-label="Update"/>
|
||||
<rect class="bar" x="75" y="0" width="18" height="0" fill="url(#barGradient)" data-index="3" data-label="Delete"/>
|
||||
</g>
|
||||
<g transform="translate(150, 145)">
|
||||
<text x="0" y="0" style="font-size:12px;fill:#666666">Read</text>
|
||||
<text x="25" y="0" style="font-size:12px;fill:#666666">Write</text>
|
||||
<text x="50" y="0" style="font-size:12px;fill:#666666">Update</text>
|
||||
<text x="75" y="0" style="font-size:12px;fill:#666666">Delete</text>
|
||||
</g>
|
||||
<g id="values-group-2" transform="translate(150, 160)">
|
||||
<text x="9" y="0" style="font-size:13px;font-weight:500;fill:#333333" text-anchor="middle" data-value="0">0</text>
|
||||
<text x="34" y="0" style="font-size:13px;font-weight:500;fill:#333333" text-anchor="middle" data-value="0">0</text>
|
||||
<text x="59" y="0" style="font-size:13px;font-weight:500;fill:#333333" text-anchor="middle" data-value="0">0</text>
|
||||
<text x="84" y="0" style="font-size:13px;font-weight:500;fill:#333333" text-anchor="middle" data-value="0">0</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="line-graph" transform="translate(320, 10)">
|
||||
<text x="0" y="0" style="font-size:14px;font-weight:600;fill:#333333">Traffic Trend</text>
|
||||
<g transform="translate(0, 25)">
|
||||
<line x1="0" y1="0" x2="200" y2="0" stroke="#E0E0E0" stroke-width="0.5"/>
|
||||
<line x1="0" y1="40" x2="200" y2="40" stroke="#E0E0E0" stroke-width="0.5"/>
|
||||
<line x1="0" y1="80" x2="200" y2="80" stroke="#E0E0E0" stroke-width="0.5"/>
|
||||
<line x1="0" y1="120" x2="200" y2="120" stroke="#E0E0E0" stroke-width="0.5"/>
|
||||
</g>
|
||||
<path id="line-path" d="M0,80 Q50,60 100,70 T200,50" stroke="#4CAF50" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-dasharray="200" stroke-dashoffset="200"/>
|
||||
<path id="line-fill" d="M0,80 Q50,60 100,70 T200,50 L200,160 Q100,140 0,160 Z" fill="url(#lineGradient)"/>
|
||||
</g>
|
||||
<g id="pie-chart" transform="translate(580, 10)">
|
||||
<text x="60" y="0" style="font-size:14px;font-weight:600;fill:#333333" text-anchor="middle">Distribution</text>
|
||||
<g transform="translate(60, 80)">
|
||||
<g class="pie-segment" id="pie-seg-1" transform="rotate(0)">
|
||||
<path d="M 0,0 L 0,-45 A 45,45 0 0,1 31.82,-31.82 Z" fill="#004225" opacity="0.9"/>
|
||||
</g>
|
||||
<g class="pie-segment" id="pie-seg-2" transform="rotate(90)">
|
||||
<path d="M 0,0 L 0,-45 A 45,45 0 0,1 31.82,-31.82 Z" fill="#4CAF50" opacity="0.8"/>
|
||||
</g>
|
||||
<g class="pie-segment" id="pie-seg-3" transform="rotate(180)">
|
||||
<path d="M 0,0 L 0,-45 A 45,45 0 0,1 31.82,-31.82 Z" fill="#f59e0b" opacity="0.7"/>
|
||||
</g>
|
||||
<g class="pie-segment" id="pie-seg-4" transform="rotate(270)">
|
||||
<path d="M 0,0 L 0,-45 A 45,45 0 0,1 31.82,-31.82 Z" fill="#ef4444" opacity="0.7"/>
|
||||
</g>
|
||||
<circle cx="0" cy="0" r="18" fill="#ffffff" stroke="#E0E0E0" stroke-width="1"/>
|
||||
<text x="0" y="5" text-anchor="middle" style="font-size:13px;font-weight:500;fill:#333333">100%</text>
|
||||
</g>
|
||||
<g transform="translate(0, 210)">
|
||||
<rect x="0" y="0" width="8" height="8" fill="#004225"/>
|
||||
<text x="12" y="7" style="font-size:12px;fill:#666666">Service A</text>
|
||||
<rect x="0" y="15" width="8" height="8" fill="#4CAF50"/>
|
||||
<text x="12" y="22" style="font-size:12px;fill:#666666">Service B</text>
|
||||
<rect x="100" y="0" width="8" height="8" fill="#f59e0b"/>
|
||||
<text x="112" y="7" style="font-size:12px;fill:#666666">Service C</text>
|
||||
<rect x="100" y="15" width="8" height="8" fill="#ef4444"/>
|
||||
<text x="112" y="22" style="font-size:12px;fill:#666666">Service D</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg></div>';
|
||||
// Render animated dashboard chart SVG
|
||||
// Text uses class hooks: .ct = title, .cl = label, .cv = value
|
||||
// JS will dynamically set fill colours based on data-theme
|
||||
$visual_html = '<div class="dashboard-chart-container" data-dashboard-container="true">
|
||||
<svg viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" class="dashboard-chart" role="img" aria-label="Animated dashboard charts" style="border-radius:4px">
|
||||
<defs>
|
||||
<linearGradient id="barGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#004225" stop-opacity="1"/>
|
||||
<stop offset="100%" stop-color="#4CAF50" stop-opacity=".8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#4CAF50" stop-opacity=".3"/>
|
||||
<stop offset="100%" stop-color="#4CAF50" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- ─ Bar chart group 1: Performance ─ -->
|
||||
<g transform="translate(20,20)">
|
||||
<text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#333">Performance</text>
|
||||
<g id="bars-group-1" transform="translate(0,25)">
|
||||
<rect class="bar" x="0" y="120" width="18" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="25" y="120" width="18" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="50" y="120" width="18" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="75" y="120" width="18" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="100" y="120" width="18" height="0" fill="url(#barGradient)"/>
|
||||
</g>
|
||||
<g transform="translate(0,152)">
|
||||
<text class="cl" x="9" y="0" font-size="10" text-anchor="middle" fill="#666">API</text>
|
||||
<text class="cl" x="34" y="0" font-size="10" text-anchor="middle" fill="#666">Cache</text>
|
||||
<text class="cl" x="59" y="0" font-size="10" text-anchor="middle" fill="#666">DB</text>
|
||||
<text class="cl" x="84" y="0" font-size="10" text-anchor="middle" fill="#666">Queue</text>
|
||||
<text class="cl" x="109" y="0" font-size="10" text-anchor="middle" fill="#666">Worker</text>
|
||||
</g>
|
||||
<g id="values-group-1" transform="translate(0,166)">
|
||||
<text class="cv" x="9" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
|
||||
<text class="cv" x="34" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
|
||||
<text class="cv" x="59" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
|
||||
<text class="cv" x="84" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
|
||||
<text class="cv" x="109" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0%</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ─ Bar chart group 2: Requests/sec ─ -->
|
||||
<g transform="translate(170,20)">
|
||||
<text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#333">Requests/sec</text>
|
||||
<g id="bars-group-2" transform="translate(0,25)">
|
||||
<rect class="bar" x="0" y="120" width="18" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="25" y="120" width="18" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="50" y="120" width="18" height="0" fill="url(#barGradient)"/>
|
||||
<rect class="bar" x="75" y="120" width="18" height="0" fill="url(#barGradient)"/>
|
||||
</g>
|
||||
<g transform="translate(0,152)">
|
||||
<text class="cl" x="9" y="0" font-size="10" text-anchor="middle" fill="#666">Read</text>
|
||||
<text class="cl" x="34" y="0" font-size="10" text-anchor="middle" fill="#666">Write</text>
|
||||
<text class="cl" x="59" y="0" font-size="10" text-anchor="middle" fill="#666">Update</text>
|
||||
<text class="cl" x="84" y="0" font-size="10" text-anchor="middle" fill="#666">Delete</text>
|
||||
</g>
|
||||
<g id="values-group-2" transform="translate(0,166)">
|
||||
<text class="cv" x="9" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
|
||||
<text class="cv" x="34" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
|
||||
<text class="cv" x="59" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
|
||||
<text class="cv" x="84" y="0" font-size="11" font-weight="600" text-anchor="middle" fill="#333">0</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ─ Line graph: Traffic Trend ─ -->
|
||||
<g id="line-graph" transform="translate(330,20)">
|
||||
<text class="ct" x="0" y="0" font-size="14" font-weight="600" fill="#333">Traffic Trend</text>
|
||||
<g transform="translate(0,25)">
|
||||
<line class="grid-line" x1="0" y1="0" x2="200" y2="0" stroke="#E0E0E0" stroke-width=".5"/>
|
||||
<line class="grid-line" x1="0" y1="40" x2="200" y2="40" stroke="#E0E0E0" stroke-width=".5"/>
|
||||
<line class="grid-line" x1="0" y1="80" x2="200" y2="80" stroke="#E0E0E0" stroke-width=".5"/>
|
||||
<line class="grid-line" x1="0" y1="120" x2="200" y2="120" stroke="#E0E0E0" stroke-width=".5"/>
|
||||
</g>
|
||||
<g transform="translate(0,25)">
|
||||
<path id="line-fill" d="M0,80 L200,80 L200,145 L0,145 Z" fill="url(#lineGradient)"/>
|
||||
<path id="line-path" d="M0,80 L200,80" stroke="#4CAF50" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ─ Pie chart: Distribution ─ -->
|
||||
<g transform="translate(590,20)">
|
||||
<text class="ct" x="60" y="0" font-size="14" font-weight="600" text-anchor="middle" fill="#333">Distribution</text>
|
||||
<g transform="translate(60,85)">
|
||||
<g class="pie-segment" transform="rotate(0)">
|
||||
<path d="M0,0 L0,-45 A45,45 0 0,1 31.82,-31.82 Z" fill="#004225" opacity=".9"/>
|
||||
</g>
|
||||
<g class="pie-segment" transform="rotate(90)">
|
||||
<path d="M0,0 L0,-45 A45,45 0 0,1 31.82,-31.82 Z" fill="#4CAF50" opacity=".8"/>
|
||||
</g>
|
||||
<g class="pie-segment" transform="rotate(180)">
|
||||
<path d="M0,0 L0,-45 A45,45 0 0,1 31.82,-31.82 Z" fill="#f59e0b" opacity=".7"/>
|
||||
</g>
|
||||
<g class="pie-segment" transform="rotate(270)">
|
||||
<path d="M0,0 L0,-45 A45,45 0 0,1 31.82,-31.82 Z" fill="#ef4444" opacity=".7"/>
|
||||
</g>
|
||||
<circle id="pie-center" cx="0" cy="0" r="18" fill="#fff" stroke="#E0E0E0" stroke-width="1"/>
|
||||
<text id="pie-center-text" x="0" y="5" text-anchor="middle" font-size="12" font-weight="600" fill="#333">100%</text>
|
||||
</g>
|
||||
<g transform="translate(0,180)">
|
||||
<rect x="0" y="0" width="8" height="8" fill="#004225"/>
|
||||
<text class="cl" x="12" y="8" font-size="11" fill="#666">Service A</text>
|
||||
<rect x="0" y="15" width="8" height="8" fill="#4CAF50"/>
|
||||
<text class="cl" x="12" y="23" font-size="11" fill="#666">Service B</text>
|
||||
<rect x="90" y="0" width="8" height="8" fill="#f59e0b"/>
|
||||
<text class="cl" x="102" y="8" font-size="11" fill="#666">Service C</text>
|
||||
<rect x="90" y="15" width="8" height="8" fill="#ef4444"/>
|
||||
<text class="cl" x="102" y="23" font-size="11" fill="#666">Service D</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>';
|
||||
$visual_cls = 'platform-visual has-dashboard';
|
||||
} elseif ( $img_url ) {
|
||||
$img_style = 'width:' . $img_w . 'px;max-width:100%;height:auto;border-radius:var(--radius-sm);object-fit:contain;display:block;margin-inline:auto;';
|
||||
|
||||
@@ -34,7 +34,7 @@ add_action( 'wp_enqueue_scripts', function () {
|
||||
'oribi-dashboard-animator',
|
||||
ORIBI_URI . '/assets/js/dashboard-animator.js',
|
||||
[],
|
||||
ORIBI_VERSION,
|
||||
ORIBI_VERSION . '.' . filemtime( ORIBI_DIR . '/assets/js/dashboard-animator.js' ),
|
||||
true
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user