import {VComponent} from './VisComponent' import {spacyColors} from '../etc/SpacyInfo' import {SVG} from '../etc/SVGplus' import * as d3 from 'd3' import * as R from 'ramda' import { D3Sel } from '../etc/Util'; import { SimpleEventHandler } from '../etc/SimpleEventHandler'; interface MarginInfo { top: number, bottom: number, right: number, left: number } // Dependent on the options in the response type MatchedMetaSelections = "pos" | "dep" | "ent" interface MatchedMetaCount { pos: number dep: number is_ent: number } interface MaxAttMetaCount { offset: number } type MatchedDataInterface = MatchedMetaCount type MaxAttDataInterface = MaxAttMetaCount type DataInterface = MatchedDataInterface | MaxAttDataInterface interface CountedHist { label: string, count: number } type RenderDataInterface = CountedHist[] /** * Data formatting functions */ const toRenderData = (obj: {[s: string]: number}): RenderDataInterface => Object.keys(obj).map((k, i) => { return {label: k, count: obj[k]} }) const toStringOrNum = (a:string) => { const na = +a if (isNaN(na)) { return a } return na } const sortByLabel = R.sortBy(R.compose(toStringOrNum, R.prop('label'))) const sortByCount = R.sortBy(R.prop('count')) const toOrderedRender = R.compose( R.reverse, // @ts-ignore -- TODO: fix sortByCount, toRenderData ) export class CorpusHistogram extends VComponent { css_name = '' static events = {} _current = { chart: { height: null, width: null } } // D3 COMPONENTS svg: D3Sel options: { margin: MarginInfo barWidth: number width: number height: number val: string xLabelRot: number xLabelOffset: number yLabelOffset: number } axes = { x: d3.scaleBand(), y: d3.scaleLinear(), } constructor(d3parent: D3Sel, eventHandler?: SimpleEventHandler, options={}) { super(d3parent, eventHandler) this.options = { margin: { top: 10, right: 30, bottom: 50, left: 40 }, barWidth: 25, width: 185, height: 230, val: "pos", // Change Default, pass through constructor xLabelRot: 45, xLabelOffset: 15, yLabelOffset: 5, } this.superInitSVG() } meta():MatchedMetaSelections meta(val:MatchedMetaSelections): this meta(val?) { if (val == null) { return this.options.val; } this.options.val = val; this.update(this._data) return this; } _init() {} private createXAxis() { const self = this; const op = this.options; const width = op.width - op.margin.left - op.margin.right this.axes.x .domain(R.map(R.prop('label'), self.renderData)) .rangeRound([0, width]) .padding(0.1) this._current.chart.width = width; } private createYAxis() { const self = this; const op = this.options; const height = op.height - op.margin.top - op.margin.bottom this.axes.y .domain([0, +d3.max(R.map(R.prop('count'), self.renderData))]) .rangeRound([height, 0]) this._current.chart.height = height; } private createAxes() { this.createXAxis() this.createYAxis() } _wrangle(data: DataInterface) { const out = data[this.options.val] return toOrderedRender(out) } width():number width(val:number):this width(val?) { if (val == null) { return this.options.width; } this.options.width = val; this.updateWidth(); this.createXAxis(); return this; } height():number height(val:number):this height(val?) { if (val == null) { return this.options.height; } this.options.height = val; this.updateHeight(); this.createYAxis(); return this; } private updateWidth() { this.svg.attr('width', this.options.width) } private updateHeight() { this.svg.attr('height', this.options.height) } private figWidth(data: RenderDataInterface) { const op = this.options; return (data.length * op.barWidth) + op.margin.left + op.margin.right } _render(data:RenderDataInterface) { const self = this; const op = this.options; const curr = this._current; this.parent.html('') this.svg = this.parent this.createAxes(); this.width(this.figWidth(data)); this.updateHeight(); // Initialize axes const g = self.svg.append("g") .attr("transform", SVG.translate({x: op.margin.left, y:op.margin.top})) // Hack to allow clearing this histograms to work self.base = g // Fix below for positional changing const axisBottom = g.append("g") .attr("transform", SVG.translate({x: 0, y:curr.chart.height})) .call(d3.axisBottom(self.axes.x)) if (op.val != "offset") { axisBottom .selectAll("text") .attr("y", op.yLabelOffset) // Move below the axis .attr("x", op.xLabelOffset) // Offset to the right a bit .attr("transform", SVG.rotate(op.xLabelRot)) } g.append("g") .call(d3.axisLeft(self.axes.y)) g.selectAll(".bar") .data(data) .join('rect') .attr("class", "bar") .attr("x", function(d) { return self.axes.x(d.label); }) .attr("y", function(d) { return self.axes.y(d.count); }) .attr("width", self.axes.x.bandwidth()) .attr("height", function(d) { return curr.chart.height - self.axes.y(d.count); }) .style('fill', k => spacyColors.colorScale[op.val](k.label)) } }