Histogram.jsx 5.58 KB
Newer Older
amarcic's avatar
amarcic committed
1
import React, {useEffect, useRef, useState} from "react";
2
import { Grid } from "@material-ui/core";
3
4
import { useTranslation } from "react-i18next";
import { useStyles } from "../../styles";
5
import {select, scaleBand, axisBottom, axisLeft, scaleLinear, max, scaleQuantize} from "d3";
6
import {prepareHistogramData, binTimespanObjects} from "../../utils";
7

8
export const Histogram = (props) => {
9
10
11
    const { t, i18n } = useTranslation();

    const classes = useStyles();
amarcic's avatar
amarcic committed
12

amarcic's avatar
amarcic committed
13
    console.log(props.timelineData);
amarcic's avatar
amarcic committed
14
    const preparedData = prepareHistogramData(props.timelineData)?.filter( e => e&&e );
amarcic's avatar
amarcic committed
15
    //console.log("data prepared for histogram: ", preparedData);
16
    const binnedData = binTimespanObjects({timespanObjects: preparedData, approxAmountBins: 20});
amarcic's avatar
amarcic committed
17
    console.log("data binned for histogram: ", binnedData);
18

19
    const svgRef = useRef();
amarcic's avatar
amarcic committed
20
    //const [data, setData] = useState(binnedData);
21

22
23
    useEffect(() => {
        const svg = select(svgRef.current);
24
        //svg dimensions
25
26
        const containerHeight = parseInt(select("#histogramContainer").style("height")),
            containerWidth = parseInt(select("#histogramContainer").style("width"));
27

28
29
        const margin = {top: 5, right: 20, left: 20, bottom: 30};

30
31
        const width = containerWidth - margin.left - margin.right,
            height = containerHeight - margin.top - margin.bottom;
32
33
34
35
36

        svg.attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)

        //remove previously rendered histogram bars in the case there is no current data from the current search
amarcic's avatar
amarcic committed
37
38
39
40
        if (!binnedData||preparedData.length===0) {
            svg.select(".bars")
                .selectAll(".bar").remove()
        } else {
41
42
            //maximum value on y axis
            const maxYValue = max(binnedData.map( bin => bin.values.length));
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
            //calculate scale for x axis
            const x = scaleBand()
                .domain(binnedData.map( bin => bin.lower))
                //.domain(binnedData.map( bin => `${bin.lower} bis ${bin.upper}`))
                .range([0, width])
                .padding(0.2);

            //calculate scale for y axis
            const y = scaleLinear()
                .domain([0,maxYValue])
                .range([height, 0]);

            //add x axis to svg and rotate labels
            //todo: labels should explicitly convey the span of years the bin covers, not just the lower threshold
            svg.select(".xAxis")
                .attr("transform", `translate(0,${height})`)
                .call(axisBottom(x))
                .selectAll("text")
                    .attr("transform", "translate(-10,0)rotate(-45)")
                    .style("text-anchor", "end");

            //add y axis to svg
            svg.select(".yAxis")
                .call(axisLeft(y));

amarcic's avatar
amarcic committed
69
70
            //color scale;
            //todo: remove; makes no sense to visualize the same thing in two ways
71
72
73
74
            const colorScale = scaleQuantize()
                .domain([0,maxYValue])
                .range(["#5AE6BA","#4BC8A3","#3EAA8C","#318D75","#25725F"]);

75
76
77
78
79
80
81
82
83
84
85
86
            //calculate and append histogram bars for each bin
            svg.select(".bars")
                .attr("transform",`translate(${margin.left}, ${margin.top})`)
                .selectAll("rect").data(binnedData).join(
                    enter => enter.append("rect")
                ).attr("class","bar")
                    //.attr("y", value => y(value.values.length))
                    .attr("y", height*-1)
                    .attr("x", value => x(value.lower))
                    //.attr("x", value => x(`${value.lower} bis ${value.upper}`))
                    .style("transform", "scale(1,-1)")
                    .attr("width", x.bandwidth())
amarcic's avatar
amarcic committed
87
                    .on("mouseenter", (event, value) => {
88
89
                        //const element = svg.selectAll(".bar").nodes();
                        //const index = element.indexOf(event.target);
amarcic's avatar
amarcic committed
90
                        //console.log(value);
amarcic's avatar
amarcic committed
91
92
93
94
95
                        svg
                            .selectAll(".tooltip")
                            .data([value])
                            .join("text")
                            .attr("class","tooltip")
amarcic's avatar
amarcic committed
96
                            .text(`${value.lower}-${value.upper}: ${value.values.length} ${t("Item", {count: value.values.length})}`)
amarcic's avatar
amarcic committed
97
98
                            .attr("text-anchor","middle")
                            .transition()
amarcic's avatar
amarcic committed
99
                            .attr("x", x(value.lower)+x.bandwidth())
amarcic's avatar
amarcic committed
100
                            .attr("y", y(value.values.length)+3);
amarcic's avatar
amarcic committed
101
                    })
amarcic's avatar
amarcic committed
102
                    //.on("mouseleave", () => svg.select(".tooltip").remove())
103
104
                    .transition()
                    .attr("height", value => height - y(value.values.length))
105
                    .attr("fill", value => colorScale(value.values.length));
amarcic's avatar
amarcic committed
106
        }
107
    }, [binnedData])
108
109

    return (
110
        <>
111
            <Grid className={classes.dashboardTileHeader} item container direction="row" spacing={2}>
112
113
114
                <Grid item>
                    <h3 className={classes.h3}>{t('Temporal distribution')}</h3>
                </Grid>
115
            </Grid>
amarcic's avatar
amarcic committed
116
            <Grid id="histogramContainer" className={classes.dashboardTileContent} item container direction="column" spacing={2}>
117
                <Grid item>
118
119
120
121
122
123
                    <svg ref={svgRef}>
                        <g className="bars">
                            <g className="xAxis"></g>
                            <g className="yAxis"></g>
                        </g>
                    </svg>
124
125
                </Grid>
            </Grid>
126
        </>
127
    )
128
};