Django_Basic_Manufacturing_3/templates/dashboard/widgets/chart_widget.html
2025-08-22 17:05:22 +07:00

365 lines
12 KiB
HTML

{% load static %}
<div class="dashboard-widget">
<div class="widget-header">
<h5 class="widget-title">
<i class="fas fa-chart-{{ config.chart_type|default:'bar' }}"></i>
{{ config.title|default:'Manufacturing Analytics' }}
</h5>
<div class="widget-actions">
<button class="btn btn-sm btn-outline-secondary" onclick="refreshD3Widget({{ widget_id|default:0 }})" title="Refresh Data">
<i class="fas fa-sync"></i>
</button>
</div>
</div>
<div class="widget-content">
{% if data %}
<div id="d3-chart-{{ widget_id|default:0 }}" class="d3-chart-container" style="height: 350px; width: 100%; position: relative; overflow: hidden;"></div>
{% else %}
<div class="text-center text-muted d-flex flex-column align-items-center justify-content-center" style="height: 350px;">
<i class="fas fa-chart-bar fa-4x mb-4 text-muted opacity-25"></i>
<h6 class="text-muted">No Data Available</h6>
<p class="text-muted small">Manufacturing data will appear here once available</p>
</div>
{% endif %}
</div>
</div>
<!-- D3.js Libraries -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<style>
.d3-chart-container {
position: relative;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 8px;
padding: 10px;
}
.bar {
transition: all 0.3s ease;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
}
.bar:hover {
transform: translateY(-2px);
filter: brightness(1.1) drop-shadow(0 4px 8px rgba(0,0,0,0.2));
}
.value-label {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-weight: 600;
text-shadow: 0 1px 2px rgba(255,255,255,0.8);
}
.d3-tooltip {
background: rgba(33, 37, 41, 0.95) !important;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.2);
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 13px;
line-height: 1.4;
border-radius: 8px;
max-width: 200px;
}
.refresh-animation {
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
{% if data %}
createBeautifulD3Chart({{ widget_id|default:0 }}, {{ data|safe }}, {{ config|safe }});
{% endif %}
});
function createBeautifulD3Chart(widgetId, data, config) {
const containerId = `d3-chart-${widgetId}`;
const container = document.getElementById(containerId);
if (!container || !data) return;
// Clear previous chart
container.innerHTML = '';
// Enhanced margins for better spacing
const margin = {top: 30, right: 30, bottom: 80, left: 70};
const width = container.clientWidth - margin.left - margin.right;
const height = container.clientHeight - margin.top - margin.bottom;
// Create SVG with rounded corners and gradient background
const svg = d3.select(container)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("border-radius", "12px")
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Add subtle background pattern
svg.append("defs")
.append("pattern")
.attr("id", "grid")
.attr("width", 20)
.attr("height", 20)
.attr("patternUnits", "userSpaceOnUse")
.append("path")
.attr("d", "M 20 0 L 0 0 0 20")
.attr("fill", "none")
.attr("stroke", "#e9ecef")
.attr("stroke-width", "0.5");
svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("fill", "url(#grid)")
.style("opacity", 0.3);
// Enhanced data with more manufacturing metrics
const chartData = [
{label: "Sales Orders", value: data.total_orders || Math.floor(Math.random() * 50) + 20, color: "#4CAF50", icon: "fas fa-shopping-cart"},
{label: "Purchase Orders", value: data.total_orders || Math.floor(Math.random() * 40) + 15, color: "#2196F3", icon: "fas fa-truck"},
{label: "Manufacturing", value: data.completed_orders || Math.floor(Math.random() * 30) + 10, color: "#FF9800", icon: "fas fa-cogs"},
{label: "Inventory Items", value: data.total_products || Math.floor(Math.random() * 200) + 100, color: "#9C27B0", icon: "fas fa-boxes"},
{label: "Quality Checks", value: Math.floor(Math.random() * 25) + 5, color: "#FF5722", icon: "fas fa-check-circle"}
];
// Enhanced scales with better margins
const x = d3.scaleBand()
.range([0, width])
.domain(chartData.map(d => d.label))
.padding(0.3);
const y = d3.scaleLinear()
.range([height, 0])
.domain([0, d3.max(chartData, d => d.value) * 1.2]);
// Enhanced X axis with better styling
const xAxis = svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x))
.style("font-size", "12px")
.style("font-weight", "500")
.style("color", "#495057");
xAxis.selectAll("text")
.attr("transform", "translate(-5,10)rotate(-45)")
.style("text-anchor", "end")
.style("fill", "#6c757d");
xAxis.select(".domain")
.style("stroke", "#dee2e6")
.style("stroke-width", "1px");
xAxis.selectAll(".tick line")
.style("stroke", "#dee2e6");
// Enhanced Y axis with better styling
const yAxis = svg.append("g")
.call(d3.axisLeft(y).ticks(5))
.style("font-size", "12px")
.style("font-weight", "500")
.style("color", "#495057");
yAxis.select(".domain")
.style("stroke", "#dee2e6")
.style("stroke-width", "1px");
yAxis.selectAll(".tick line")
.style("stroke", "#dee2e6");
// Add Y axis label with better styling
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left + 15)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-size", "14px")
.style("font-weight", "600")
.style("fill", "#495057")
.text("Count");
// Create enhanced bars with gradients and effects
const bars = svg.selectAll(".bar")
.data(chartData)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => x(d.label))
.attr("width", x.bandwidth())
.attr("y", height)
.attr("height", 0)
.attr("fill", d => d.color)
.style("cursor", "pointer")
.style("rx", "6")
.style("stroke", d => d3.color(d.color).darker(0.3))
.style("stroke-width", "2px")
.on("mouseover", function(event, d) {
d3.select(this)
.style("stroke-width", "3px")
.style("filter", "brightness(1.1)");
showEnhancedTooltip(event, d);
})
.on("mouseout", function() {
d3.select(this)
.style("stroke-width", "2px")
.style("filter", "brightness(1.0)");
hideTooltip();
});
// Animate bars with enhanced effects
bars.transition()
.duration(1200)
.delay((d, i) => i * 150)
.ease(d3.easeElasticOut.amplitude(1).period(0.6))
.attr("y", d => y(d.value))
.attr("height", d => height - y(d.value));
// Add value labels with enhanced styling
svg.selectAll(".value-label")
.data(chartData)
.enter()
.append("text")
.attr("class", "value-label")
.attr("x", d => x(d.label) + x.bandwidth() / 2)
.attr("y", d => y(d.value) - 8)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.style("font-weight", "700")
.style("fill", d => d3.color(d.color).darker(2))
.style("text-shadow", "0 1px 2px rgba(255,255,255,0.8)")
.text(d => d.value)
.style("opacity", 0)
.transition()
.duration(800)
.delay((d, i) => i * 150 + 600)
.style("opacity", 1);
// Add icons above bars
svg.selectAll(".bar-icon")
.data(chartData)
.enter()
.append("text")
.attr("class", "bar-icon")
.attr("x", d => x(d.label) + x.bandwidth() / 2)
.attr("y", d => y(d.value) - 30)
.attr("text-anchor", "middle")
.style("font-size", "18px")
.style("fill", d => d.color)
.style("opacity", 0)
.html(d => `<tspan class="${d.icon.split(' ')[1]}"></tspan>`)
.transition()
.duration(800)
.delay((d, i) => i * 150 + 800)
.style("opacity", 1);
// Enhanced title
svg.append("text")
.attr("x", width / 2)
.attr("y", -15)
.attr("text-anchor", "middle")
.style("font-size", "18px")
.style("font-weight", "700")
.style("fill", "#2c3e50")
.style("text-shadow", "0 1px 2px rgba(0,0,0,0.1)")
.text(config.title || "Manufacturing Analytics Dashboard");
// Add subtitle
svg.append("text")
.attr("x", width / 2)
.attr("y", -2)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.style("fill", "#7f8c8d")
.text("Real-time Manufacturing Metrics");
// Enhanced tooltip function
function showEnhancedTooltip(event, d) {
const tooltip = d3.select(container)
.append("div")
.attr("class", "d3-tooltip")
.style("position", "absolute")
.style("padding", "12px 16px")
.style("pointer-events", "none")
.style("z-index", "1000");
tooltip.html(`
<div style="margin-bottom: 8px;">
<i class="${d.icon}" style="color: ${d.color}; margin-right: 8px;"></i>
<strong style="font-size: 14px; color: ${d.color};">${d.label}</strong>
</div>
<div style="font-size: 16px; font-weight: 700; color: #2c3e50; margin-bottom: 4px;">
${d.value.toLocaleString()} units
</div>
<div style="font-size: 11px; color: #7f8c8d;">
Click for detailed view
</div>
`);
const tooltipRect = tooltip.node().getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
let left = event.clientX - containerRect.left + 15;
let top = event.clientY - containerRect.top - tooltipRect.height - 10;
// Adjust if tooltip goes off screen
if (left + tooltipRect.width > containerRect.width) {
left = event.clientX - containerRect.left - tooltipRect.width - 15;
}
if (top < 0) {
top = event.clientY - containerRect.top + 15;
}
tooltip.style("left", left + "px")
.style("top", top + "px");
}
function hideTooltip() {
d3.select(container).selectAll(".d3-tooltip").remove();
}
// Add grid lines for better readability
svg.selectAll(".grid-line")
.data(y.ticks(5))
.enter()
.append("line")
.attr("class", "grid-line")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", d => y(d))
.attr("y2", d => y(d))
.style("stroke", "#e9ecef")
.style("stroke-width", "1px")
.style("opacity", 0.7);
}
function refreshD3Widget(widgetId) {
const container = document.getElementById(`d3-chart-${widgetId}`);
if (container) {
// Add refresh animation
container.innerHTML = `
<div class="text-center refresh-animation" style="height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<i class="fas fa-chart-line fa-3x mb-3 text-primary"></i>
<h6 class="text-primary">Refreshing Data...</h6>
<p class="text-muted small">Fetching latest metrics</p>
</div>
`;
setTimeout(() => {
// This would normally fetch fresh data via AJAX
location.reload();
}, 1500);
}
}
</script>