Plotter.react.js

import React from 'react';
import Base from './_sub/Base.react';
import Accordion from 'react-bootstrap/Accordion';
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';
import PropTypes from 'prop-types';


import Scatter from './_plot/Scatter.react';
import Box from './_plot/Box.react';
import Violin from './_plot/Violin.react';

import Imshow from './_plot/Imshow.react';

import Bar from './_plot/Bar.react';
import BarCount from './_plot/BarCount.react';
import ScatterMatrix from './_plot/ScatterMatrix.react';

import HistogramLine from './_plot/HistogramLine.react';
import Probability from './_plot/Probability.react';

import Polygon from './_plot/Polygon.react';

import Table from './_plot/Table.react';
let known_plots = [Scatter, Box, Violin, Imshow, Bar, BarCount, ScatterMatrix, HistogramLine, Probability, Polygon, Table].map(el => {
    return { type: el.type, class: el, label: el.label, icon: el.icon }
});
let plots_dict = Object.assign({}, ...known_plots.map((x) => ({ [x.type]: x })));


/**
 * <div style="width:450px; margin-left: 20px; float: right;  margin-top: -150px;">
 * <img src="https://raw.githubusercontent.com/VK/dash-express-components/main/.media/plotter.png"/>
 * <img src="https://raw.githubusercontent.com/VK/dash-express-components/main/.media/plotter-modal.png"/>
 * </div>
 * 
 * The `Plotter` component helps to define the right plot parameters in the style of plotly.express.
 * 
 * There are several different plot types, and some of them are given directly by plotly.express, like:
 * <ul style="margin-left: 20px;">
 *   <li>scatter</li>
 *   <li>box</li>
 *   <li>violin</li>
 *   <li>bar</li>
 *   <li>scatter_matrix</li>
 * </ul>
 * 
 * Others are computed more indirect, like:
 * <ul style="margin-left: 20px;">
 *   <li>imshow</li>
 *   <li>bar_count</li>
 *   <li>histogram_line</li>
 *   <li>probability</li>
 *   <li>table</li>
 * </ul>
 * 
 * @hideconstructor
 * 
 * @example
 * import dash_express_components as dxc
 * import plotly.express as px
 * 
 * meta = dxc.get_meta(px.data.gapminder())
 * 
 * dxc.Plotter(
 * ???
 * )
 * @public
 */
class Plotter extends Base {
    constructor(props) {
        super({}, props);

        this.state =
        {
            ...this.state,

            /* state of the modal to add new filters */
            showModal: false,
            plotType: (this.state.config && this.state.config.type) ? this.state.config.type : "scatter",

        };
    }

    handleClose() {
        this.setState({ showModal: false });
    }
    handleShow() {
        this.setState({ showModal: true });
    }

    UNSAFE_componentWillReceiveProps(newProps) {
        super.UNSAFE_componentWillReceiveProps(newProps);
        try {
            if (("config" in newProps) && ("type" in newProps.config)) {
                this.setState({
                    plotType:
                        newProps.config.type
                });
            }
        } catch { };




    }


    get_modal() {

        const {
            showModal,
            id
        } = this.state;

        return (<Modal
            size="xl"
            centered
            backdrop="static"
            animation={false}
            show={showModal}
            onHide={() => this.handleClose()}
            key={id + "-plot-type-modal"}
        >
            <Modal.Header closeButton>
                <Modal.Title>Plot Types</Modal.Title>
            </Modal.Header>
            <Modal.Body><div className="mt-2 dxc-container dxc-row" style={{ padding: 0 }}>

                {known_plots.map(pt => {


                    return (
                        <div className="dxc-col-6 dxc-mb-2"><Button
                            key={"set-plot-" + pt.type}
                            variant="outline-secondary"
                            className="d-flex align-items-center w-100"

                            onClick={(e) => {

                                this.setState({
                                    plotType: pt.type
                                });
                                this.handleClose()
                            }}

                        >
                            <div style={{ width: "75px", height: "75px" }}>{(pt && "icon" in pt) ? pt.icon : ""}</div>
                            <div className="flex-grow-1 dxc-mt-2 h3">
                                {(pt && "label" in pt) ? pt.label : ""}
                            </div>
                        </Button></div>
                    );
                })}

            </div>
            </Modal.Body>
            <Modal.Footer>
                <Button variant="secondary" onClick={() => this.handleClose()}>
                    Close
                </Button>

            </Modal.Footer>
        </Modal>)
    }

    render() {
        const {
            plotType,
            config,
            allColOptions,
            catColOptions,
            numColOptions,
            allOptions
        } = this.state;

        const { id } = this.props;

        const pt = plots_dict[plotType];

        return (
            <div>

                <Button
                    key={id + "plot-open-button"}
                    variant="outline-secondary"
                    className="d-flex align-items-center w-100 mb-2"
                    onClick={() => this.handleShow()}>

                    {(pt && "icon" in pt) && <div style={{ width: "60px", height: "60px" }}>{pt.icon}</div>}
                    {(pt && "icon" in pt) && <div className="flex-grow-1 mt-2 h3">{pt.label}</div>}
                    {!(pt && "icon" in pt) && <div className="flex-grow-1 mt-2 h3" >Choose a plot type</div>}

                </Button>


                {
                    known_plots.map((plt, idx) => {

                        return (
                            plotType === plt["type"] &&
                            <plt.class
                                key={id + "plottype-" + plt["type"]}
                                id={id + "plottype-" + plt["type"]}
                                allColOptions={allColOptions}
                                catColOptions={catColOptions}
                                numColOptions={numColOptions}
                                allOptions={allOptions}
                                config={config}
                                setProps={e => {
                                    if ("config" in e) {
                                        super.update_config(e["config"]);
                                    }
                                }}
                            />
                        )
                    })
                }


                {this.get_modal()}

            </div>
        )

    }
}



Plotter.defaultProps = {};

/**
 * @typedef
 * @public
 * @enum {}
 */
Plotter.propTypes = {

    /**
     * The ID used to identify this component in Dash callbacks.
     */
    id: PropTypes.string.isRequired,

    /**
    * The config the user sets in this component.
    */
    config: PropTypes.any,

    /**
     * The metadata this section is based on.
     */
    meta: PropTypes.any.isRequired,


    /**
     * The metadata section will create as output.
     */
    meta_out: PropTypes.any,


    /**
     * Dash-assigned callback that should be called to report property changes
     * to Dash, to make them available for callbacks.
     */
    setProps: PropTypes.func
};

/**
 * @private
 */
export default Plotter;