Converting Responsive Grid Layout to Plotly Dash

I'm a very active Dash user and I have started to find a lot of limitations in my Dash usage and I realized that the information/contents about how converting components to dash are absolutely limited, with outdated and very simple examples... And I do not have almost any knowledge in Javascript or React, I'm completely lost on how to convert a component.

I'm trying to convert the Responsive Grid Layout component from react.js to Plotly Dash but I don't know how should I deal with the properties in this case? Link to the component: https://github.com/STRML/react-grid-layout/blob/master/lib/ResponsiveReactGridLayout.jsx

As I'm not experienced with react.js I'm confused about what modifications should I do to be able to convert this component to Plotly Dash.

In the case of the component above, should I only declare the Properties on the Proptypes (as shown below) or do I need to do more modifications?

ResponsiveReactGridLayout.propTypes{
  //
  // Basic props
  //
  className: PropTypes.string,
  style: PropTypes.object,

  // This can be set explicitly. If it is not set, it will automatically
  // be set to the container width. Note that resizes will *not* cause this to adjust.
  // If you need that behavior, use WidthProvider.
  width: PropTypes.number,

  // If true, the container height swells and contracts to fit contents
  autoSize: PropTypes.bool,
  // # of cols.
  cols: PropTypes.number,

  // A selector that will not be draggable.
  draggableCancel: PropTypes.string,
  // A selector for the draggable handler
  draggableHandle: PropTypes.string,

  // Deprecated
  verticalCompact: function (props: Props) {
    if (
      props.verticalCompact === false &&
      process.env.NODE_ENV !== "production"
    ) {
      console.warn(
        // eslint-disable-line no-console
        "`verticalCompact` on <ReactGridLayout> is deprecated and will be removed soon. " +
          'Use `compactType`: "horizontal" | "vertical" | null.'
      );
    }
  },
  // Choose vertical or hotizontal compaction
  compactType: PropTypes.oneOf(["vertical", "horizontal"]),

  // layout is an array of object with the format:
  // {x: Number, y: Number, w: Number, h: Number, i: String}
  layout: function (props: Props) {
    var layout = props.layout;
    // I hope you're setting the data-grid property on the grid items
    if (layout === undefined) return;
    require("./utils").validateLayout(layout, "layout");
  },

  //
  // Grid Dimensions
  //

  // Margin between items [x, y] in px
  margin: PropTypes.arrayOf(PropTypes.number),
  // Padding inside the container [x, y] in px
  containerPadding: PropTypes.arrayOf(PropTypes.number),
  // Rows have a static height, but you can change this based on breakpoints if you like
  rowHeight: PropTypes.number,
  // Default Infinity, but you can specify a max here if you like.
  // Note that this isn't fully fleshed out and won't error if you specify a layout that
  // extends beyond the row capacity. It will, however, not allow users to drag/resize
  // an item past the barrier. They can push items beyond the barrier, though.
  // Intentionally not documented for this reason.
  maxRows: PropTypes.number,

  //
  // Flags
  //
  isBounded: PropTypes.bool,
  isDraggable: PropTypes.bool,
  isResizable: PropTypes.bool,
  // If true, grid items won't change position when being dragged over.
  preventCollision: PropTypes.bool,
  // Use CSS transforms instead of top/left
  useCSSTransforms: PropTypes.bool,
  // parent layout transform scale
  transformScale: PropTypes.number,
  // If true, an external element can trigger onDrop callback with a specific grid position as a parameter
  isDroppable: PropTypes.bool,

  // Resize handle options
  resizeHandles: resizeHandlesType,
  resizeHandle: resizeHandleType,

  //
  // Callbacks
  //

  // Callback so you can save the layout. Calls after each drag & resize stops.
  onLayoutChange: PropTypes.func,

  // Calls when drag starts. Callback is of the signature (layout, oldItem, newItem, placeholder, e, ?node).
  // All callbacks below have the same signature. 'start' and 'stop' callbacks omit the 'placeholder'.
  onDragStart: PropTypes.func,
  // Calls on each drag movement.
  onDrag: PropTypes.func,
  // Calls when drag is complete.
  onDragStop: PropTypes.func,
  //Calls when resize starts.
  onResizeStart: PropTypes.func,
  // Calls when resize movement happens.
  onResize: PropTypes.func,
  // Calls when resize is complete.
  onResizeStop: PropTypes.func,
  // Calls when some element is dropped.
  onDrop: PropTypes.func,

  //
  // Other validations
  //

  droppingItem: PropTypes.shape({
    i: PropTypes.string.isRequired,
    w: PropTypes.number.isRequired,
    h: PropTypes.number.isRequired
  }),

  // Children must not have duplicate keys.
  children: function (props: Props, propName: string) {
    var children = props[propName];

    // Check children keys for duplicates. Throw if found.
    var keys = {};
    React.Children.forEach(children, function (child) {
      if (keys[child.key]) {
        throw new Error(
          'Duplicate child key "' +
            child.key +
            '" found! This will cause problems in ReactGridLayout.'
        );
      }
      keys[child.key] = true;
    });
  },

  // Optional ref for getting a reference for the wrapping div.
  innerRef: PropTypes.any
};

