File size: 5,243 Bytes
494c9e4 | 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 | import * as d3 from 'd3';
import type { GLTR_Text_Box } from '../vis/GLTR_Text_Box';
import type { Histogram } from '../vis/Histogram';
import type { HistogramBinClickEvent } from '../vis/Histogram';
import type { ScatterPlot, ScatterChunkClickEvent } from '../vis/ScatterPlot';
import type { FrontendAnalyzeResult } from '../api/GLTR_API';
import {
calculateHighlights,
type HistogramType,
type HighlightData
} from '../utils/highlightUtils';
export type HighlightCurrentData = { result: FrontendAnalyzeResult; signalProbs?: number[]; pPwValues?: number[]; pwScores?: number[] } | null;
export type HighlightControllerOptions = {
stats_frac: Histogram;
stats_raw_score_normed?: Histogram;
stats_match_score_progress?: ScatterPlot;
lmf: GLTR_Text_Box;
currentData: HighlightCurrentData;
};
export class HighlightController {
private options: HighlightControllerOptions;
constructor(options: HighlightControllerOptions) {
this.options = options;
}
/**
* 清除所有高亮(文本与直方图)
*/
public clearHighlights(): void {
this.options.stats_frac.clearSelection();
this.options.stats_raw_score_normed?.clearSelection();
this.options.stats_match_score_progress?.clearSelection();
this.options.lmf.clearHighlight();
}
/**
* 处理直方图 bin 点击事件
*/
public handleHistogramBinClick(ev: HistogramBinClickEvent): void {
const { currentData } = this.options;
if (!currentData) return;
// 如果 binIndex 为 -1,表示用户取消选择,清除所有高亮
if (ev.binIndex === -1) {
this.clearHighlights();
return;
}
const { x0, x1, binIndex, no_bins, source } = ev;
const highlightData: HighlightData = { ...currentData.result, signalProbs: currentData.signalProbs, pPwValues: currentData.pPwValues, pwScores: currentData.pwScores };
let histogramType: HistogramType = 'token';
if (source === 'stats_raw_score_normed') histogramType = 'raw_score_normed';
if (histogramType === 'raw_score_normed') {
this.options.stats_frac.clearSelection();
} else {
this.options.stats_raw_score_normed?.clearSelection();
}
this.options.stats_match_score_progress?.clearSelection();
const { indices, style } = calculateHighlights(histogramType, x0, x1, binIndex, no_bins, highlightData);
this.options.lmf.setHighlightedIndices(indices, style);
}
/**
* 处理 match score per chunk 进度图 chunk 区域点击
*/
public handleMatchScoreChunkClick(ev: ScatterChunkClickEvent): void {
if (ev.source !== 'stats_match_score_progress') return;
const { currentData } = this.options;
if (!currentData) return;
if (ev.chunkIndex === -1) {
this.options.lmf.clearHighlight();
return;
}
this.options.stats_frac.clearSelection();
this.options.stats_raw_score_normed?.clearSelection();
this.options.lmf.setChunkCharRangeHighlight(ev.x0, ev.x1);
this.options.lmf.scrollToUnicodeCharOffset(ev.x0);
}
/** 获取当前高亮数据 */
public getCurrentData(): HighlightCurrentData {
return (this.options as { currentData: HighlightCurrentData }).currentData;
}
/**
* 更新当前数据(当数据变化时调用)
*/
public updateCurrentData(currentData: HighlightCurrentData): void {
(this.options as { currentData: HighlightCurrentData }).currentData = currentData;
}
}
/**
* 初始化高亮清除事件监听(点击空白处和 ESC 键)
*/
export const initHighlightClearListeners = (
clearHighlights: () => void
): void => {
// 点击页面空白处清除高亮(通用解决方案)
// 监听整个文档的点击事件,但排除可交互元素
d3.select('body').on('click.clearHighlight', (event: MouseEvent) => {
const target = <HTMLElement>event.target;
if (!target) return;
// 排除可交互元素:token、按钮、输入框、直方图bin等
const isInteractive =
target.closest('.token') || // token元素
target.closest('button') || // 按钮
target.closest('input') || // 输入框
target.closest('textarea') || // 文本域
target.closest('select') || // 下拉框
target.closest('.bar') || // 直方图bar
target.closest('.hover-area') || // 直方图悬停区域
target.closest('a') || // 链接
target.closest('[role="button"]') || // 有button角色的元素
target.closest('[onclick]'); // 有onclick属性的元素
// 如果点击的不是可交互元素,则清除高亮
if (!isInteractive) {
clearHighlights();
}
});
// 按下 ESC 键清除高亮
d3.select(window).on('keydown.clearHighlight', (event: KeyboardEvent) => {
if (event.key === 'Escape') {
clearHighlights();
}
});
};
|