jzou19950715's picture
Update components/visualization.py
14b70ba verified
raw
history blame
9.22 kB
# components/visualization.py
class D3Visualizer:
"""D3.js visualization component"""
@staticmethod
def create_interactive_plot(plot_type: str, data: dict) -> str:
"""Create interactive D3 visualization"""
# Base CSS for visualizations
base_css = """
<style>
.visualization-container { width: 100%; height: 500px; }
.bar { fill: steelblue; }
.bar:hover { fill: brown; }
.line { fill: none; stroke: steelblue; stroke-width: 2; }
.area { fill: steelblue; opacity: 0.2; }
.tooltip { position: absolute; padding: 8px; background: white; border: 1px solid #ddd; border-radius: 4px; }
.axis-label { font-size: 12px; }
</style>
"""
if plot_type == "distribution":
return base_css + f"""
<div id="distribution-plot" class="visualization-container"></div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
(function() {{
const values = {data['values']};
const margin = {{top: 40, right: 40, bottom: 60, left: 60}};
const width = 800 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
// Create SVG
const svg = d3.select("#distribution-plot")
.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}})`);
// Create scales
const x = d3.scaleLinear()
.domain([d3.min(values), d3.max(values)])
.range([0, width]);
const histogram = d3.histogram()
.domain(x.domain())
.thresholds(x.ticks(20));
const bins = histogram(values);
const y = d3.scaleLinear()
.domain([0, d3.max(bins, d => d.length)])
.range([height, 0]);
// Create bars
svg.selectAll(".bar")
.data(bins)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", d => x(d.x0))
.attr("y", d => y(d.length))
.attr("width", d => Math.max(0, x(d.x1) - x(d.x0) - 1))
.attr("height", d => height - y(d.length))
.on("mouseover", function(event, d) {{
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(
`Range: ${{d.x0.toFixed(2)}} - ${{d.x1.toFixed(2)}}<br/>` +
`Count: ${{d.length}}`
)
.style("left", (event.pageX + 5) + "px")
.style("top", (event.pageY - 28) + "px");
}})
.on("mouseout", function(d) {{
tooltip.transition()
.duration(500)
.style("opacity", 0);
}});
// Add axes
svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${{height}})`)
.call(d3.axisBottom(x))
.append("text")
.attr("class", "axis-label")
.attr("x", width/2)
.attr("y", 40)
.text("Value");
svg.append("g")
.attr("class", "y-axis")
.call(d3.axisLeft(y))
.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("x", -height/2)
.style("text-anchor", "middle")
.text("Frequency");
// Add tooltip
const tooltip = d3.select("#distribution-plot")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
}})();
</script>
"""
elif plot_type == "forecast":
return base_css + f"""
<div id="forecast-plot" class="visualization-container"></div>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
(function() {{
const data = {data};
const margin = {{top: 40, right: 40, bottom: 60, left: 60}};
const width = 800 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
// Create SVG
const svg = d3.select("#forecast-plot")
.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}})`);
// Create scales
const x = d3.scaleLinear()
.domain([0, data.time.length-1])
.range([0, width]);
const y = d3.scaleLinear()
.domain([
d3.min(data.lower),
d3.max(data.upper)
])
.range([height, 0]);
// Create area
const area = d3.area()
.x((d, i) => x(i))
.y0(d => y(d[0]))
.y1(d => y(d[1]));
// Add confidence interval area
svg.append("path")
.datum(data.time.map((t, i) => [data.lower[i], data.upper[i]]))
.attr("class", "area")
.attr("d", area);
// Add mean line
const line = d3.line()
.x((d, i) => x(i))
.y(d => y(d));
svg.append("path")
.datum(data.mean)
.attr("class", "line")
.attr("d", line);
// Add axes
svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${{height}})`)
.call(d3.axisBottom(x))
.append("text")
.attr("class", "axis-label")
.attr("x", width/2)
.attr("y", 40)
.text("Time Period");
svg.append("g")
.attr("class", "y-axis")
.call(d3.axisLeft(y))
.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("x", -height/2)
.style("text-anchor", "middle")
.text("Value");
// Add tooltip for hover
const tooltip = d3.select("#forecast-plot")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Add hover interaction
const bisect = d3.bisector(d => d).left;
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.on("mousemove", function(event) {{
const x0 = x.invert(d3.pointer(event)[0]);
const i = Math.round(x0);
if (i >= 0 && i < data.time.length) {{
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(
`Time: ${{data.time[i]}}<br/>` +
`Mean: ${{data.mean[i].toFixed(2)}}<br/>` +
`Range: [${{data.lower[i].toFixed(2)}}, ${{data.upper[i].toFixed(2)}}]`
)
.style("left", (event.pageX + 5) + "px")
.style("top", (event.pageY - 28) + "px");
}}
}})
.on("mouseout", function() {{
tooltip.transition()
.duration(500)
.style("opacity", 0);
}});
}})();
</script>
"""
return "Unsupported visualization type"