Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Chart Support #846

Merged
merged 86 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
f0d3c3d
adding CellValue::Html
davidfig Nov 18, 2023
3697b91
import/export w/html works
davidfig Nov 19, 2023
6e7ce40
WIP basic html support
davidfig Nov 19, 2023
bc3b1bb
update transform
davidfig Nov 19, 2023
9dc9aa5
improve title
davidfig Nov 19, 2023
d6db4b4
fix sizing and positioning of html
davidfig Nov 20, 2023
faa22a0
fix fps dot
davidfig Nov 20, 2023
db67d7d
adding OutputSize column to control the size of the html
davidfig Nov 20, 2023
5f76bed
right and bottom resizing
davidfig Nov 20, 2023
e2b5942
resizing corner
davidfig Nov 21, 2023
d33b036
resizing of html box
davidfig Nov 21, 2023
c6a4b44
better sheet handling for html cells
davidfig Nov 21, 2023
278ff5d
real time updating of html
davidfig Nov 21, 2023
7ecb0cc
div properly collapses
davidfig Nov 21, 2023
af1cc9b
renaming OutputSize to RenderSize for html cells
davidfig Nov 21, 2023
637ab2c
finish renaming output_size to render_size
davidfig Nov 21, 2023
26455bd
duplicate sheets properly copies html
davidfig Nov 21, 2023
4e62a6d
thumbnail includes gray box for html output
davidfig Nov 22, 2023
747ab71
fixing thumbnail for html when w/h is not defined
davidfig Nov 22, 2023
fb83c5a
included the html cell (y for box was too high)
davidfig Nov 22, 2023
7511de9
included top margin in thumbnail for html
davidfig Nov 22, 2023
f2c116d
slightly better anti-zoom when hovering over html
davidfig Nov 22, 2023
ffc9aea
Merge branch 'main' into plotly
davidfig Nov 22, 2023
8f02398
move context menu above html cells
davidfig Nov 22, 2023
7e11529
fix bug so html properly moves on alt+0, etc.
davidfig Nov 22, 2023
34d121a
remove debug circle
davidfig Nov 22, 2023
d6c3dc6
pinching on div and html properly zoom the viewport
davidfig Nov 22, 2023
9109586
Outputting a plotly.Figure automatically calls to_html()
davidfig Nov 22, 2023
9b8c0d9
try shadows instead of borders for charts
jimniels Nov 22, 2023
2e84289
Refactor `get_render_cells()` internally
HactarCE Nov 23, 2023
ef4d7a7
test for set_cell_render_size
davidfig Nov 23, 2023
744cd32
merge main
davidkircos Nov 24, 2023
504c95a
Merge branch 'main' into plotly
davidfig Nov 27, 2023
3dfda40
fixed the zoom issue with iframes
davidfig Nov 27, 2023
fa1c8b7
fixed the margin in iframe to avoid zoom as well
davidfig Nov 27, 2023
9560c84
highlight edges for resizing
davidfig Nov 27, 2023
4d5e8c4
remove console.log
davidfig Nov 27, 2023
beb0bab
responding to comments
davidfig Nov 27, 2023
f0ad23b
Merge branch 'main' into plotly
davidfig Nov 27, 2023
c6ecfce
Merge branch 'main' into plotly
davidfig Nov 27, 2023
82e4e7d
fix clippy
davidfig Nov 27, 2023
ec9478d
adding tests
davidfig Nov 27, 2023
dcdb238
revert change
davidfig Nov 27, 2023
745d14f
fix render size changes
davidfig Nov 27, 2023
48d3006
Change HTML to CHART
davidfig Nov 28, 2023
f0b93f2
Merge branch 'main' into plotly
davidfig Nov 28, 2023
be89288
fixing syntax errors in merge
davidfig Nov 28, 2023
df9a38b
rendering.rs tests
davidfig Nov 28, 2023
3c0746c
html renders again
davidfig Nov 28, 2023
8585725
Merge branch 'main' into plotly
davidfig Nov 28, 2023
a7238fd
added examples; fixed colors of borders and chart text
davidfig Nov 28, 2023
728c3c7
snapping
davidfig Nov 28, 2023
c5765b7
remove extra Number()
davidfig Nov 28, 2023
177a9a5
Merge branch 'main' into plotly
davidfig Nov 29, 2023
fded5c3
darkened color of "CHART" text
davidfig Nov 29, 2023
8925cd7
pushing changes for comparisons
davidfig Nov 29, 2023
0dcb511
reverting some changes
davidfig Nov 29, 2023
1055008
more reverting
davidfig Nov 29, 2023
005dc8d
more reversions
davidfig Nov 29, 2023
1c2188e
removing commented out code
davidfig Nov 29, 2023
f819c3a
fixed test issues
davidfig Nov 29, 2023
3d68e87
code editor can resize again
davidfig Nov 29, 2023
e34ca20
fixed bug with reloading html
davidfig Nov 29, 2023
9a429c7
fix margins
davidfig Nov 29, 2023
127a9da
deleting/undoing properly shows/removes html
davidfig Nov 29, 2023
ed6ed2e
handle escape key when resizing html
davidfig Nov 29, 2023
e3d31cd
refactoring html cells
davidfig Nov 30, 2023
faaa0f5
Merge branch 'main' into plotly-html
davidfig Nov 30, 2023
ea66ed8
starting to work
davidfig Nov 30, 2023
063e4d6
fixed issue with resizing
davidfig Dec 1, 2023
32d8a12
resizing work
davidfig Dec 1, 2023
2cea5fd
remove toggleoutlines change
davidfig Dec 1, 2023
26653f9
resizing is pixel perfect (except for rust)
davidfig Dec 1, 2023
59eed2a
reworking RenderCells
davidfig Dec 1, 2023
9568b21
RenderSize is now float
davidfig Dec 1, 2023
c042a2d
Merge branch 'main' into plotly
davidfig Dec 1, 2023
6fe4375
removing commented out code
davidfig Dec 1, 2023
9b3c798
adding more test coverage
davidfig Dec 1, 2023
d0a1e52
remove sort of working snapping
davidfig Dec 1, 2023
ea1ccc8
comment out snapping value
davidfig Dec 1, 2023
4d0dae5
fix charting example
davidfig Dec 1, 2023
d8f0d94
default width/height for html
davidfig Dec 1, 2023
a54469c
Merge branch 'main' into plotly
davidfig Dec 1, 2023
9856047
added micropip to chart example; html now stays until it is replaced
davidfig Dec 1, 2023
b5286f3
"add a chart" placeholder link
jimniels Dec 4, 2023
c997abf
Merge branch 'main' into plotly
davidkircos Dec 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions quadratic-client/public/run_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ async def __getitem__(item):
output_value.notnull(), None
).values.tolist()

