Graph.react.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import './css/saveClick.css';

import CoreGraph from './_core/CoreGraph.react';
import CoreDataTable from './_core/CoreDataTable.react';
import Configurator from './Configurator.react';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';


const EMPTY_DATA = [];

const TABLE_PNG = "data:image/png;base64,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kuucXmmavXPvZpW9Be316fVkbaRGCDDzjIAAi6kp+1NiLitJqkm5arSjlJJJaTIpTUopKbM1xkNHqlSpqiqDqs6grlMdnED7GxqUQXV1Ppk0qepqb4H6zSglVV1nePpYJs35lO1d07GAmSWAAIunlL11H9eomv1JZPv3dTuTccZlkpPLx/LS43fmvhN358TKegZVLYiQZC94TEqTp7cu5pGnv5wvPHMuT21fyLAaZGkw3Fs3lL016NeGjTKe7BUcvNkgUUqqUZ3BmWNpHjufZncshAAzSQABFkvJ3oLzJldPmblmytXWeDsby+v5d1715vz7r/nL+YY7X5mlwWj/6TZcVpJMmiY7k9384aOfyT//+Afy/3zyt/PMzrNZHS7t/6X9c+7KENJk71wcVDc/xa8pqUaD1KfX0zxxfm/3NiEEmDECCNCOWVmBXnJ56tVBf68JH5u723nl6ZfkH3zL9+Vtr3xD931k5gwGdZYGw3zzva/LN9/7urztFW/IjzzwT/LZJ7+U1dHy3l862NjgihByaSrWrcTaUlItjzI4dSyTJy7srSkRQoAZYhE6sFiuW2TwypGP3dx7/M7847f+LeGD2/Ydr3xD/udv/+G89MRd2RrvXPGVa06+kusvVH8hpaReXcrg5FpS17MR/gH2CSDAQinX7npVLgeScTPJ0mCYH3nT9+RN97y2+84xV77pnq/P337jd2epHmbc7K85unJr533POSdvVimpjy1ncHxVjRpgpgggQIvKdLfS7LWrPrf3saTJbrObb773tfmu17zliL4/LJp/7zVvyZvufW12m939jQsun3NXnYPXfu4WWr2xknpjpeu3BnDbBBBgsVz7lHj/aXQpyage5jte+cYsD0bd94u5tDJcynd8zRszqocpl4paXjsN6xALqEqSqmRwYi31seXnHhtgCgkgwOK43r3ZQb2PlCwNRvkmU69o2Rtf8vVZGowub918vfXih8kN+3VrBifXUq0tKVQITD0BBFgYz304fHnefFOarI2Wc3rleNfdYs6dWT2RtdFymnLl7mvXrAM5bGbY311reGo91eqSkRBgqtmGF2jFwTT2ab7tec5Mlyv+XJpkZbCSynamtKyu6qwMVlKa8ykHp9e1dWja2Ma6lKSuU59ay+SJJtlRqBCYTkZAAGBelJJqOMjg1PpelXUjIcAUEkAAYJ6Ukmpp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";


//generates random id;
let guid = () => {
    let s4 = () => {
        return Math.floor((1 + Math.random()) * 0x10000)
            .toString(16)
            .substring(1);
    }
    //return id of format 'aaaaaaaa'-'aaaa'-'aaaa'-'aaaa'-'aaaaaaaaaaaa'
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}

