feat(frontend): add vega-lite component

This commit is contained in:
Ruben Fiszel
2023-01-18 14:57:11 +01:00
parent 173093a403
commit bd79938bed
8 changed files with 175 additions and 31 deletions
@@ -5,8 +5,6 @@
export let id: string
export let componentInput: AppInput | undefined
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['result', 'loading']
@@ -24,17 +22,15 @@
bind:clientHeight={h}
bind:clientWidth={w}
>
<RunnableWrapper flexWrap bind:componentInput {id} bind:result>
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
{#key result}
<iframe
frameborder="0"
style="height: {h}px; width: {w}px"
class="p-0"
title="sandbox"
srcdoc={result ? '<scr' + `ipt src="/tailwind.css"></script>` + result : 'No html'}
/>
{/key}
</AlignWrapper>
<RunnableWrapper autoRefresh flexWrap bind:componentInput {id} bind:result>
{#key result}
<iframe
frameborder="0"
style="height: {h}px; width: {w}px"
class="p-0"
title="sandbox"
srcdoc={result ? '<scr' + `ipt src="/tailwind.css"></script>` + result : 'No html'}
/>
{/key}
</RunnableWrapper>
</div>
@@ -0,0 +1,58 @@
<script lang="ts">
import { parse } from 'path'
import { onMount } from 'svelte'
import type { AppInput } from '../../inputType'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['result', 'loading']
let result: object | undefined = undefined
let divEl: HTMLDivElement | null = null
let vegaEmbed
onMount(async () => {
if (divEl) {
//@ts-ignore
await import('https://cdn.jsdelivr.net/npm/vega@5.22.1')
//@ts-ignore
await import('https://cdn.jsdelivr.net/npm/vega-lite@5.6.0')
//@ts-ignore
await import('https://cdn.jsdelivr.net/npm/vega-embed@6.21.0')
vegaEmbed = window['vegaEmbed']
}
})
let h: number | undefined = undefined
let w: number | undefined = undefined
let canvas = false
$: vegaEmbed &&
result &&
divEl &&
h &&
w &&
vegaEmbed(
divEl,
{ ...result, ...{ width: w - 100 } },
{
mode: 'vega-lite',
renderer: canvas ? 'canvas' : 'svg',
height: h - 75,
width: w - 75
}
)
</script>
<InputValue {id} input={configuration.canvas} bind:value={canvas} />
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
<RunnableWrapper flexWrap bind:componentInput {id} bind:result>
<div on:pointerdown bind:this={divEl} />
</RunnableWrapper>
</div>
@@ -19,6 +19,7 @@
import AppTimeseries from '../components/dataDisplay/AppTimeseries.svelte'
import AppHtml from '../components/dataDisplay/AppHtml.svelte'
import AppSliderInputs from '../components/numberInputs/AppSliderInputs.svelte'
import VegaLiteHtml from '../components/dataDisplay/VegaLiteHtml.svelte'
export let component: AppComponent
export let selected: boolean
@@ -78,6 +79,12 @@
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'vegalitecomponent'}
<VegaLiteHtml
{...component}
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'scatterchartcomponent'}
<AppScatterChart
{...component}
@@ -38,7 +38,8 @@
'barchartcomponent',
'piechartcomponent',
'displaycomponent',
'scatterchartcomponent'
'scatterchartcomponent',
'vegalitecomponent'
],
'3:10-6:10': ['tablecomponent']
}
@@ -240,6 +240,41 @@ const display: ComponentSet = {
configuration: {},
card: false
},
{
softWrap: false,
id: 'vegalitecomponent',
type: 'vegalitecomponent',
componentInput: {
type: 'static',
fieldType: 'object',
value: {
data: {
values: [
{ a: 'A', b: 28 },
{ a: 'B', b: 55 },
{ a: 'C', b: 43 },
{ a: 'D', b: 91 },
]
},
mark: 'bar',
encoding: {
x: { field: 'a', type: 'ordinal' },
y: { field: 'b', type: 'quantitative' }
}
}
},
configuration: {
canvas: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false,
tooltip: "use the canvas renderer instead of the svg one for more interactive plots"
}
},
card: false
},
{
softWrap: true,
horizontalAlignment: 'left',
+57 -16
View File
@@ -7,7 +7,7 @@ export function defaultCode(component: string, language: string): string | undef
const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'python3', string>>> = {
tablecomponent: {
deno:
`export async function main() {
`export async function main() {
return [
{
"id": 1,
@@ -22,7 +22,7 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
]
}`,
python3:
`def main():
`def main():
return [
{
"id": 1,
@@ -38,16 +38,16 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
},
textcomponent: {
deno:
`export async function main() {
`export async function main() {
return "foo"
}`,
python3:
`def main():
`def main():
return "foo"`,
},
barchartcomponent: {
deno:
`export async function main() {
`export async function main() {
return {
"data": [
25,
@@ -62,7 +62,7 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
}
}`,
python3:
`def main():
`def main():
return {
"data": [
25,
@@ -78,20 +78,20 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
},
displaycomponent: {
deno:
`export async function main() {
`export async function main() {
return {
"foo": 42
}
}`,
python3:
`def main():
`def main():
return {
"foo": 42
}`,
},
htmlcomponent: {
deno:
`export async function main() {
`export async function main() {
return \`<img
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80"
>
@@ -100,17 +100,58 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
</h1>\`
}`,
python3:
`def main():
`def main():
return '''<img
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80"
>
<h1 class="absolute top-4 left-2 text-white">
Hello world
</h1>'''`,
},
vegalitecomponent: {
deno:
`
export async function main() {
return {
data: {
values: [
{ a: "A", b: 28 },
{ a: "B", b: 55 },
{ a: "C", b: 43 },
{ a: "D", b: 91 },
],
},
mark: "bar",
encoding: {
x: { field: "a", type: "ordinal" },
y: { field: "b", type: "quantitative" },
},
};
}
`,
python3:
`
def main():
return {
"data": {
"values": [
{ "a": "A", "b": 28 },
{ "a": "B", "b": 55 },
{ "a": "C", "b": 43 },
{ "a": "D", "b": 91 },
],
},
"mark": "bar",
"encoding": {
"x": { "field": "a", "type": "ordinal" },
"y": { "field": "b", "type": "quantitative" },
},
}
`
},
piechartcomponent: {
deno:
`export async function main() {
`export async function main() {
return {
"data": [
25,
@@ -125,7 +166,7 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
}
}`,
python3:
`def main():
`def main():
return {
"data": [
25,
@@ -141,7 +182,7 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
},
scatterchartcomponent: {
deno:
`export async function main() {
`export async function main() {
return [
{
"label": "foo",
@@ -164,7 +205,7 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
]
}`,
python3:
`def main():
`def main():
return [
{
"label": "foo",
@@ -188,7 +229,7 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
},
timeseriescomponent: {
deno:
`export async function main() {
`export async function main() {
return [
{
"label": "foo",
@@ -229,7 +270,7 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
]
}`,
python3:
`def main():
`def main():
return [
{
"label": "foo",
@@ -24,6 +24,7 @@ export type DateInputComponent = BaseComponent<'dateinputcomponent'>
export type NumberInputComponent = BaseComponent<'numberinputcomponent'>
export type SliderComponent = BaseComponent<'slidercomponent'>
export type HtmlComponent = BaseComponent<'htmlcomponent'>
export type VegaLiteComponent = BaseComponent<'vegalitecomponent'>
export type TimeseriesComponent = BaseComponent<'timeseriescomponent'>
export type ButtonComponent = BaseComponent<'buttoncomponent'> & {
recomputeIds: string[] | undefined
@@ -97,6 +98,7 @@ export type AppComponent = BaseAppComponent &
| CheckboxComponent
| RadioComponent
| FormComponent
| VegaLiteComponent
)
export type ComponentSet = {
@@ -108,6 +108,10 @@ export const displayData: Record<AppComponent['type'], { name: string; icon: any
name: 'Html',
icon: Code2
},
vegalitecomponent: {
name: 'Vega Lite',
icon: PieChart
},
timeseriescomponent: {
name: 'Timeseries',
icon: GripHorizontal