File size: 3,445 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
import { VComponent } from "./VisComponent" 
import { D3Sel } from "../utils/Util";
import { SimpleEventHandler } from "../utils/SimpleEventHandler";
import { tickStep } from "d3";
import * as d3 from "d3";


export type BarChartData = {
    values: number[],
    label?: string[],
    extent?: number[],
    colors?: string[]
}

export class BarChart extends VComponent<BarChartData>{
    protected options = {
        width: 200,
        height: 150,
        margin_top: 10,
        numberFormat: d3.format('.3')
    };

    protected css_name = "barchartX";
    protected _current = {};
    highlightLabel: d3.Selection<SVGTextElement, any, any, any>;

    constructor(parent: D3Sel, eventHandler?: SimpleEventHandler, options = {}) {
        super(parent, eventHandler);
        this.superInitSVG(options);
        this._init();
    }

    protected _init() {
        const op = this.options;
        this.parent.attrs({ width: op.width, height: op.height });

        this.layers.bg.append('g')
            .attr('class', 'y-axis')
            .attr('transform', `translate(${op.width - 33},0)`);

        this.highlightLabel = this.layers.fg.append('text')
            .attr('class', 'highlightLabel sizeLabel')
    }

    protected _wrangle(data: BarChartData) {
        return data;
    }

    protected _render(rd: BarChartData): void {
        const op = this.options;

        const xScale = d3.scaleLinear().domain([0, rd.values.length])
            .range([5, op.width - 35]);

        const extent = rd.extent || [0, d3.max(rd.values)]
        const yScale = d3.scaleLinear().domain(extent)
            .nice(10).range([op.height - 35, op.margin_top]);

        const adjustWidth = (bandH: number) => (bandH > 5) ? (bandH - 1) : (0.9 * bandH);
        const width = adjustWidth(xScale(1) - xScale(0))

        const colorValue = (index: number) => rd.colors ? (rd.colors[index % rd.colors.length]) : null;


        const allData = rd.values.map((v, i) => ({ v, c: colorValue(i) }))

        this.layers.main.selectAll('.bar').data(allData)
            .join('rect')
            .attr('class', 'bar')
            .attrs({
                x: (d, i) => xScale(i),
                y: d => yScale(d.v),
                width: d => width,
                height: d => op.height - 35 - yScale(d.v),
            })
            .style('fill', d => d.c)
            .style('opacity', 1)
            .on('mouseenter', (event, d: any) => {
                // In D3 v7, data is the second argument. We need the index 'i' which is not passed directly in v7 event listeners on selections.
                // However, we can use the data 'd' directly if we change how we access values.
                // But here xScale uses index 'i'.
                // A workaround is to attach index to data or find index.
                // Since allData is bound, we can find index of d in allData.
                const i = allData.indexOf(d);
                
                const x = xScale(i) + .5 * width;
                const y = yScale(d.v) - 2;

                this.highlightLabel
                    .attr('transform', `translate(${x},${y})`)
                    .style('visibility', null)
                    .text(() => d.v);
            })
            .on('mouseleave', () => this.highlightLabel.style('visibility', 'hidden'))

        this.layers.bg.select('.y-axis').call(<any>d3.axisRight(yScale).tickFormat(op.numberFormat));


    }


}