|
1 | 1 | <script> |
2 | 2 | import { getContext } from "svelte"; |
3 | | - import { line } from "d3-shape"; |
| 3 | + import { area, line } from "d3-shape"; |
4 | 4 | import { |
5 | 5 | agentSelected, |
6 | 6 | thresholdAgentNum, |
|
107 | 107 | .x((d) => $xScale(d[0])) |
108 | 108 | .y((d) => $yScale(d[1]))(equalAdvantagePoints); |
109 | 109 |
|
110 | | - // Calculate polygon for shaded "sweet spot" (right of agent threshold & below equal advantage) |
111 | | - $: highPerformancePolygonPoints = (() => { |
112 | | - if (!$xScale || !$yScale) return ""; |
| 110 | + const clamp = (value, min, max) => Math.min(Math.max(value, min), max); |
| 111 | +
|
| 112 | + // Calculate shaded "sweet spot" with the same area logic as the agent-card scatterplot |
| 113 | + $: highPerformanceRegionPath = (() => { |
| 114 | + if (!xDomain || !yDomain) return null; |
113 | 115 |
|
114 | | - const xDomain = |
115 | | - typeof $xScale?.domain === "function" ? $xScale.domain() : [0, 3]; |
116 | | - const yDomain = |
117 | | - typeof $yScale?.domain === "function" ? $yScale.domain() : [0, 3]; |
118 | 116 | const [xMin, xMax] = xDomain; |
119 | 117 | const [yMin, yMax] = yDomain; |
| 118 | + if (!Number.isFinite(xMin) || !Number.isFinite(xMax) || xMax <= xMin) |
| 119 | + return null; |
| 120 | + if (!Number.isFinite(yMin) || !Number.isFinite(yMax) || yMax <= yMin) |
| 121 | + return null; |
| 122 | +
|
| 123 | + const agentThreshold = Number($thresholdAgentNum); |
| 124 | + const oracleThreshold = Number($thresholdOracleNum); |
| 125 | + if (!Number.isFinite(agentThreshold) || !Number.isFinite(oracleThreshold)) |
| 126 | + return null; |
| 127 | +
|
| 128 | + const xStart = clamp(agentThreshold, xMin, xMax); |
| 129 | + if (xStart >= xMax) return null; |
| 130 | +
|
| 131 | + // Vertical equal-advantage line has no well-defined "under" region. |
| 132 | + if (Math.abs(agentThreshold) < 1e-6) return null; |
120 | 133 |
|
121 | | - const clamp = (val, min, max) => Math.max(min, Math.min(max, val)); |
122 | | - const rawThresholdX = Number.isFinite($thresholdAgentNum) |
123 | | - ? $thresholdAgentNum |
124 | | - : xMin; |
125 | | - const thresholdX = clamp(rawThresholdX, xMin, xMax); |
126 | | - if (thresholdX >= xMax) return ""; |
127 | | -
|
128 | | - const slope = |
129 | | - Number.isFinite($thresholdAgentNum) && Math.abs($thresholdAgentNum) > 0 |
130 | | - ? $thresholdOracleNum / $thresholdAgentNum |
131 | | - : null; |
132 | | -
|
133 | | - const yAtThreshold = |
134 | | - slope === null |
135 | | - ? Number.isFinite($thresholdOracleNum) |
136 | | - ? $thresholdOracleNum |
137 | | - : yMax |
138 | | - : slope * thresholdX; |
139 | | - const startY = clamp(yAtThreshold, yMin, yMax); |
| 134 | + const slope = oracleThreshold / agentThreshold; |
| 135 | + if (!Number.isFinite(slope) || slope < 0) return null; |
140 | 136 |
|
| 137 | + const steps = 40; |
| 138 | + const dx = (xMax - xStart) / steps; |
141 | 139 | const points = []; |
142 | | - points.push([$xScale(thresholdX), $yScale(startY)]); |
143 | | -
|
144 | | - if (slope === null || !Number.isFinite(slope) || slope <= 0) { |
145 | | - const yLine = |
146 | | - slope === null |
147 | | - ? clamp( |
148 | | - Number.isFinite($thresholdOracleNum) ? $thresholdOracleNum : yMax, |
149 | | - yMin, |
150 | | - yMax |
151 | | - ) |
152 | | - : clamp(slope * xMax, yMin, yMax); |
153 | | - points.push([$xScale(xMax), $yScale(yLine)]); |
154 | | - } else { |
155 | | - const xAtTop = yMax / slope; |
156 | | - if (xAtTop >= xMax) { |
157 | | - const yRight = clamp(slope * xMax, yMin, yMax); |
158 | | - points.push([$xScale(xMax), $yScale(yRight)]); |
159 | | - } else { |
160 | | - const clampedXTop = Math.max(thresholdX, Math.min(xAtTop, xMax)); |
161 | | - points.push([$xScale(clampedXTop), $yScale(yMax)]); |
162 | | - if (clampedXTop < xMax) { |
163 | | - points.push([$xScale(xMax), $yScale(yMax)]); |
164 | | - } |
| 140 | + let hasArea = false; |
| 141 | +
|
| 142 | + for (let i = 0; i <= steps; i++) { |
| 143 | + const xVal = xStart + dx * i; |
| 144 | + const rawY = slope * xVal; |
| 145 | + const clampedY = clamp(rawY, yMin, yMax); |
| 146 | + if (clampedY > yMin + 1e-6) { |
| 147 | + hasArea = true; |
165 | 148 | } |
| 149 | + points.push({ x: xVal, y: clampedY }); |
166 | 150 | } |
167 | 151 |
|
168 | | - points.push([$xScale(xMax), $yScale(yMin)]); |
169 | | - points.push([$xScale(thresholdX), $yScale(yMin)]); |
| 152 | + if (!hasArea) return null; |
170 | 153 |
|
171 | | - return points.map(([x, y]) => `${x},${y}`).join(" "); |
| 154 | + return area() |
| 155 | + .x((d) => $xScale(d.x)) |
| 156 | + .y0(() => $yScale(yMin)) |
| 157 | + .y1((d) => $yScale(d.y))(points); |
172 | 158 | })(); |
173 | 159 |
|
174 | 160 | // Regression zone: x < 1.0 AND y < 1.0 |
|
346 | 332 | })(); |
347 | 333 | </script> |
348 | 334 |
|
349 | | -<!-- |
350 | | -{#if isStep(7)} |
351 | | -<g class="zone-highlights"> |
352 | | - <polygon |
353 | | - class="highlight-quadrant" |
354 | | - points={highPerformancePolygonPoints} |
355 | | - fill="var(--bg-tertiary)" |
356 | | - opacity={0.3} |
357 | | - /> |
358 | | -</g> |
359 | | -{/if} --> |
| 335 | +{#if isExplorePhase && highPerformanceRegionPath} |
| 336 | + <g class="zone-highlights"> |
| 337 | + <path class="highlight-region" d={highPerformanceRegionPath} /> |
| 338 | + </g> |
| 339 | +{/if} |
360 | 340 |
|
361 | 341 | <defs> |
362 | 342 | <marker |
|
786 | 766 | pointer-events: none; |
787 | 767 | } |
788 | 768 |
|
| 769 | + .highlight-region { |
| 770 | + fill: var(--bg-tertiary); |
| 771 | + opacity: 0.3; |
| 772 | + } |
| 773 | +
|
789 | 774 | .overflow-indicators { |
790 | 775 | pointer-events: none; |
791 | 776 | } |
|
0 commit comments