5

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
                }),
            }
lallen6
  • 71
  • 6
  • 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. – Graham Hesketh Feb 07 '21 at 00:45

2 Answers2

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} />
Graham Hesketh
  • 317
  • 3
  • 16
  • 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. – lallen6 Feb 18 '21 at 12:26
  • 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? – Graham Hesketh Feb 19 '21 at 13:31
  • 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. – lallen6 Feb 20 '21 at 13:24
-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.

Abhay Phougat
  • 280
  • 2
  • 6