diff --git a/packages/webr/R/canvas.R b/packages/webr/R/canvas.R index a3558929..97bf366c 100644 --- a/packages/webr/R/canvas.R +++ b/packages/webr/R/canvas.R @@ -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) }) } diff --git a/src/repl/App.tsx b/src/repl/App.tsx index 1e169627..59cf5c81 100644 --- a/src/repl/App.tsx +++ b/src/repl/App.tsx @@ -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({ @@ -33,6 +33,7 @@ export interface FilesInterface { } export interface PlotInterface { + resize: (direction: "width" | "height", px: number) => void; newPlot: () => void; drawImage: (img: ImageBitmap) => void; } @@ -49,6 +50,7 @@ const filesInterface: FilesInterface = { }; const plotInterface: PlotInterface = { + resize: () => { return; }, newPlot: () => { return; }, drawImage: () => { throw new Error('Unable to plot, plotting not initialised.'); @@ -71,11 +73,15 @@ async function handlePagerMessage(msg: PagerMessage) { } } +const onPanelResize = (size: number) => { + plotInterface.resize("width", size * window.innerWidth / 100); +}; + function App() { return (
- + - + - + @@ -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. diff --git a/src/repl/components/Plot.css b/src/repl/components/Plot.css index eb191b8d..25ad95fb 100644 --- a/src/repl/components/Plot.css +++ b/src/repl/components/Plot.css @@ -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; } diff --git a/src/repl/components/Plot.tsx b/src/repl/components/Plot.tsx index b4ac3dfb..ff135684 100644 --- a/src/repl/components/Plot.tsx +++ b/src/repl/components/Plot.tsx @@ -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(null); const canvasRef = React.useRef(null); const canvasElements = React.useRef([]); + const plotSize = React.useRef<{width: number, height: number}>({width: 1008, height: 1008}); const [selectedCanvas, setSelectedCanvas] = React.useState(null); // Register the current canvas with the plotting interface so that when the @@ -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 @@ -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]); @@ -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 ( - +