/**
 * <div style="width:450px; margin-left: 20px; float: right;  margin-top: -150px;">
 * <img src="https://raw.githubusercontent.com/VK/dash-express-components/main/.media/graph.png"/>
 * <img src="https://raw.githubusercontent.com/VK/dash-express-components/main/.media/graph-table.png"/>
 * <img src="https://raw.githubusercontent.com/VK/dash-express-components/main/.media/graph-modal.png"/>
 * </div>
 * 
 * 
 * The `Graph` component is a combination of the original dash `Graph` and the dash `data_table`.
 *
 * It can not only be used to render a plotly.js-powered data visualization,
 * but also shows a searchable table, if only data is submitted.
 * 
 * In addition, there is the possibility to add plot parameters as `defParams` and 
 * the dataframe `meta` data.  
 * This automatically adds a configurator modal, which can be opened via a button
 * at the bottom right.
 * 
 * 
 * @hideconstructor
 * 
 * @example
 * import dash_express_components as dxc
 * import plotly.express as px
 * 
 * meta = dxc.get_meta(px.data.gapminder())
 * 
 * dxc.Graph(
 *     id="fig",
 *     meta=meta,
 *     defParams={}
 * )
 * @public
 */
class Graph extends Component {
    constructor(props) {
        super(props);

        this.state = {
            prependData: [],
            extendData: [],
            //page_current: 0,
            sort_by: [],
            is_loading: false,
            filter_query: "",
            config_modal_open: false,
            defParams: props.defParams,
            hiddenColumns: props.hiddenColumns,
            meta: this.filterMeta(props.meta),
            internalFigure: { data: [] }
        };

        this.clearState = this.clearState.bind(this);
        this.config_in_modal_ref = React.createRef();
        this.config_modal_id = guid();
    }

    filterMeta(meta) { 
    // remove the hiddenColums from the meta
        const hiddenColumns = (this.state && this.state.hiddenColumns) ? this.state.hiddenColumns : this.props.hiddenColumns;
        
        if (typeof meta === "object") {
            for (let key in meta) {
                if (hiddenColumns.includes(key)) {
                    delete meta[key];
                }
            }
        }
        
        return meta;
    }

    componentDidMount() {
        if ("plotApi" in this.props && "defParams" in this.props) {
            this.setState({ is_loading: true });

            let that = this;

            setTimeout(() => {
                that.update_figure_from_defParams(that.props.defParams, true);
            }, 200);

        }
    }


    isGraph() {
        try {
            if ("plotApi" in this.props && this.props.plotApi !== "") {
                return ("internalFigure" in this.state && "data" in this.state.internalFigure)
            } else {
                return ("data" in this.props.figure)
            }
        } catch (e) {
            return ("data" in this.props.figure)
        }
    }


    sendSavedData(image) {
        let messageData = { thumbnail: image, defs: this.props.defParams, app: window.appName, href: location.href };

        try {
            window.parent.postMessage(messageData, "*");
        } catch (e) {
            console.log(e);
        }
    }
    saveClick() {
        if (this.isGraph()) {
            //compute a thumpnail
            let imagePromise = Plotly.toImage(document.getElementById(this.props.id).children[1], { format: 'png', height: 400, width: 800 });
            //send the image data once created
            imagePromise.then((img) => this.sendSavedData(img));
        } else {
            this.sendSavedData(TABLE_PNG);
        }
    }



    handleOpen() {
        this.setState({ config_modal_open: true });

        let that = this;
        setTimeout(() => {
            that.config_in_modal_ref.current.update_config(this.props.defParams);
        }, 200);
    }

    handleClose() {
        this.setState({ config_modal_open: false });
    }

    inIframe() {
        try {
            return window.self !== window.top;
        } catch (e) {
            return true;
        }
    }

    usePlotApi() {
        try {
            return "plotApi" in this.props && this.props.plotApi !== "";
        } catch (e) {
            return false;
        }
    }

