import * as d3 from "d3"; import * as R from 'ramda' import 'd3-selection-multi' import {d3S, D3Sel} from "../etc/Util"; import { VComponent } from "./VisComponent"; import { SimpleEventHandler } from "../etc/SimpleEventHandler"; import * as tp from "../etc/types" import '../etc/xd3' // Helpers const currMatchIdx = (elem) => +(elem.parentNode).getAttribute('matchidx') const currRowNum = (elem) => +(elem.parentNode).getAttribute('rownum') const backgroundColor = x => `rgba(128, 0, 150, ${0.6*x})` export class CorpusInspector extends VComponent{ css_name = 'corpus-inspector'; _current: {}; _data: tp.FaissSearchResults[]; // The passed data static events = { rowMouseOver: "CorpusInspector_rowMouseOver", rowMouseOut: "CorpusInspector_rowMouseOut", rowClick: "CorpusInspector_rowClick", rowDblClick: "CorpusInspector_rowDblClick", cellMouseOver: "CorpusInspector_cellMouseOver", cellMouseOut: "CorpusInspector_cellMouseOut", cellClick: "CorpusInspector_cellClick", cellDblClick: "CorpusInspector_cellDblClick", } options = { showNext: false } // COMPONENTS inspectorRows: D3Sel inspectorCells: D3Sel scaler = d3.scalePow().range([0,0.9]).exponent(2) constructor(d3Parent: D3Sel, eventHandler?:SimpleEventHandler, options: {} = {}) { super(d3Parent, eventHandler) this.superInitHTML(options) this._init() } private createRows() { const data = this._data this.inspectorRows = this.base.selectAll(".inspector-row") .data(data) .join('div') .classed('inspector-row', true) .attrs({ matchIdx: d => d.index, rowNum: (d, i) => i, }) .on("mouseover", (d, i) => { this.eventHandler.trigger(CorpusInspector.events.rowMouseOver, {}) }) } private addTooltip() { this.inspectorCells = this.inspectorCells .classed('celltooltip', true) .append('span') .classed('tooltiptext', true) .html((d, i, n) => { const entityStr = d.is_ent ? "
Entity" : "" const att = (n[i].parentNode).getAttribute('att').slice(0, 7) const attStr = `
Attention: ${att}` return `POS: ${d.pos.toLowerCase()}
DEP: ${d.dep.toLowerCase()}` + entityStr + attStr }) } private createCells() { const self = this this.inspectorCells = this.inspectorRows.selectAll('.inspector-cell') .data((d:tp.FaissSearchResults) => d.tokens) .join('div') .classed('inspector-cell', true) .attr('index-offset', (d, i, n:HTMLElement[]) => { const matchIdx = currMatchIdx(n[i]) return i - matchIdx }) .attrs({ pos: d => d.pos.toLowerCase(), dep: d => d.dep.toLowerCase(), is_ent: d => d.is_ent }) .text(d => d.token.replace("\u0120", " ")) .classed('matched-cell', d => d.is_match) .classed('next-cell', function(d) { return self.showNext() && d.is_next_word }) .classed('gray-cell', function(d, i) { const idx = +currMatchIdx(this) return self.showNext() && i > idx }) // Highlight the cells appropriately this.inspectorCells.each((d,i,n) => { const idx = currMatchIdx(n[i]) if (i == idx) { const att = d.inward const maxAtt = +d3.max(att) const currRow = currRowNum(n[i]) const scaler = self.scaler.domain([0, maxAtt]) d3.selectAll(`.inspector-row[rownum='${currRow}']`) .selectAll(`.inspector-cell`) .style('background', (d, i) => { return backgroundColor(scaler(att[i])) }) .attr('att', (d, i) => att[i]) } }) self.addTooltip() } private updateData() { this.createRows() this.createCells() } _init() {} _wrangle(data: tp.FaissSearchResults[]) { this._data = data return data; } _render(data: tp.FaissSearchResults[]) { // Remember that this._data is defined in wrangle which should always be called before render // as is defined in the update function this.updateData() } showNext(): boolean showNext(v:boolean): this showNext(v?) { if (v == null) return this.options.showNext this.options.showNext = v return this } }