Skip to content

Commit

Permalink
[default-card][ui] vegachart component
Browse files Browse the repository at this point in the history
- Chart support within tables and anywhere across the page
- Chart mutation done based on spec/data changes.
  • Loading branch information
valayDave committed Nov 21, 2023
1 parent 0a3fbb9 commit 760b9b7
Show file tree
Hide file tree
Showing 10 changed files with 4,202 additions and 5,374 deletions.
8 changes: 4 additions & 4 deletions metaflow/plugins/cards/card_modules/bundle.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ html, body {
.embed .card_app {
min-height: var(--embed-card-min-height);
}
/* PrismJS 1.25.0
https://prismjs.com/download.html#themes=prism&languages=clike+python */
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
.mf-card * {
box-sizing: border-box;
}
Expand Down Expand Up @@ -152,10 +155,7 @@ html, body {
text-shadow: none;
user-select: auto;
}
/* PrismJS 1.25.0
https://prismjs.com/download.html#themes=prism&languages=clike+python */
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
aside.svelte-1okdv0e{display:none;line-height:2;text-align:left}@media(min-width: 60rem){aside.svelte-1okdv0e{display:flex;flex-direction:column;height:100vh;justify-content:space-between;padding:2.5rem 0 1.5rem 1.5rem;position:fixed;width:var(--aside-width)}}.embed aside{display:none}aside ul{list-style-type:none}aside a, aside button, aside a:visited{text-decoration:none;cursor:pointer;font-weight:700;color:var(--black)}aside a:hover, aside button:hover{text-decoration:underline}.logoContainer svg{width:100%;max-width:140px;margin-bottom:3.75rem;height:auto}.mainContainer.svelte-13ho8jo{max-width:110rem}main.svelte-13ho8jo{flex:0 1 auto;max-width:100rem;padding:1.5rem}@media(min-width: 60rem){main.svelte-13ho8jo{margin-left:var(--aside-width)}}.embed main{margin:0 auto}.modal.svelte-1hhf5ym{align-items:center;background:rgba(0, 0, 0, 0.5);bottom:0;cursor:pointer;display:flex;height:100%;justify-content:center;left:0;overflow:hidden;position:fixed;right:0;top:0;width:100%;z-index:100}.modalContainer > *{background-color:white;border-radius:5px;cursor:default;flex:0 1 auto;padding:1rem;position:relative}.modal img{max-height:80vh !important}.cancelButton.svelte-1hhf5ym{color:white;cursor:pointer;font-size:2rem;position:absolute;right:1rem;top:1rem}.cancelButton.svelte-1hhf5ym:hover{color:var(--blue)}.nav.svelte-1kdpgko.svelte-1kdpgko{border-radius:0 0 5px 0;display:none;margin:0;top:0}ul.navList.svelte-1kdpgko.svelte-1kdpgko{list-style-type:none}ul.navList.svelte-1kdpgko ul.svelte-1kdpgko{margin:0.5rem 1rem 2rem}.navList.svelte-1kdpgko li.svelte-1kdpgko{display:block;margin:0}.navItem.svelte-1kdpgko li.svelte-1kdpgko:hover{color:var(--blue)}.pageId.svelte-1kdpgko.svelte-1kdpgko{display:block;border-bottom:1px solid var(--grey);padding:0 0.5rem;margin-bottom:1rem}@media(min-width: 60rem){.nav.svelte-1kdpgko.svelte-1kdpgko{display:block}ul.navList.svelte-1kdpgko.svelte-1kdpgko{text-align:left}.navList.svelte-1kdpgko li.svelte-1kdpgko{display:block;margin:0.5rem 0}}.container.svelte-ubs992{width:100%;overflow:auto}table.svelte-ubs992{width:100%}header.svelte-1ugmt5d{margin-bottom:var(--component-spacer)}figure.svelte-1x96yvr{background:var(--lt-grey);padding:1rem;border-radius:5px;text-align:center;margin:0 auto var(--component-spacer)}@media(min-width: 60rem){figure.svelte-1x96yvr{margin-bottom:0}}img.svelte-1x96yvr{max-width:100%;max-height:500px}.label.svelte-1x96yvr{font-weight:bold;margin:0.5rem 0}.description.svelte-1x96yvr{font-size:0.9rem;font-style:italic;text-align:center;margin:0.5rem 0}.log.svelte-1jhmsu{background:var(--lt-grey) !important;font-size:0.9rem;padding:2rem}.page.svelte-v7ihqd:last-of-type{margin-bottom:var(--component-spacer)}.page:last-of-type section:last-of-type hr{display:none}.svelte-5d173i::-webkit-progress-bar,.svelte-5d173i::-moz-progress-bar{background-color:#326cde}table .container{background:transparent !important;font-size:10px !important;padding:0 !important}table progress{height:4px !important}.container.svelte-5d173i{display:flex;align-items:center;justify-content:center;font-size:12px;border-radius:3px;background:#edf5ff;padding:3rem}.inner.svelte-5d173i{max-width:410px;width:100%;text-align:center}.info.svelte-5d173i{display:flex;justify-content:space-between}table .info{text-align:left;flex-direction:column}label.svelte-5d173i{font-weight:bold}.labelValue.svelte-5d173i{border-left:1px solid rgba(0, 0, 0, 0.1);margin-left:0.25rem;padding-left:0.5rem}.details.svelte-5d173i{font-family:var(--mono-font);font-size:8px;color:#333433;line-height:18px;overflow:hidden;white-space:nowrap}progress.svelte-5d173i{width:100%;border:none;border-radius:5px;height:8px;background:white}.heading.svelte-17n0qr8{margin-bottom:1.5rem}.sectionItems.svelte-17n0qr8{display:block}.sectionItems .imageContainer{max-height:500px}.container.svelte-17n0qr8{scroll-margin:var(--component-spacer)}hr.svelte-17n0qr8{background:var(--grey);border:none;height:1px;margin:var(--component-spacer) 0;padding:0}@media(min-width: 60rem){.sectionItems.svelte-17n0qr8{display:grid;grid-gap:2rem}}.subtitle.svelte-lu9pnn{font-size:1rem;text-align:left}.title.svelte-117s0ws{text-align:left}.idCell.svelte-pt8vzv{font-weight:bold;text-align:right;background:var(--lt-grey);width:12%}.codeCell.svelte-pt8vzv{text-align:left;user-select:all}td.svelte-gl9h79{text-align:left}td.labelColumn.svelte-gl9h79{text-align:right;background-color:var(--lt-grey);font-weight:700;width:12%;white-space:nowrap}.tableContainer.svelte-q3hq57{overflow:auto}th.svelte-q3hq57{position:sticky;top:-1px;z-index:2;white-space:nowrap;background:var(--white)}:root {
aside.svelte-1okdv0e{display:none;line-height:2;text-align:left}@media(min-width: 60rem){aside.svelte-1okdv0e{display:flex;flex-direction:column;height:100vh;justify-content:space-between;padding:2.5rem 0 1.5rem 1.5rem;position:fixed;width:var(--aside-width)}}.embed aside{display:none}aside ul{list-style-type:none}aside a, aside button, aside a:visited{text-decoration:none;cursor:pointer;font-weight:700;color:var(--black)}aside a:hover, aside button:hover{text-decoration:underline}.logoContainer svg{width:100%;max-width:140px;margin-bottom:3.75rem;height:auto}.mainContainer.svelte-13ho8jo{max-width:110rem}main.svelte-13ho8jo{flex:0 1 auto;max-width:100rem;padding:1.5rem}@media(min-width: 60rem){main.svelte-13ho8jo{margin-left:var(--aside-width)}}.embed main{margin:0 auto}.modal.svelte-1hhf5ym{align-items:center;background:rgba(0, 0, 0, 0.5);bottom:0;cursor:pointer;display:flex;height:100%;justify-content:center;left:0;overflow:hidden;position:fixed;right:0;top:0;width:100%;z-index:100}.modalContainer > *{background-color:white;border-radius:5px;cursor:default;flex:0 1 auto;padding:1rem;position:relative}.modal img{max-height:80vh !important}.cancelButton.svelte-1hhf5ym{color:white;cursor:pointer;font-size:2rem;position:absolute;right:1rem;top:1rem}.cancelButton.svelte-1hhf5ym:hover{color:var(--blue)}.nav.svelte-1kdpgko.svelte-1kdpgko{border-radius:0 0 5px 0;display:none;margin:0;top:0}ul.navList.svelte-1kdpgko.svelte-1kdpgko{list-style-type:none}ul.navList.svelte-1kdpgko ul.svelte-1kdpgko{margin:0.5rem 1rem 2rem}.navList.svelte-1kdpgko li.svelte-1kdpgko{display:block;margin:0}.navItem.svelte-1kdpgko li.svelte-1kdpgko:hover{color:var(--blue)}.pageId.svelte-1kdpgko.svelte-1kdpgko{display:block;border-bottom:1px solid var(--grey);padding:0 0.5rem;margin-bottom:1rem}@media(min-width: 60rem){.nav.svelte-1kdpgko.svelte-1kdpgko{display:block}ul.navList.svelte-1kdpgko.svelte-1kdpgko{text-align:left}.navList.svelte-1kdpgko li.svelte-1kdpgko{display:block;margin:0.5rem 0}}.container.svelte-ubs992{width:100%;overflow:auto}table.svelte-ubs992{width:100%}header.svelte-1ugmt5d{margin-bottom:var(--component-spacer)}figure.svelte-1x96yvr{background:var(--lt-grey);padding:1rem;border-radius:5px;text-align:center;margin:0 auto var(--component-spacer)}@media(min-width: 60rem){figure.svelte-1x96yvr{margin-bottom:0}}img.svelte-1x96yvr{max-width:100%;max-height:500px}.label.svelte-1x96yvr{font-weight:bold;margin:0.5rem 0}.description.svelte-1x96yvr{font-size:0.9rem;font-style:italic;text-align:center;margin:0.5rem 0}.log.svelte-1jhmsu{background:var(--lt-grey) !important;font-size:0.9rem;padding:2rem}.page.svelte-v7ihqd:last-of-type{margin-bottom:var(--component-spacer)}.page:last-of-type section:last-of-type hr{display:none}.heading.svelte-17n0qr8{margin-bottom:1.5rem}.sectionItems.svelte-17n0qr8{display:block}.sectionItems .imageContainer{max-height:500px}.container.svelte-17n0qr8{scroll-margin:var(--component-spacer)}hr.svelte-17n0qr8{background:var(--grey);border:none;height:1px;margin:var(--component-spacer) 0;padding:0}@media(min-width: 60rem){.sectionItems.svelte-17n0qr8{display:grid;grid-gap:2rem}}.svelte-5d173i::-webkit-progress-bar,.svelte-5d173i::-moz-progress-bar{background-color:#326cde}table .container{background:transparent !important;font-size:10px !important;padding:0 !important}table progress{height:4px !important}.container.svelte-5d173i{display:flex;align-items:center;justify-content:center;font-size:12px;border-radius:3px;background:#edf5ff;padding:3rem}.inner.svelte-5d173i{max-width:410px;width:100%;text-align:center}.info.svelte-5d173i{display:flex;justify-content:space-between}table .info{text-align:left;flex-direction:column}label.svelte-5d173i{font-weight:bold}.labelValue.svelte-5d173i{border-left:1px solid rgba(0, 0, 0, 0.1);margin-left:0.25rem;padding-left:0.5rem}.details.svelte-5d173i{font-family:var(--mono-font);font-size:8px;color:#333433;line-height:18px;overflow:hidden;white-space:nowrap}progress.svelte-5d173i{width:100%;border:none;border-radius:5px;height:8px;background:white}.subtitle.svelte-lu9pnn{font-size:1rem;text-align:left}.title.svelte-117s0ws{text-align:left}.idCell.svelte-pt8vzv{font-weight:bold;text-align:right;background:var(--lt-grey);width:12%}.codeCell.svelte-pt8vzv{text-align:left;user-select:all}.tableContainer.svelte-q3hq57{overflow:auto}th.svelte-q3hq57{position:sticky;top:-1px;z-index:2;white-space:nowrap;background:var(--white)}td.svelte-gl9h79{text-align:left}td.labelColumn.svelte-gl9h79{text-align:right;background-color:var(--lt-grey);font-weight:700;width:12%;white-space:nowrap}:root {
--dag-border: #282828;
--dag-bg-static: var(--lt-grey);
--dag-bg-success: #a5d46a;
Expand Down
18 changes: 14 additions & 4 deletions metaflow/plugins/cards/card_modules/main.js

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion metaflow/plugins/cards/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@
"license": "UNLICENSED",
"dependencies": {
"@iconify/svelte": "^2.1.1",
"@rollup/plugin-json": "^6.0.1",
"chart.js": "^3.7.0",
"svelte-markdown": "^0.2.1"
"svelte-markdown": "^0.2.1",
"svelte-vega": "^2.1.0",
"vega": "^5.26.1",
"vega-lite": "5.16.3"
}
}
3 changes: 2 additions & 1 deletion metaflow/plugins/cards/ui/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import livereload from "rollup-plugin-livereload";
import { terser } from "rollup-plugin-terser";
import sveltePreprocess from "svelte-preprocess";
import typescript from "@rollup/plugin-typescript";
import json from "@rollup/plugin-json"
/**
* @NOTE: we are currently commenting out postcss,
* it will be added back in a separate PR
Expand Down Expand Up @@ -77,7 +78,7 @@ export default {
sourceMap: !production,
inlineSources: !production,
}),

json(),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import Table from "./table.svelte";
import Text from "./text.svelte";
import Title from "./title.svelte";
import VegaChart from "./vega-chart.svelte";
export let componentData: types.CardComponent;
Expand All @@ -36,6 +37,7 @@
table: Table,
text: Text,
title: Title,
vegaChart: VegaChart,
};
let component = typesMap?.[componentData.type];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Heading from "./heading.svelte";
import Image from "./image.svelte";
import LineChart from "./line-chart.svelte";
import VegaChart from "./vega-chart.svelte";
import Log from "./log.svelte";
import Markdown from "./markdown.svelte";
import Text from "./text.svelte";
Expand All @@ -27,6 +28,7 @@
markdown: Markdown,
progressBar: ProgressBar,
text: Text,
vegaChart: VegaChart,
};
const type = (componentData as types.CardComponent)?.type;
Expand Down
108 changes: 108 additions & 0 deletions metaflow/plugins/cards/ui/src/components/vega-chart.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<script lang="ts">
import type {VegaChartComponent} from "../types";
import { Vega } from "svelte-vega";
export let componentData: VegaChartComponent
$:({data, spec} = componentData)
</script>
{#if data && spec}
<Vega data={data} spec={spec} />
{:else}
<Vega spec={spec} />
{/if}


<!-- // For an example, see https://github.com/vega/svelte-vega/blob/main/packages/storybook/stories/spec1.ts -->
<!-- // $: const spec: VisualizationSpec; -->

<!-- // = {
// $schema: "https://vega.github.io/schema/vega/v5.json",
// width: 400,
// height: 200,
// padding: { left: 5, right: 5, top: 5, bottom: 5 },
// data: [
// {
// name: "table",
// values: [
// { category: "A", amount: 28 },
// { category: "B", amount: 55 },
// { category: "C", amount: 43 },
// { category: "D", amount: 91 },
// { category: "E", amount: 81 },
// { category: "F", amount: 53 },
// { category: "G", amount: 19 },
// { category: "H", amount: 87 },
// ],
// },
// ],
// signals: [
// {
// name: "tooltip",
// value: {},
// on: [
// { events: "rect:mouseover", update: "datum" },
// { events: "rect:mouseout", update: "{}" },
// ],
// },
// ],
// scales: [
// {
// name: "xscale",
// type: "band",
// domain: { data: "table", field: "category" },
// range: "width",
// },
// {
// name: "yscale",
// domain: { data: "table", field: "amount" },
// nice: true,
// range: "height",
// },
// ],
// axes: [
// { orient: "bottom", scale: "xscale" },
// { orient: "left", scale: "yscale" },
// ],
// marks: [
// {
// type: "rect",
// from: { data: "table" },
// encode: {
// update: {
// x: { scale: "xscale", field: "category", offset: 1 },
// width: { scale: "xscale", band: 1, offset: -1 },
// y: { scale: "yscale", field: "amount" },
// y2: { scale: "yscale", value: 0 },
// fill: { value: "steelblue" },
// },
// hover: {
// fill: { value: "red" },
// },
// },
// },
// {
// type: "text",
// encode: {
// enter: {
// align: { value: "center" },
// baseline: { value: "bottom" },
// fill: { value: "#333" },
// },
// update: {
// x: { scale: "xscale", signal: "tooltip.category", band: 0.5 },
// y: { scale: "yscale", signal: "tooltip.amount", offset: -2 },
// text: { signal: "tooltip.amount" },
// fillOpacity: [
// { test: "datum === tooltip", value: 0 },
// { value: 1 },
// ],
// },
// },
// },
// ],
// }; // any vega spec. -->
29 changes: 28 additions & 1 deletion metaflow/plugins/cards/ui/src/store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type * as types from "./types";
import type { VisualizationSpec } from "svelte-vega";
import { writable } from "svelte/store";
import type { Writable } from "svelte/store";

Expand Down Expand Up @@ -26,6 +27,24 @@ export const setCardDataFromWindow = ((window as any).metaflow_card_update = (
return true;
});

const mutateChartElement = (
chart: types.VegaChartComponent,
newChart: types.VegaChartComponent,
) => {
if (chart.data) {
chart.data = JSON.parse(JSON.stringify(newChart.data)) as Record<
string,
unknown
>;
}
const specHasChanged =
JSON.stringify(newChart.spec) === JSON.stringify(chart.spec);

if (specHasChanged) {
chart.spec = JSON.parse(JSON.stringify(newChart.spec)) as VisualizationSpec;
}
};

// NOTE: this function mutates the object! Be careful with it.
const findAndMutateTree = (
components: types.CardComponent[],
Expand All @@ -36,7 +55,15 @@ const findAndMutateTree = (
);

if (componentIndex > -1) {
Object.assign(components[componentIndex], newComponent);
if (components[componentIndex].type == "vegaChart") {
// if the component is a vegaChart, we need to merge the data
mutateChartElement(
components[componentIndex] as types.VegaChartComponent,
newComponent as types.VegaChartComponent,
);
} else {
Object.assign(components[componentIndex], newComponent);
}
} else {
// if the component has children, and nothing was found, run again with them
components.forEach((component) => {
Expand Down
Loading

0 comments on commit 760b9b7

Please sign in to comment.