    update_figure_from_defParams(input_params, initial = true) {
        let defParams = JSON.parse(JSON.stringify(input_params));

        if (!initial) {
            this.setState({ defParams: defParams });
        }

        this.setState({ is_loading: true });

        const handleResponse = (xhr) => {
            if (xhr.status === 200) {
                if (xhr.responseText !== "") {
                    try {
                        var data = JSON.parse(xhr.responseText);

                        // Handling the response data accordingly
                        if ("plots" in data) {
                            data = data.plots;
                            if (Array.isArray(data)) {
                                data = data[0];
                            }
                        }

                        if ("meta" in data) {
                            const { meta, ...figdata } = data;
                            this.setState({ internalFigure: figdata, meta: this.filterMeta(data.meta) });
                        } else {
                            this.setState({ internalFigure: data });
                        }
                    } catch (e) {
                        console.log(e);
                    }
                    this.setState({ is_loading: false });
                }
            } else if (xhr.status === 202) {
                // Retry the request if status is 202 (Accepted)
                setTimeout(() => {
                    sendRequest(defParams);
                }, 1000); // Adjust the retry interval as needed
            } else {
                this.setState({ is_loading: false });
            }
        };

        const handleTimeout = () => {
            // Handle timeout situations if needed
            this.setState({ is_loading: false });
        };

        const handleLongCallback = (xhr) => {
            if (this.props.longCallback) {
                xhr.setRequestHeader('X-Longcallback', 'true');
            }
        };

        const sortedObject = (obj) => {
            if (typeof obj !== "object" || obj === null) return obj;

            if (Array.isArray(obj)) return obj.map(sortedObject);

            const ordered = {};
            Object.keys(obj).sort().forEach(function (key) {
                ordered[key] = sortedObject(obj[key]);
            });
            return ordered;
        }

        const sortedStringify = (obj) => {
            return JSON.stringify(sortedObject(obj));
        }

        const sendRequest = (send_data) => {
            var xhr = new XMLHttpRequest();
            xhr.open("POST", this.props.plotApi, true);
            xhr.setRequestHeader('Content-Type', 'application/json');

            handleLongCallback(xhr);

            xhr.onreadystatechange = function () {
                if (xhr.readyState === XMLHttpRequest.DONE) {
                    handleResponse(xhr);
                }
            };

            xhr.ontimeout = handleTimeout;

            xhr.send(sortedStringify(send_data));
        };

        let send_data = JSON.parse(JSON.stringify(defParams));
        if (["auto", "png"].includes(send_data["plot"]["params"]["render"])) {
            if (send_data["plot"]["params"]["render_size"] === undefined) {
                try {
                    send_data["plot"]["params"]["render_size"] = [this.graphDiv.clientWidth, this.graphDiv.clientHeight];
                } catch (e) { }
            }
        }

        sendRequest(send_data);
    }

    /**
     * if the plot config changes and the extra plotApi should be used
     * Then we have to update the content
     * @private
     */
    UNSAFE_componentWillReceiveProps(newProps) {

        if (this.usePlotApi()) {
            if (newProps.defParams !== this.state.defParams) {

                if (JSON.stringify(newProps.defParams) !== JSON.stringify(this.state.defParams)) {
                    this.update_figure_from_defParams(newProps.defParams, false);
                }

            }
        }

        if (newProps.figure !== this.state.internalFigure) {

            // remove meta from figure
            if (newProps.figure && newProps.figure.meta) {
                let { meta, ...figure } = newProps.figure;
                this.setState({ figure: figure });
            } else {
                this.setState({ figure: newProps.figure });
            }

        }

        if (newProps.meta !== this.state.meta && newProps.meta) {
            this.setState({ meta: this.filterMeta(newProps.meta) });
        }

    }

    clearState(dataKey) {

        this.setState(props => {
            var data = props[dataKey];
            const res =
                data && data.length
                    ? {
                        [dataKey]: EMPTY_DATA,
                    }
                    : undefined;

            return res;
        });
    }


