-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdashboard-template.html
More file actions
175 lines (153 loc) · 6.03 KB
/
Copy pathdashboard-template.html
File metadata and controls
175 lines (153 loc) · 6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Live Bot Performance Visualizer</title>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: #f8fafc;
margin: 0;
padding: 24px;
}
.container {
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
padding: 24px;
display: block;
max-width: 100%;
margin: 0 auto;
box-sizing: border-box;
}
h3 {
margin-top: 0;
color: #1e293b;
}
/* 1. FLEXBOX CONTAINER: Locks Y-Axis left, lets Chart scroll right */
#chart {
display: flex;
width: 100%;
border: 1px solid #e2e8f0;
border-radius: 4px;
background-color: #ffffff;
}
/* Fixed Y-Axis container layer */
#y-axis-container {
flex: 0 0 65px; /* Fixed width, will never shrink or grow */
background-color: #ffffff;
z-index: 10;
}
/* Scrollable timeline window pane layer */
#timeline-container {
flex: 1;
overflow-x: auto;
overflow-y: hidden;
}
.bar {
fill: #4f46e5;
transition: fill 0.1s ease;
}
.bar:hover {
fill: #ef4444;
}
.tooltip {
position: absolute;
padding: 6px 10px;
background: rgba(15, 23, 42, 0.9);
color: #ffffff;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
opacity: 0;
box-shadow: 0 2px 4px rgb(0 0 0 / 0.1);
font-variant-numeric: tabular-nums;
z-index: 50;
}
</style>
</head>
<body>
<div class="container">
<h3>Search Time Results</h3>
<div id="chart">
<div id="y-axis-container"></div>
<div id="timeline-container"></div>
</div>
</div>
<div id="tooltip" class="tooltip"></div>
<script>
// Shared structural metrics
const margin = { top: 20, right: 30, bottom: 40, left: 0 };
const yAxisWidth = 65;
const height = 400 - margin.top - margin.bottom;
const pixelsPerBar = 25;
const barPadding = 0.2;
// --- Layer A: Fixed Y-Axis SVG Configuration ---
const ySvg = d3.select("#y-axis-container")
.append("svg")
.attr("width", yAxisWidth)
.attr("height", height + margin.top + margin.bottom);
const yGroup = ySvg.append("g")
.attr("transform", `translate(${yAxisWidth - 5}, ${margin.top})`);
const yScale = d3.scaleLinear().range([height, 0]);
const yAxis = d3.axisLeft(yScale).tickFormat(d => d + 's');
// --- Layer B: Scrollable Timeline SVG Configuration ---
const timelineSvg = d3.select("#timeline-container")
.append("svg")
.attr("height", height + margin.top + margin.bottom);
const g = timelineSvg.append("g")
.attr("transform", `translate(10, ${margin.top})`); // Small 10px breathing room buffer on the left edge
const xScale = d3.scaleBand().padding(barPadding);
const xAxisGroup = g.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0, ${height})`);
const tooltip = d3.select("#tooltip");
// --- Core Real-Time Render Pipeline ---
window.updateRealTimeChart = function(results) {
if (!results || results.length === 0) return;
// 1. Dynamic scale updates based on stream contents
const maxTime = d3.max(results, d => d.timeElapsed) ?? 10;
yScale.domain([0, maxTime * 1.1]);
// Redraw the static Y-Axis layer instantly
yGroup.call(yAxis);
// 2. Expand scrollable canvas width layout dynamically
const dynamicWidth = Math.max(500, results.length * pixelsPerBar);
timelineSvg.attr("width", dynamicWidth + margin.right);
// 3. Adjust X-Domain mapping
xScale.domain(results.map(d => d.runId)).range([0, dynamicWidth]);
// Limit text overlapping down on the x-axis tick collections
const tickInterval = Math.ceil(results.length / 25);
const xTicks = results.map(d => d.runId).filter((d, i) => i % tickInterval === 0);
xAxisGroup.call(d3.axisBottom(xScale).tickValues(xTicks));
// 4. Data-bind rectangles
const bars = g.selectAll(".bar").data(results, d => d.runId);
bars.exit().remove();
bars.enter()
.append("rect")
.attr("class", "bar")
.merge(bars)
.attr("x", d => xScale(d.runId))
.attr("y", d => yScale(d.timeElapsed))
.attr("width", xScale.bandwidth())
.attr("height", d => height - yScale(d.timeElapsed))
.on("mouseover", function(event, d) {
tooltip.style("opacity", 1)
.html(`<strong>Run:</strong> #${d.runId}<br><strong>Time:</strong> ${d.timeElapsed}s`);
})
.on("mousemove", function(event) {
tooltip.style("left", (event.pageX + 12) + "px")
.style("top", (event.pageY - 35) + "px");
})
.on("mouseout", function() {
tooltip.style("opacity", 0);
});
// 5. Scroll the timeline window frame right while keeping the Y-Axis locked left
const scrollDiv = document.getElementById('timeline-container');
if(scrollDiv) {
scrollDiv.scrollLeft = scrollDiv.scrollWidth;
}
};
</script>
</body>
</html>