holderdeord/hdo-transcript-search

View on GitHub
webapp/src/frontend/components/Timeline.js

Summary

Maintainability
B
6 hrs
Test Coverage
import React, { PropTypes, Component } from 'react';
import BaseChart from './BaseChart';
import TimeUtils from '../utils/TimeUtils';
import Colors from '../utils/Colors';
import { Link } from 'react-router';
import key from 'keymaster';
import { connect } from 'react-redux';

@connect(({ summary: { results } }) => ({ results }))
export default class Timeline extends Component {
    static propTypes = {
        unit: PropTypes.string.isRequired,
        interval: PropTypes.string.isRequired,
        onUnitChange: PropTypes.func,
        focusedIndex: PropTypes.number.isRequired,
    };

    chartOptions = {
        chartPadding: { left: 20, top: 35 },
        low: 0,
        fullWidth: false,
        axisX: {
            showGrid: false,
            labelOffset: { x: -15, y: 8 },
        },
        axisY: {
            labelOffset: { x: -5, y: 6 },
            showGrid: true,
            labelInterpolationFnc: ::this.formatValue,
        },
    };

    responsiveOptions = [
        [
            'screen and (max-width: 710px)',
            {
                chartPadding: { left: 10 },
                fullWidth: true,
                axisX: {
                    labelOffset: { x: -5, y: 0 },
                    labelInterpolationFnc: d => d.slice(2, 4),
                },
            },
        ],
    ];

    aspectRatios = [
        ['screen and (max-width: 992px)', 'minor-sixth'],
        ['screen', 'double-octave'],
    ];

    state = this.fetchStateFrom({ results: [] });

    componentWillReceiveProps(props) {
        this.setState(this.fetchStateFrom(props));
    }

    componentDidMount() {
        key('ctrl+1', ::this.setPercent);
        key('ctrl+2', ::this.setAbsolute);
    }

    componentWillUnmount() {
        key.unbind('ctrl+1');
        key.unbind('ctrl+2');
    }

    setPercent() {
        this.props.onUnitChange({ target: { value: 'Prosent' } });
    }

    setAbsolute() {
        this.props.onUnitChange({ target: { value: 'Absolutt' } });
    }

    fetchStateFrom(props) {
        let queries = [];
        let labels = [];
        let series = { pct: [], count: [] };
        let results = props.results || [];

        results.forEach(r => {
            let query = r.query;
            let result = r.result;

            queries.push(query);

            if (!labels.length && result.timeline.length) {
                labels = result.timeline.map(::this.formatLabel);
            }

            series.pct.push({
                data: result.timeline.map(e => e.pct.toFixed(2)),
            });
            series.count.push({ data: result.timeline.map(e => e.count) });
        });

        return {
            queries: queries,

            data: {
                pct: { labels: labels, series: series.pct },
                count: { labels: labels, series: series.count },
            },
        };
    }

    render() {
        if (this.state.queries.length) {
            this.chartOptions.axisY.onlyInteger = this.props.unit === 'count';

            return (
                <div className="timeline">
                    <div className="controls">
                        <div
                            className="btn-group btn-toggle"
                            onClick={this.props.onUnitChange}>
                            <input
                                type="button"
                                value="Prosent"
                                className={`btn ${
                                    this.props.unit === 'pct'
                                        ? 'btn-primary'
                                        : 'btn-default'
                                }`}
                            />

                            <input
                                type="button"
                                value="Absolutt"
                                className={`btn ${
                                    this.props.unit === 'count'
                                        ? 'btn-primary'
                                        : 'btn-default'
                                }`}
                            />
                        </div>
                    </div>

                    <div className="card">
                        <div className="row result-box result-box-header">
                            <div className="col-md-12">
                                <h4 className="text-center">
                                    Forekomsten av «{this.state.queries.join(', ')}»
                                    over tid
                                </h4>
                            </div>
                        </div>

                        <div className="row result-box">
                            <div className="col-md-12 col-xs-12">
                                <BaseChart
                                    type="Line"
                                    animate
                                    aspectRatios={this.aspectRatios}
                                    tooltipSuffix={
                                        this.props.unit === 'pct' ? '%' : null
                                    }
                                    data={this.state.data[this.props.unit]}
                                    options={this.chartOptions}
                                    responsiveOptions={this.responsiveOptions}
                                />

                                {this.renderQueries()}
                            </div>
                        </div>
                    </div>
                </div>
            );
        } else {
            return <div className="row timeline" />;
        }
    }

    renderQueries() {
        // see chartist.scss for colors
        var queries = this.state.queries.map((q, i) => {
            let className = Colors.colorAt(i);
            let query;

            if (+this.props.focusedIndex === i) {
                query = (
                    <span className={className} style={{ fontWeight: '700' }}>
                        {q}
                    </span>
                );
            } else {
                let queryPath = this.state.queries.join('.');
                let unit = this.props.unit;
                let focused = i;

                query = (
                    <Link to={`/search/${unit}/${queryPath}/${focused}`}>
                        <span className={className}>{q}</span>
                    </Link>
                );
            }

            return <li key={q}>{query}</li>;
        });

        return (
            <ul className="queries-selector" style={{ paddingTop: '1.5rem' }}>
                {queries}
            </ul>
        );
    }

    formatLabel(d) {
        return TimeUtils.formatIntervalLabel(d.key, this.props.interval);
    }

    formatValue(value) {
        return this.props.unit === 'pct'
            ? `${value.toFixed(2).replace('.', ',')}%`
            : value;
    }
}