    render() {


        const spinnerClass = this.state.is_loading ? "dxc-spinner-container" : "dxc-spinner-container dxc-spinner-hidden";

        /*Start VK addon*/
        let save_button = "";
        let edit_button = "";
        if (this.props.defParams && this.inIframe() && this.props.saveClick) {
            save_button = <a className="saveClickButton p-1" onClick={this.saveClick.bind(this)} key={this.props.id + "-save-button"}>
                <svg viewBox="0 -35 576 512" className="icon" height="1.5em" width="1.5em">
                    <path fill="currentColor" d="M416 448h-84c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h84c17.7 0 32-14.3 32-32V160c0-17.7-14.3-32-32-32h-84c-6.6 0-12-5.4-12-12V76c0-6.6 5.4-12 12-12h84c53 0 96 43 96 96v192c0 53-43 96-96 96zm-47-201L201 79c-15-15-41-4.5-41 17v96H24c-13.3 0-24 10.7-24 24v96c0 13.3 10.7 24 24 24h136v96c0 21.5 26 32 41 17l168-168c9.3-9.4 9.3-24.6 0-34z" transform="matrix(-1 0 0 1  576 0 )"></path>
                </svg>
            </a>
        }
        if (this.props.defParams && this.state.meta && this.props.editButton) {
            edit_button = <a className="saveClickButton" onClick={e => this.handleOpen()} key={this.props.id + "-edit-button"}>
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" height="1.3em" width="1.3em">
                    <path fill="currentColor" d="M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z" />
                </svg>
            </a>
        }


        let config_meta = (this.state.meta) ? this.state.meta : this.props.meta
        config_meta = (config_meta) ? config_meta : {}
        let configurator_modal = (
            <Modal
                centered
                backdrop="static"
                animation={false}
                show={this.state.config_modal_open}
                onHide={() => this.handleClose()}>
                <Modal.Header closeButton>
                    <Modal.Title> Edit Plot Config</Modal.Title>
                </Modal.Header>
                <Modal.Body>
                    <Configurator ref={this.config_in_modal_ref}
                        id={this.config_modal_id}
                        config={this.props.defParams}
                        meta={config_meta}
                        showUpdate={false}
                        showFilter={this.props.showFilter}
                        showTransform={this.props.showTransform}
                    />
                </Modal.Body>
                <Modal.Footer>
                    <Button variant="secondary" onClick={() => this.handleClose()}>
                        Close
                    </Button>
                    <Button variant="primary" onClick={(e) => {

                        this.props.setProps({
                            defParams:
                                this.config_in_modal_ref.current.state.config
                        });

                        this.handleClose();
                    }}>
                        Update Plot
                    </Button>
                </Modal.Footer>
            </Modal>
        )

        /*End VK addon*/

        if (this.isGraph()) {

            if (this.usePlotApi()) {

                let inner_props = {
                    ...this.props,
                    figure: this.state.internalFigure
                }

                return (
                    <div className='pxc-graph-container' ref={(divElement) => { this.graphDiv = divElement }}>
                        <div className={spinnerClass}><div className="dxc-spinner-border" role="status"><span className="sr-only"></span></div>
                        </div>
                        <CoreGraph
                            {...inner_props}
                            clearState={this.clearState}
                        />
                        <div className="saveClickContainer" >{save_button}{edit_button}</div>
                        {configurator_modal}
                    </div>
                );

            } else {
                return (
                    <div className='pxc-graph-container' ref={(divElement) => { this.graphDiv = divElement }}>
                        <div className={spinnerClass}><div className="dxc-spinner-border" role="status"><span className="sr-only"></span></div>
                        </div>
                        <CoreGraph
                            {...this.props}
                            clearState={this.clearState}
                        />
                        <div className="saveClickContainer" >{save_button}{edit_button}</div>
                        {configurator_modal}
                    </div>
                );
            }

        } else {

            let columns = [];

            if (this.usePlotApi()) {
                columns = Object.keys(this.state.internalFigure).map(k => { return { name: k, id: k, hideable:false } });
            } else {
                columns = Object.keys(this.props.figure).map(k => { return { name: k, id: k, hideable:false } });
            }
            if ("defParams" in this.props) {
                try {
                    columns = this.props.defParams.plot.params.dimensions.map(k => { return { name: k, id: k, hideable:false } })
                } catch { }
            }


            const hiddenColumns = (this.state && this.state.hiddenColumns) ? this.state.hiddenColumns : this.props.hiddenColumns;
            let props = {
                id: this.props.id,
                className: this.props.className,
                data: this.props.figure,
                columns: columns,
                //page_current: this.state.page_current,
                sort_by: this.state.sort_by,
                filter_query: this.state.filter_query,
                virtualization: true,
                page_action: 'none',
                fixed_rows: { headers: true, data: 0 },
                style_table: { height: '300px', overflowY: 'auto' },
                style_cell: { 'minWidth': '50px', fontSize: "14px" },
                hidden_columns: hiddenColumns,
            }

            if (this.usePlotApi()) {
                props.data = this.state.internalFigure;
            }



            return (
                <div className='pxc-graph-container' style={{ padding: "5px" }}>
                    <div className={spinnerClass}><div className="dxc-spinner-border" role="status"><span className="sr-only"></span></div>
                    </div>

                    {(!this.state.is_loading) && <CoreDataTable {...props} setProps={
                        el => {

                            if (("selectedData" in el) || ("prependData" in el) || ("extendData" in el)) {
                                this.props.setProps(el);
                            } else {

                                if (
                                    ("page_current" in el && el.page_current !== this.state.page_current) ||
                                    ("sort_by" in el && el.sort_by !== this.state.sort_by) ||
                                    ("filter_query" in el && el.filter_query !== this.state.filter_query)
                                ) {
                                    this.setState(el);
                                }

                            }
                        }
                    }
                    />}

                    <div className="saveClickContainer" style={{ position: "relative", left: "-20px", bottom: "0px" }}>{save_button}{edit_button}</div>
                    {configurator_modal}
                </div>
            );

        }


    }
}


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

    /**
     * The ID of this component, used to identify dash components
     * in callbacks. The ID needs to be unique across all of the
     * components in an app.
     * @type {string}
     */
    id: PropTypes.string.isRequired,

    /**
     * Configuration to describe the plot features
     */
    defParams: PropTypes.object,


    /**
     * The metadata the plotter selection is based on.
     */
    meta: PropTypes.any,

    /**
     * Url to the plot Api
     */
    plotApi: PropTypes.string,

    /**
     * Plotly `figure` object. See schema:
     * https://plotly.com/javascript/reference
     *
     * `config` is set separately by the `config` property
     */
    figure: PropTypes.any,

    /**
     * Generic style overrides on the plot div
     */
    style: PropTypes.object,

    /**
     * The data selected in the plot or in the table
     */
    selectedData: PropTypes.any,

    /**
     * className of the parent div
     */
    className: PropTypes.string,

    /**
     * enable/disable saveClick button
     */
    saveClick: PropTypes.bool,

    /**
     * enable/disable long callbacks
     */
    longCallback: PropTypes.bool,

    /**
     * enable/disable edit button
     */
    editButton: PropTypes.bool,


    /**
     * The current configuration of the plot.
     * @type {Object}
     */
    currentConfig: PropTypes.any,


    /**
     * Prop to define the visibility of the Filter panel
     * @type {boolean}
     */
    showFilter: PropTypes.bool,

    /**
     * Prop to define the visibility of the Transform panel
     * @type {boolean}
     */
    showTransform: PropTypes.bool,

    /**
     * Function that updates the state tree.
     */
    setProps: PropTypes.func,


    /**
     * hidden column names (array of strings)
     */
    hiddenColumns: PropTypes.array,

};


Graph.defaultProps = {
    meta: null,
    figure: {
        data: [],
        layout: {},
        frames: [],
    },
    style: null,
    saveClick: false,
    editButton: true,
    longCallback: false,
    showFilter: true,
    showTransform: true,
    className: "",
    plotApi: "",
    hiddenColumns: ["_id", "index"],
};



/**
 * @private
 */
export default Graph;