feat(frontend): add vega-lite component
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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&ixlib=rb-1.2.1&auto=format&fit=crop&w=1024&h=1280&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&ixlib=rb-1.2.1&auto=format&fit=crop&w=1024&h=1280&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
|
||||
|
||||
Reference in New Issue
Block a user