CoolFace
Apppublic

exbert-project/exbert

sourceHugging Faceapache-2.0updated 3y agoView on Hugging Face
183likes
AttentionConnector.ts280 linesDownload Raw Back to vis
1import * as d3 from "d3";2import 'd3-selection-multi'3import { D3Sel } from "../etc/Util";4import { Edge, EdgeData } from "./EdgeConnector"5import { VComponent } from "./VisComponent";6import { SimpleEventHandler } from "../etc/SimpleEventHandler";7import * as tp from "../etc/types"8 9export type AttentionData = number[][]10 11export const scaleLinearWidth = opacity => 5 * opacity^0.33;12 13export class AttentionGraph extends VComponent<AttentionData>{14    css_name = '';15    _current: {};16 17    _data: AttentionData; // The passed data18    edgeData: EdgeData; // A wrapper around _data. User should not mind19    plotData: Edge[]; // Needed for plotting20 21    /** COMPONENTS22     * Expose the components belonging to the class as properties of the class. 23     * This is useful to create methods that specifically modify a single part or component without having to reselect it. 24     * Makes for more responsive applications25     * */26    svg: D3Sel;27    graph: D3Sel;28 29    // The below components require data30    paths: D3Sel;31    opacityScales: d3.ScaleLinear<any, any>[];32    linkGen: d3.Link<any, any, any>33 34    // OPTIONS WITH DEFAULTS35    _threshold = 0.7; // Accumulation threshold. Between 0-136    normBy: tp.NormBy37 38    static events = {} // No events needed for this one39 40    options = {41        boxheight: 26, // The height of the div boxes around the SVG element42        height: 500,43        width: 200,44        offset: 0, // Should I offset the left side by 1 or not?45    }46 47    constructor(d3Parent: D3Sel, eventHandler?: SimpleEventHandler, options: {} = {}) {48        super(d3Parent, eventHandler)49        this.superInitSVG(options)50        this._init()51    }52 53    _init() {54        this.svg = this.parent;55        this.graph = this.svg.selectAll(`.atn-curve`);56        this.linkGen = d3.linkHorizontal()57            .x(d => d[0])58            .y(d => d[1]);59    }60 61    // Define whether to use the 'j' or 'i' attribute to calculate opacities62    private scaleIdx(): "i" | "j" {63        switch (this.normBy) {64            case tp.NormBy.COL:65                return 'j'66            case tp.NormBy.ROW:67                return 'i'68            case tp.NormBy.ALL:69                return 'i'70 71        }72 73    }74 75    /**76     * Create connections between locations of the SVG using D3's linkGen77     */78    private createConnections() {79        const self = this;80        const op = this.options;81        if (this.paths) {82            this.paths.attrs({83                'd': (d, i) => {84                    const data: { source: [number, number], target: [number, number] } =85                    {86                        source: [0, op.boxheight * (d.i + 0.5 + op.offset)],87                        target: [op.width, op.boxheight * (d.j + 0.5)] // + 2 allows small offset88                    };89                    return this.linkGen(data);90                },91                'class': 'atn-curve'92            })93                .attr("src-idx", (d, i) => d.i)94                .attr("target-idx", (d, i) => d.j);95        }96    }97 98    /**99     * Change the height of the SVG100     */101    private updateHeight() {102        const op = this.options;103        if (this.svg != null) {104            this.svg.attr("height", this.options.height + (op.offset * this.options.boxheight))105        }106        return this;107    }108 109    /**110     * Change the width of the SVG111     */112    private updateWidth() {113        if (this.svg != null) {114            this.svg.attr("width", this.options.width)115        }116        return this;117    }118 119    /**120     * Change the Opacity of the lines according to the value of the data121     */122    private updateOpacity() {123        const self = this;124        if (this.paths != null) {125            // paths.transition().duration(500).attr('opacity', (d) => {126            this.paths.attr('opacity', (d) => {127                const val = this.opacityScales[d[self.scaleIdx()]](d.v);128                return val;129            })130            this.paths.attr('stroke-width', (d) => {131                const val = this.opacityScales[d[self.scaleIdx()]](d.v);132                return scaleLinearWidth(val) //5 * val^0.33;133            })134        }135        return this;136    }137 138    /**139     * Rerender the graph in the event that the data changes140     */141    private updateData() {142        if (this.graph != null) {143            d3.selectAll(".atn-curve").remove();144 145            const data = this.plotData146 147            this.paths = this.graph148                .data(data)149                .join('path');150 151            this.createConnections();152            this.updateOpacity();153 154            return this;155        }156    }157 158    /**159     * Scale the opacity according to the values of the data, from 0 to max of contained data160     * Normalize by each source target, or across the whole161     */162    private createScales = () => {163        this.opacityScales = [];164        let arr = []165 166        // Group normalization167        switch (this.normBy){168            case tp.NormBy.ROW:169                arr = this.edgeData.extent(1);170                this.opacityScales = [];171                arr.forEach((v, i) => {172                    (this.opacityScales as d3.ScaleLinear<any, any>[]).push(173                        d3.scaleLinear()174                            .domain([0, v[1]])175                            .range([0, 0.9])176                    )177                })178                break;179            case tp.NormBy.COL:180                arr = this.edgeData.extent(0);181                this.opacityScales = [];182                arr.forEach((v, i) => {183                    (this.opacityScales as d3.ScaleLinear<any, any>[]).push(184                        d3.scaleLinear()185                            .domain([0, v[1]])186                            .range([0, 0.9])187                    )188                })189                break;190            case tp.NormBy.ALL:191                const maxIn = d3.max(this.plotData.map((d) => d.v))192                for (let i = 0; i < this._data.length; i++) {193                    this.opacityScales.push(d3.scaleLinear()194                        .domain([0, maxIn])195                        .range([0, 1]));196                }197                break;198            default:199                console.log("Nor norming specified");200                break;201        }202    }203 204    /**205     * Access / modify the data in a D3 style way. If modified, the component will update just the part that is needed to be updated206     */207    data(): AttentionData208    data(value: AttentionData): this209    data(value?) {210        if (value == null) {211            return this._data;212        }213 214        this._data = value;215        this.edgeData = new EdgeData(value);216        this.plotData = this.edgeData.format(this._threshold);217        this.createScales();218        this.updateData();219        return this;220    }221 222    /**223     * Access / modify the height in a D3 style way. If modified, the component will update just the part that is needed to be updated224     */225    height(): number226    height(value: number): this227    height(value?) {228        if (value == null) {229            return this.options.height230        }231 232        this.options.height = value233        this.updateHeight()234        return this;235    }236 237    /**238     * Access / modify the width in a D3 style way. If modified, the component will update just the part that is needed to be updated239     */240    width(): number241    width(value: number): this242    width(value?: number): this | number {243        if (value == null) {244            return this.options.width;245        }246        this.options.width = value;247        this.updateWidth();248        return this;249    }250 251    /**252     * Access / modify the threshold in a D3 style way. If modified, the component will update just the part that is needed to be updated253     */254    threshold(): number255    threshold(value: number): this256    threshold(value?) {257        if (value == null) {258            return this._threshold;259        }260 261        this._threshold = value;262        this.plotData = this.edgeData.format(this._threshold);263        this.createScales();264        this.updateData();265        return this;266    }267 268    _wrangle(data: AttentionData) {269        return data;270    }271 272    _render(data: AttentionData) {273        this.svg.html('')274        this.updateHeight();275        this.updateWidth();276 277        this.updateData();278        return this;279    }280}