feat: add click interactivity to Plotly 3D scatter chart
Enable users to click on points in the vector space visualization to open the chunk viewer modal, providing a more direct interaction method alongside the existing "Show Chunk" button. Implementation details: - Register plotly_click event handler in renderPlot() after chart creation - Add handlePlotClick() method to process click events - Use point index mapping to access full result object from this.results - Add loading guard in viewChunk() to prevent concurrent chunk loading - Add cursor styling: pointer for result points, default for query point - Add beforeDestroy() lifecycle hook to cleanup event handlers Features: - Both interaction methods work: click chart points OR "Show Chunk" button - Only result points (trace 0) are clickable, query point (trace 1) ignored - Pointer cursor on hover indicates clickable points - Loading state prevents rapid clicks from causing issues - Memory leak prevention through proper event handler cleanup Technical approach: - Uses index mapping (not data duplication) for efficiency - Results and coordinates arrays have guaranteed 1:1 mapping from API - Event handler re-registered on each chart re-render - CSS-based cursor styling (more performant than JS hover handlers) Testing: - ESLint validation passed - Follows Vue 2.7 component property order conventions - Compatible with existing chunk viewer modal
This commit is contained in:
Vendored
+57
@@ -490,6 +490,13 @@ export default {
|
|||||||
return this.results.filter(r => (r.score || 0) >= threshold)
|
return this.results.filter(r => (r.score || 0) >= threshold)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// Clean up Plotly event handlers to prevent memory leaks
|
||||||
|
const plotDiv = document.getElementById('viz-plot')
|
||||||
|
if (plotDiv && plotDiv.on) {
|
||||||
|
plotDiv.removeAllListeners('plotly_click')
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleDocType(docTypeId, checked) {
|
toggleDocType(docTypeId, checked) {
|
||||||
if (checked && !this.selectedDocTypes.includes(docTypeId)) {
|
if (checked && !this.selectedDocTypes.includes(docTypeId)) {
|
||||||
@@ -733,6 +740,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Plotly.newPlot('viz-plot', traces, layout, config)
|
Plotly.newPlot('viz-plot', traces, layout, config)
|
||||||
|
|
||||||
|
// Register click event handler for result points
|
||||||
|
const plotDiv = document.getElementById('viz-plot')
|
||||||
|
if (plotDiv) {
|
||||||
|
plotDiv.on('plotly_click', this.handlePlotClick)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updatePlot() {
|
updatePlot() {
|
||||||
@@ -751,6 +764,11 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async viewChunk(result) {
|
async viewChunk(result) {
|
||||||
|
// Guard against concurrent loading
|
||||||
|
if (this.viewerLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.showViewer = true
|
this.showViewer = true
|
||||||
this.viewerLoading = true
|
this.viewerLoading = true
|
||||||
this.viewerTitle = result.title || 'Chunk Viewer'
|
this.viewerTitle = result.title || 'Chunk Viewer'
|
||||||
@@ -828,6 +846,35 @@ export default {
|
|||||||
this.showViewer = false
|
this.showViewer = false
|
||||||
this.pdfTotalPages = 0
|
this.pdfTotalPages = 0
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handlePlotClick(eventData) {
|
||||||
|
// Only handle clicks on trace 0 (document results)
|
||||||
|
// Trace 1 is the query point - ignore clicks on it
|
||||||
|
if (!eventData.points || eventData.points.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const point = eventData.points[0]
|
||||||
|
const traceIndex = point.curveNumber // 0 = documents, 1 = query
|
||||||
|
const pointIndex = point.pointNumber // Index in trace data
|
||||||
|
|
||||||
|
// Ignore clicks on query point (trace 1)
|
||||||
|
if (traceIndex !== 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access full result object using pointIndex
|
||||||
|
// Results array is 1:1 with coordinates array (guaranteed by API)
|
||||||
|
const result = this.results[pointIndex]
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
console.warn('Click handler: result not found for index', pointIndex)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call existing viewChunk method
|
||||||
|
this.viewChunk(result)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -955,6 +1002,16 @@ export default {
|
|||||||
#viz-plot {
|
#viz-plot {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
// Pointer cursor for clickable result points (trace 0)
|
||||||
|
:deep(.scatterlayer .trace:first-child .point) {
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default cursor for query point (trace 1)
|
||||||
|
:deep(.scatterlayer .trace:nth-child(2) .point) {
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Results
|
// Results
|
||||||
|
|||||||
Reference in New Issue
Block a user