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
}),
}
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
}),
}
Share
Improve this question
edited Feb 20, 2021 at 13:21
lallen6
asked Jan 4, 2021 at 15:21
lallen6lallen6
812 silver badges6 bronze badges
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. – Graham Hesketh Commented Feb 7, 2021 at 0:45
3 Answers
Reset to default 6I 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 ponent 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} />
The remendation 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
ponent 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/>
)
}
Why dont you set an ID of the chart container (grid layout child ponent) and when its resizes simply get the new height and width of the container aka grid layout child ponent / grid item and pass it on to the chart. I am doing the same and it works flawlessly on all breakpoints.