try:
import plotly
if isinstance(output_value, plotly.graph_objs._figure.Figure):
output_value = output_value.to_html()
except:
pass

# Convert Pandas.Series to array_output
if isinstance(output_value, pd.Series):
Expand Down
20 changes: 20 additions & 0 deletions quadratic-client/src/grid/controller/Grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
CellWrap,
FormattingSummary,
JsClipboard,
JsHtmlOutput,
JsRenderCell,
JsRenderFill,
Rect,
Expand Down Expand Up @@ -122,6 +123,10 @@ export class Grid {
this.thumbnailDirty = true;
}

if (summary.html) {
window.dispatchEvent(new CustomEvent('html-update', { detail: summary.html }));
}

const cursor = summary.cursor ? (JSON.parse(summary.cursor) as SheetCursorSave) : undefined;
if (cursor) {
sheets.current = cursor.sheetId;
Expand Down Expand Up @@ -412,6 +417,16 @@ export class Grid {
this.dirty = true;
}

setCellRenderSize(sheetId: string, x: number, y: number, width: number, height: number) {
const summary = this.gridController.setCellRenderSize(
sheetId,
posToRect(x, y),
BigInt(Math.round(width)),
BigInt(Math.round(height))
);
this.transactionResponse(summary);
}

//#endregion

//#region get grid information
Expand Down Expand Up @@ -465,6 +480,11 @@ export class Grid {
return this.gridController.getRenderBorders(sheetId);
}

getHtmlOutput(sheetId: string): JsHtmlOutput[] {
const data = this.gridController.getHtmlOutput(sheetId);
return JSON.parse(data);
}

//#endregion

//#region Bounds
Expand Down
4 changes: 4 additions & 0 deletions quadratic-client/src/gridGL/QuadraticGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
import { useRecoilState } from 'recoil';
import { editorInteractionStateAtom } from '../atoms/editorInteractionStateAtom';
import { FloatingContextMenu } from '../ui/menus/ContextMenu/FloatingContextMenu';
import { HtmlCells } from './htmlCells/HtmlCells';
import { CellInput } from './interaction/CellInput';
import { useKeyboard } from './interaction/keyboard/useKeyboard';
import { pixiApp } from './pixiApp/PixiApp';
Expand All @@ -16,6 +17,7 @@ export default function QuadraticGrid() {
const containerRef = useCallback((node: HTMLDivElement | null) => {
if (node) setContainer(node);
}, []);

useEffect(() => {
if (container) pixiApp.attach(container);
}, [container]);
Expand Down Expand Up @@ -90,6 +92,7 @@ export default function QuadraticGrid() {
outline: 'none',
overflow: 'hidden',
WebkitTapHighlightColor: 'transparent',
touchAction: 'none',
cursor: panMode === PanMode.Enabled ? 'grab' : panMode === PanMode.Dragging ? 'grabbing' : 'unset',
}}
onContextMenu={(event) => {
Expand All @@ -116,6 +119,7 @@ export default function QuadraticGrid() {
onKeyUp={onKeyUp}
>
{showInput && <CellInput container={container} />}
<HtmlCells />
<FloatingContextMenu container={container} showContextMenu={showContextMenu} />
</div>
);
Expand Down
67 changes: 67 additions & 0 deletions quadratic-client/src/gridGL/UI/HtmlPlaceholders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { sheets } from '@/grid/controller/Sheets';
import { JsHtmlOutput } from '@/quadratic-core/types';
import { colors } from '@/theme/colors';
import { Graphics } from 'pixi.js';
import { TOP_HTML_MARGIN } from '../htmlCells/HtmlCells';

const BORDER_WIDTH = 1;

// Draws the html placeholder for thumbnails
export class HtmlPlaceholders extends Graphics {
// tracks the same data as HtmlCells (duplicated to more easily get it out of react)
private htmlOutput: JsHtmlOutput[] = [];

constructor() {
super();
this.visible = false;
}

private drawPlaceholder(htmlCell: JsHtmlOutput) {
let w = Number(htmlCell.w);
let h = Number(htmlCell.h);

// if width and height = 0 (ie, not user set), then use the size of the HTML element
if (!w || !h) {
const element = document.querySelector(
`[data-sheet="${htmlCell.sheet_id}"][data-pos="${htmlCell.x},${htmlCell.y}"]`
);
if (!element) return;
if (element.tagName === 'iframe') {
const iframe = element as HTMLIFrameElement;
w = parseFloat(iframe.width);
h = parseFloat(iframe.height);
} else {
w = element.clientWidth;
h = element.clientHeight;
}
}
const sheet = sheets.getById(htmlCell.sheet_id);
if (!sheet) {
throw new Error('Expected sheet to be defined in HtmlPlaceholders.drawPlaceholder');
}
const offsets = sheet.offsets.getCellOffsets(Number(htmlCell.x), Number(htmlCell.y));
this.lineStyle(BORDER_WIDTH, colors.htmlPlaceholderThumbnailBorderColor, 1);
this.beginFill(colors.htmlPlaceholderThumbnailColor);
this.drawRect(offsets.x, offsets.y + offsets.h + TOP_HTML_MARGIN, w, h);
this.endFill();
}

prepare() {
this.clear();
const firstId = sheets.getFirst().id;
this.htmlOutput.forEach((cell) => {
if (cell.sheet_id === firstId) {
this.drawPlaceholder(cell);
}
});
this.visible = true;
}

hide() {
this.visible = false;
}

setHtmlCells(htmlOutput: JsHtmlOutput[]) {
this.htmlOutput = htmlOutput;
}
}
63 changes: 63 additions & 0 deletions quadratic-client/src/gridGL/htmlCells/DivHtmlCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { CELL_HEIGHT, CELL_WIDTH } from '@/constants/gridConstants';
import { sheets } from '@/grid/controller/Sheets';
import { JsHtmlOutput } from '@/quadratic-core/types';
import { useCallback } from 'react';
import { pixiApp } from '../pixiApp/PixiApp';
import { Wheel } from '../pixiOverride/Wheel';

interface Props {
htmlCell: JsHtmlOutput;
}

export const DivHtmlCell = (props: Props) => {
const { htmlCell } = props;

const ref = useCallback(
(node: HTMLDivElement | null) => {
if (node) {
node.style.width = htmlCell.w ? Number(htmlCell.w) + 'px' : '';
node.style.height = htmlCell.h ? Number(htmlCell.h) + 'px' : '';
node.innerHTML = htmlCell.html;
node.addEventListener(
'wheel',
(event) => {
const wheel = pixiApp.viewport.plugins.get('wheel') as Wheel | null;
if (!wheel) {
throw new Error('Expected wheel plugin to be defined on viewport');
}
wheel.wheel(event);
event.stopPropagation();
event.preventDefault();
},
{ passive: false }
);
}
},
[htmlCell.h, htmlCell.html, htmlCell.w]
);

const offset = sheets.sheet.getCellOffsets(Number(htmlCell.x), Number(htmlCell.y));

return (
<div
ref={ref}
title={`HTML from (${htmlCell.x}, ${htmlCell.y})`}
// this is needed by HtmlCells.tsx
data-sheet={htmlCell.sheet_id}
// this is needed by PointerHtmlCells.ts
data-pos={`${htmlCell.x},${htmlCell.y}`}
style={{
position: 'absolute',
pointerEvents: 'auto',
left: offset.x,
top: offset.y + offset.height,
minWidth: `${CELL_WIDTH}px`,
minHeight: `${CELL_HEIGHT - 2}px`,
background: 'white',
border: '1px solid black',
boxSizing: 'border-box',
overflow: 'hidden',
}}
/>
);
};
63 changes: 63 additions & 0 deletions quadratic-client/src/gridGL/htmlCells/HTMLResizeControl.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* Shared styles for the 1px line */
.resize-control {
background-color: var(--resize-control-background);
z-index: 10;
}

/* Vertical control, right side */
.resize-control--position-RIGHT {
width: 1px;
height: 100%;
position: absolute;
top: 0;
right: -1px;
cursor: col-resize;
}

/* Horizontal control, bottom side */
.resize-control--position-BOTTOM {
position: relative;
width: 100%;
height: 1px;
cursor: row-resize;
bottom: 0px;
}

/* Effect upon hover/drag */
.resize-control:before {
content: '';
transition: 0.2s ease background-color 0ms;
}
.resize-control--position-RIGHT:before {
position: absolute;
width: 5px;
height: 100%;
right: -2px;
top: 0;
}
.resize-control--position-BOTTOM:before {
position: absolute;
width: 100%;
height: 5px;
bottom: -2px;
left: 0;
}
.resize-control--is-dragging:before,
.resize-control:hover:before {
background-color: var(--resize-control-highlight);
z-index: 100;
/* Delay the transition effect so it only appears when the user shows intent */
transition: 0.2s ease background-color 300ms;
}

/* When control is being dragged, applies styles to body to prevent text
selection and try to keep correct cursor displayed */
body:has(.resize-control--is-dragging) {
user-select: none;
}
body:has(.resize-control--is-dragging.resize-control--position-LEFT) {
cursor: col-resize !important;
}
body:has(.resize-control--is-dragging.resize-control--position-TOP) {
cursor: row-resize !important;
}
59 changes: 59 additions & 0 deletions quadratic-client/src/gridGL/htmlCells/HTMLResizeControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { colors } from '@/theme/colors';
import './HTMLResizeControl.css';

// todo: make this a generic component and combine it with ResizeControl for CodeEditor

interface ResizeControlProps {
position: 'BOTTOM' | 'RIGHT';
}

// the actual class changing is handled in PointerHtmlCells.ts by changing the className programmatically
export function HTMLResizeControl({ position }: ResizeControlProps) {
return (
<div
className={`resize-control resize-control--position-${position}`}
style={{
// @ts-expect-error typescript doesn't like us setting CSS custom properties
'--resize-control-highlight': colors.quadraticPrimary,
'--resize-control-background': colors.mediumGray,
pointerEvents: 'none',
}}
// onPointerDown={(e) => {
davidfig marked this conversation as resolved.
Show resolved Hide resolved
// dispatchEvent(new CustomEvent('resize-html-pointer-down', { detail: e }));
// const target = e.currentTarget;
// target.classList.add('resize-control--is-dragging');
// }}
// onPointerUp={(e) => {
// dispatchEvent(new CustomEvent('resize-html-pointer-up', { detail: e }));
// const target = e.currentTarget;
// target.classList.remove('resize-control--is-dragging');
// }}

// onMouseDown={(e) => {
// // set drag style via class
// const target = e.currentTarget;
// target.classList.add('resize-control--is-dragging');

// // function mousemove(event_mousemove: globalThis.MouseEvent) {
// // setState(
// // position === 'RIGHT'
// // ? window.innerWidth - event_mousemove.x
// // : // 51 is a bit of a magic number. It's the height of the CodeEditorHeader
// // window.innerHeight - event_mousemove.y - 51
// // );
// // }

// function mouseup() {
// // window.removeEventListener('mousemove', mousemove);
// window.removeEventListener('mouseup', mouseup);

// // revert to non-drag style
// target.classList.remove('resize-control--is-dragging');
// }

// // window.addEventListener('mousemove', mousemove);
// window.addEventListener('mouseup', mouseup);
// }}
/>
);
}
Loading
Loading