Any help or references are very welcome...

Regards, Leonardo


Implementing the custom component

If you just want to use components from a library with a package available through npm (like react-grid-layout), you don't need to re-implement the components in these libraries. You can simply install them with npm and use them in your custom component.

Example component using ResponsiveGridLayout (src/lib/components/GridLayout.react.js):

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import RGL, {WidthProvider} from 'react-grid-layout';
import '../../../node_modules/react-grid-layout/css/styles.css';
import '../../../node_modules/react-resizable/css/styles.css';

const ResponsiveGridLayout = WidthProvider(RGL);

export default class GridLayout extends Component {
    render() {
        const {id, setProps} = this.props;
        const layout = [
            {x: 0, y: 0, w: 3, h: 3, i: 'a'},
            {x: 0, y: 1, w: 3, h: 3, i: 'b'},
        ];

        return (
            <div id={id}>
                <ResponsiveGridLayout rowHeight={30}>
                    {layout.map((item) => (
                        <div key={item.i} data-grid={item}>
                            <span>{item.i}</span>
                        </div>
                    ))}
                </ResponsiveGridLayout>
            </div>
        );
    }
}

GridLayout.defaultProps = {};

GridLayout.propTypes = {
    /**
     * The ID used to identify this component in Dash callbacks.
     */
    id: PropTypes.string,

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

Environment setup (Skip if you've already done this)

To create and edit your custom component you need to have followed the instructions to setup the cookiecutter dash-component-boilerplate.

But to re-iterate what is said in a couple of bullet points, you need to:

  • Install cookiecutter: pip install cookiecutter
  • Run cookiecutter https://github.com/plotly/dash-component-boilerplate.git. This will generate the environment where you can create your custom components.
  • After filling in the name you want your custom component to have you change directory into the directory generated based on the name you provided. I have chosen the name grid_layout, your structure will be different if you choose different values after being prompted by cookiecutter.
  • At this point you need to install the needed python dependencies by running pip install -r requirements.txt. You can now also install react-grid-layout using npm i react-grid-layout.

Basic usage

When everything is installed we can edit the custom component inside the src/lib/components directory. When we've made some change (replaced the example code with the code listed above) and we are satisfied we can run npm run build to persist the changes.

After this you can run python usage.py and your dash app using your custom component will be run.

usage.py is a regular Dash application that imports the component that is generated from the react component after the build process and can look something like this:

import grid_layout
import dash
from dash.dependencies import Input, Output
import dash_html_components as html

app = dash.Dash(__name__)

app.layout = html.Div([grid_layout.GridLayout(id="grid-layout")])


if __name__ == "__main__":
    app.run_server(debug=True)

You can also edit this as you like.