6

I have been working on a react-grid-layout to display and move graphs around on screen. Currently I am able to add a plotly.js graph to a container. It is moveable but does not resize with the container. I am wondering if an async function is required to allow the plot to re-render when the container box is resized. Below is the code for the grid layout, and for the histogram as well.

const ReactGridLayout = WidthProvider(Responsive);

const Dash = (props) => {
  const { value, addItem } = props
  const ref = useRef()

  return (
    <div>
      <button onClick={addItem}>Add Item</button>
        <ReactGridLayout
          className="layout"
          onLayoutChange={(e) => console.log(props.layout)}
          breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
          cols={{ lg: 12, md: 12, sm: 6, xs: 4, xxs: 2 }}
          resizeHandles={['s', 'w', 'e', 'n', 'sw', 'nw','se', 'ne']}
          verticalCompact={false}
          // onDragStart={'.dragbackground'}
          // isDraggable={false}
          draggableHandle=".dragHandle"
               
      >        
        {_.map(value, (item, i) => (
          <div id = 'gridID' ref={ref} key={i} data-grid={props.layout[i]}  onClick={() => props.updateLayout(props.layout[i])}>
            <span className='dragHandle'>Drag From Here</span>
            <br/>
            <DashItem  key={i} >
              {item}
            </DashItem>
            <span className='dragHandle'>Drag From Here</span>
          </div>
        ))}
        </ReactGridLayout>
    </div>
  );
}


Dash.propTypes = {
  value: PropTypes.array,
  onIncreaseClick: PropTypes.func.isRequired
}


const mapStateToProps = (state) => {
  return {
    value: state.count,
    layout: state.layout,
    onLayoutChange: state.onLayoutChange,
  };
};

const mapDispatchToProps = dispatch => { 
  return {
    addItem: () => dispatch({type: actionTypes.ADD_GRID_ITEM}),
    updateLayout: (i) => dispatch({type: actionTypes.UPDATE_LAYOUT, layoutId: i}),
    removeItem: (i) => dispatch({type: actionTypes.REMOVE_ITEM, layoutElId: i})
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Dash);

Here is the code for the histogram using plotly.js:

export default function Histogram(props) {

  const { width, height, ref } = useResizeDetector({
    //
  })

  const layout = props.layout

  return(   
    <div style={{height: '100%', width: '100%'}}> 
      <Plot
      useResizeHandler = {true}
      ChartComponent = {Histogram}
      callback={(chart) => this.setChart(chart)}
      style={{width: "100%", height: "100%"}}
      config={{responsive: true}}
      data={[
        {
          x: d1,
          type: 'histogram',
          marker: {
            colour: 'red',
            opacity: 0.5
          },
        },
        {
          x: d2,
          type: 'histogram',
          marker: {
            colour: 'blue',
            opacity: 0.5
          }
        }
      ]}
      layout={{   
        ...layout,
        height: height,
        width: width,
        autosize:true,
        margin: {
          l: 50,
          r: 50,
          b: 100,
          t: 100,
          pad: 4
        },
        title: 'Histogram Title',
          barmode: 'stack',
          bargap: 0.05,
          bargroup: 2,
          xaxis: {
            title: 'X Axis Title'
          },
          yaxis: {
            title: 'Frequency',
            automargin:true
          }}}
      style= {{
        display: 'flex',
        alignItems: 'center',
      }}
      config= {{
      responsive: true 
      }}  
      />
    </div>   
  )
};

The Redux reducer I am using to input graph elements into my grid:

const reducer = (state=initialState, action) => {
    switch (action.type) {
        case actionTypes.ADD_GRID_ITEM:
            const id = uuid()
            console.log("adding: " + id)
            return{
                ...state,
                count: state.count.concat(<Histogram/>),
                layout: state.layout.concat({
                    i: `${id}`,
                    x: 2,
                    y: 0,
                    w: 4,
                    h: 5
                }),
            }
1
  • Word of warning, the Plotly graph is also draggable and so if you try to zoom/pan on the plot it will pick up and drag the entire graph. This requires some care to toggle draggable on the graph once happy with its position. Commented Feb 7, 2021 at 0:45

3 Answers 3

6

I have just built something similar and what works for me is to wrap the plot in a div with a ref using react-resize-detector so that it always fills its parent that way it responds to window resize and dragging resize but remains decoupled from the react layout grid. Here is the plot component I used:

import { useResizeDetector } from 'react-resize-detector'
import Plot from 'react-plotly.js'
export default function ResponsivePlot(props) {
  const { width, height, ref } = useResizeDetector({ 
    // refreshMode: 'debounce', 
    // refreshRate: 1000
  })
  const { layout, layers } = props
  return (
    <div ref={ref} style={{ display: 'flex', height: '100%' }}>
      <Plot
        data={layers}
        layout={{
          ...layout, 
          ...{
            width: width, 
            height: height
          }
        }}
      />
    </div>
  )
};

Usage:

import ResponsivePlot from './ResponsivePlot'

//...other stuff 

<ResponsivePlot layers={layers} layout={layout} />
Sign up to request clarification or add additional context in comments.

3 Comments

I am still having an issue getting this to work, I have tried to implement the react-resize-detector but it's throwing me TypeErrors as it says it is an Object not a function - I don't understand what this means as the component is functional. Apologies if I am missing something obvious - I am quite new to react.
No worries. I'd have to see how you are using the component to debug. Perhaps you could screenshot the code snippets ad post them as an EDIT at the bottom of your question?
I have updated to the current code I am using - as it is I get a TypeError - object is not a function on the useResizeDetector line.
0

The recommendation I would make here is to use react-virtualized-auto-sizer. I have the same use-case where I have a Plotly chart inside of react-grid-layout and AutoSizer has worked like a charm. It greedily grows to fill space and you can just pass in the width/height values it provides to your Plot component instead of using 100% values for width/height.

The below is the minimum configuration I believe you need to ensure your Plot resizes correctly in react-grid-layout.

import { useMemo } from "react";

import Plot from "react-plotly.js";
import AutoSizer from "react-virtualized-auto-sizer";

// Assuming PlotlyChartComponent is a child item of `GridLayout`
function PlotlyChartComponent() {
  const layout = useMemo(() => ({
    autosize: true, // this is important
    // ...other plot layout fields
  }), [])

  return (
    <AutoSizer>
      {({ height, width }) => (
        <Plot
          layout={layout}
          config={{ autosizable: true }}
          style={{ width, height: height - 38 }}
          data={[...]}
        />
      )}
    <AutoSizer/>
  )
}

Comments

-1

Why dont you set an ID of the chart container (grid layout child component) and when its resizes simply get the new height and width of the container aka grid layout child component / grid item and pass it on to the chart. I am doing the same and it works flawlessly on all breakpoints.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.