Skip to content

Commit

Permalink
Resize webR app canvas device to match plot pane
Browse files Browse the repository at this point in the history
  • Loading branch information
georgestagg committed Jun 20, 2024
1 parent f437d4c commit f8b7f33
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 13 deletions.
3 changes: 2 additions & 1 deletion packages/webr/R/canvas.R
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ canvas_purge <- function() {
#' @param ... Arguments to be passed to the graphics device.
#' @export
canvas_install <- function(...) {
args <- as.list(match.call()[-1])
options(device = function() {
webr::canvas(...)
do.call(webr::canvas, args)
})
}
21 changes: 16 additions & 5 deletions src/repl/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import Editor from './components/Editor';
import Plot from './components/Plot';
import Files from './components/Files';
import { Readline } from 'xterm-readline';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { WebR } from '../webR/webr-main';
import { CanvasMessage, PagerMessage } from '../webR/webr-chan';
import { Panel, PanelGroup, PanelResizeHandle, getPanelElement } from 'react-resizable-panels';
import './App.css';

const webR = new WebR({
Expand All @@ -33,6 +33,7 @@ export interface FilesInterface {
}

export interface PlotInterface {
resize: (direction: "width" | "height", px: number) => void;
newPlot: () => void;
drawImage: (img: ImageBitmap) => void;
}
Expand All @@ -49,6 +50,7 @@ const filesInterface: FilesInterface = {
};

const plotInterface: PlotInterface = {
resize: () => { return; },
newPlot: () => { return; },
drawImage: () => {
throw new Error('Unable to plot, plotting not initialised.');
Expand All @@ -71,11 +73,15 @@ async function handlePagerMessage(msg: PagerMessage) {
}
}

const onPanelResize = (size: number) => {
plotInterface.resize("width", size * window.innerWidth / 100);
};

function App() {
return (
<div className='repl'>
<PanelGroup direction="horizontal">
<Panel defaultSize={50} minSize={20}>
<Panel defaultSize={50} minSize={10}>
<PanelGroup autoSaveId="conditional" direction="vertical">
<Editor
webR={webR}
Expand All @@ -87,11 +93,11 @@ function App() {
</PanelGroup>
</Panel>
<PanelResizeHandle />
<Panel minSize={20}>
<Panel onResize={onPanelResize} minSize={10}>
<PanelGroup direction="vertical">
<Files webR={webR} filesInterface={filesInterface} />
<PanelResizeHandle />
<Plot plotInterface={plotInterface} />
<Plot webR={webR} plotInterface={plotInterface} />
</PanelGroup>
</Panel>
</PanelGroup>
Expand All @@ -107,7 +113,12 @@ void (async () => {

// Set the default graphics device and pager
await webR.evalRVoid('webr::pager_install()');
await webR.evalRVoid('webr::canvas_install()');
await webR.evalRVoid(`
webr::canvas_install(
width = getOption("webr.fig.width", 504),
height = getOption("webr.fig.height", 504)
)
`);

// shim function from base R with implementations for webR
// see ?webr::shim_install for details.
Expand Down
5 changes: 2 additions & 3 deletions src/repl/components/Plot.css
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
.plot-background {
background-color: var(--bg-dark);
flex-grow: 1;
overflow: auto;
display: flex;
justify-content: center;
align-items: center;
min-height: 0;
min-width: 0;
}

.plot-container {
max-width: 90%;
max-height: 90%;
aspect-ratio: 1 / 1;
overflow: hidden;
background-color: white;
}

Expand Down
34 changes: 30 additions & 4 deletions src/repl/components/Plot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ import React from 'react';
import './Plot.css';
import { PlotInterface } from '../App';
import { FaArrowCircleLeft, FaArrowCircleRight, FaRegSave, FaTrashAlt } from 'react-icons/fa';
import { Panel } from 'react-resizable-panels';
import { Panel, getPanelElement } from 'react-resizable-panels';
import { WebR } from '../../webR/webr-main';

export function Plot({
webR,
plotInterface,
}: {
webR: WebR;
plotInterface: PlotInterface;
}) {
const plotContainerRef = React.useRef<HTMLDivElement | null>(null);
const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
const canvasElements = React.useRef<HTMLCanvasElement[]>([]);
const plotSize = React.useRef<{width: number, height: number}>({width: 1008, height: 1008});
const [selectedCanvas, setSelectedCanvas] = React.useState<number | null>(null);

// Register the current canvas with the plotting interface so that when the
Expand All @@ -28,13 +32,33 @@ export function Plot({
plotInterface.newPlot = () => {
const plotNumber = canvasElements.current.length + 1;
const canvas = document.createElement('canvas');
canvas.setAttribute('width', '1008');
canvas.setAttribute('height', '1008');
canvas.setAttribute('width', String(plotSize.current.width * 2));
canvas.setAttribute('height', String(plotSize.current.height * 2));
canvas.setAttribute('aria-label', `R Plot ${plotNumber}`);
canvasRef.current = canvas;
canvasElements.current.push(canvas);
setSelectedCanvas(plotNumber - 1);
};

// Resize the canvas() device when the plotting pane changes size
plotInterface.resize = (direction, px) => {
plotSize.current[direction] = Math.max(px / 1.5, 150);
void webR.init().then(async () => {
await webR.evalRVoid(`
# Close any active canvas devices
repeat {
devices <- dev.list()
idx <- which(names(devices) == "canvas")
if (length(idx) == 0) {
break
}
dev.off(devices[idx[1]])
}
# Set canvas size for future devices
options(webr.fig.width = ${plotSize.current.width}, webr.fig.height = ${plotSize.current.height})
`);
});
};
}, [plotInterface]);

// Update the plot container to display the currently selected canvas element
Expand All @@ -47,6 +71,7 @@ export function Plot({
} else {
const canvas = canvasElements.current[selectedCanvas];
plotContainerRef.current.replaceChildren(canvas);
plotContainerRef.current.style.aspectRatio = `${canvas.width} / ${canvas.height}`;
}
}, [selectedCanvas]);

Expand All @@ -66,9 +91,10 @@ export function Plot({

const nextPlot = () => setSelectedCanvas((selectedCanvas === null) ? null : selectedCanvas + 1);
const prevPlot = () => setSelectedCanvas((selectedCanvas === null) ? null : selectedCanvas - 1);
const onResize = (size:number) => plotInterface.resize("height", size * window.innerHeight / 100);

return (
<Panel id="plot" role="region" aria-label="Plotting Pane" minSize={20}>
<Panel id="plot" role="region" aria-label="Plotting Pane" minSize={20} onResize={onResize}>
<div className="plot-header">
<div role="toolbar" aria-label="Plotting Toolbar" className="plot-actions">
<button
Expand Down

0 comments on commit f8b7f33

Please sign in to comment.