177 lines
5.6 KiB
Plaintext

@{
ViewData["Title"] = "Ventas Mensuales";
Layout = "_Layout";
}
<div class="container my-4">
<h2 class="text-center mb-4">@ViewData["Title"]</h2>
<div class="row mb-4">
<div class="col-md-6">
<label for="productSelect" class="form-label">Productos</label>
<select id="productSelect" class="form-select" multiple size="5"></select>
<small class="form-text text-muted">
Mantén Ctrl para seleccionar varios productos.
</small>
</div>
</div>
<div id="legend"></div>
<div id="salesChart" style="position: relative;"></div>
</div>
@section Scripts {
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
d3.json('@Url.Action("GetMonthlyData","Sales")').then(rawData => {
//Agrupamos en un Map<string, Array<{month,units}>>
const salesData = d3.group(rawData, d => d.productName);
const margin = { top: 30, right: 20, bottom: 60, left: 60 },
width = 800 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
const svg = d3.select("#salesChart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const x0 = d3.scaleBand().range([0, width]).padding(0.2),
x1 = d3.scaleBand().padding(0.05),
y = d3.scaleLinear().range([height, 0]);
const xAxisG = svg.append("g")
.attr("transform", `translate(0,${height})`);
const yAxisG = svg.append("g");
const allProducts = Array.from(salesData.keys());
const color = d3.scaleOrdinal()
.domain(allProducts)
.range(d3.schemeCategory10);
const productSelect = d3.select("#productSelect");
productSelect
.selectAll("option")
.data(allProducts)
.join("option")
.attr("value", d => d)
.text(d => d);
const tooltip = d3.select("body")
.append("div")
.attr("id", "tooltip");
function updateChart(selected) {
d3.select("#legend").selectAll("*").remove();
if (!selected.length) {
svg.selectAll(".barGroup").remove();
xAxisG.call(d3.axisBottom(x0));
yAxisG.call(d3.axisLeft(y));
return;
}
selected.forEach(key => {
const item = d3.select("#legend")
.append("div")
.style("display","flex")
.style("align-items","center");
item.append("div")
.style("background", color(key));
item.append("span").text(key);
});
const months = Array.from(new Set(
selected.flatMap(p => salesData.get(p).map(d => d.month))
)).sort();
const data = months.map(month => {
const obj = { month };
selected.forEach(p => {
const rec = salesData.get(p).find(d => d.month === month);
obj[p] = rec ? rec.units : 0;
});
return obj;
});
x0.domain(months);
x1.domain(selected).range([0, x0.bandwidth()]);
y.domain([0, d3.max(data, d => d3.max(selected, p => d[p])) * 1.1]);
xAxisG.call(d3.axisBottom(x0))
.selectAll("text")
.attr("transform","rotate(-40)")
.style("text-anchor","end");
yAxisG.call(d3.axisLeft(y));
const groups = svg.selectAll(".barGroup")
.data(data, d => d.month);
const groupsEnter = groups.join(
enter => enter.append("g")
.attr("class","barGroup")
.attr("transform", d => `translate(${x0(d.month)},0)`),
update => update,
exit => exit.remove()
);
groupsEnter.selectAll("rect")
.data(d => selected.map(p => ({
key: p,
value: d[p],
month: d.month
})))
.join(
enter => enter.append("rect")
.attr("x", d => x1(d.key))
.attr("y", height)
.attr("width", x1.bandwidth())
.attr("height", 0)
.attr("fill", d => color(d.key))
.on("mouseover",(e,d)=>{
tooltip.html(`<strong>${d.key}</strong><br>${d.month}: ${d.value}`)
.style("opacity",1);
})
.on("mousemove",e=>{
tooltip.style("left",(e.pageX+10)+"px")
.style("top",(e.pageY-28)+"px");
})
.on("mouseout",()=>{
tooltip.style("opacity",0);
})
.call(g => g.transition().duration(600)
.attr("y", d => y(d.value))
.attr("height", d => height - y(d.value))
),
update => update.call(u => u.transition().duration(600)
.attr("x", d => x1(d.key))
.attr("y", d => y(d.value))
.attr("width", x1.bandwidth())
.attr("height", d => height - y(d.value))
.attr("fill", d => color(d.key))
),
exit => exit.call(e => e.transition().duration(300)
.attr("y", height)
.attr("height", 0)
.remove()
)
);
}
productSelect.on("change", function(){
const sel = Array.from(this.selectedOptions).map(o=>o.value);
updateChart(sel);
});
productSelect.selectAll("option").property("selected", true);
updateChart(allProducts);
})
.catch(err => console.error("Error cargando datos de ventas:", err));
</script>
}