Skip to content

Commit

Permalink
feat: add JS api (#13)
Browse files Browse the repository at this point in the history
* feat: add JS api

* fix: web app error
  • Loading branch information
hustcc authored Jan 2, 2024
1 parent 0bc8fba commit d18dfad
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 18 deletions.
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ def read(*names, **kwargs):

setup(
name="streamlit-g2",
version="0.1.0",
version="0.1.1",
author="hustcc",
author_email="i@hust.cc",
description="Render G2 charts in Streamlit",
long_description=read('readme.md'),
long_description_content_type="text/plain",
long_description_content_type="text/markdown",
keywords=["antv", "g2", "streamlit-component", "streamlit-g2"],
url="https://github.com/hustcc/streamlit-g2",
packages=find_packages(),
Expand All @@ -60,6 +60,7 @@ def read(*names, **kwargs):
install_requires=[
"streamlit >= 0.63",
"pandas>=1.0.0",
"simplejson"
],
cmdclass={"upload": UploadCommand},
)
11 changes: 7 additions & 4 deletions streamlit_g2/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
import streamlit.components.v1 as components
import spec
from streamlit_g2.spec import JS, json_dump_options

# Create a _RELEASE constant. We'll set this to False while we're developing
# the component, and True when we're ready to package and distribute it.
Expand Down Expand Up @@ -68,8 +68,8 @@ def g2(options, style=None, key=None):
# "default" is a special argument that specifies the initial return
# value of the component before the user has interacted with it.
# Loop to change pd.DataFrame to JSON Object.
spec.normalize_options(options)
component_value = _component_func(options=options, style=style, key=key)
o = json_dump_options(options)
component_value = _component_func(options=o, style=style, key=key)
return component_value

def st_g2(options, style=None, key=None):
Expand Down Expand Up @@ -106,8 +106,11 @@ def st_g2(options, style=None, key=None):
"encode": {
"x": "genre",
"y": "sold",
"color": "genre",
"color": JS('''(d) => d.sold > 200 ? "red" : "green"'''),
},
"scale": {
"color": { "type": "identity" }
}
},
{
"type": "line",
Expand Down
23 changes: 22 additions & 1 deletion streamlit_g2/frontend/src/g2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,42 @@ import {
} from 'streamlit-component-lib';
import { Chart } from '@berryv/g2-react';

const SEP = '!!-_-____-_-!!';

/**
* G2 Theme follow streamlit.
*/
function getDefaultTheme(theme: Theme | undefined): 'dark' | 'light' {
return theme?.base === 'dark' ? 'dark' : 'light';
}

function processJSString(options: any): any {
if (typeof options === 'string' && options.includes(SEP)) return eval(options.replaceAll('!!-_-____-_-!!', ''));
if (Array.isArray(options)) {
return options.map((o) => processJSString(o));
}
if (typeof options === 'object') {
const o = { ...options };
for (const key in options) {
if (Object.prototype.hasOwnProperty.call(options, key)) {
o[key] = processJSString(options[key])
}
}
return o;
}
return options;
}

const G2Component: React.FC<ComponentProps> = (props) => {
const { theme, args } = props;
const { style, options } = args;

const o = processJSString(options);

return (
<Chart
style={{width: '100%', height: 400, ...style}}
options={{ theme: getDefaultTheme(theme), ...options }}
options={{ theme: getDefaultTheme(theme), ...o }}
onInit={() => Streamlit.setFrameHeight()}
/>
)
Expand Down
33 changes: 22 additions & 11 deletions streamlit_g2/spec.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import datetime
import re
import simplejson
import pandas as pd

# Loop to process dataFrame to data dict.
def normalize_options(options):
if isinstance(options, dict):
for k, v in options.items():
if k == "data" and isinstance(v, pd.DataFrame):
options["data"] = v.to_dict(orient='records')
else:
normalize_options(v)
elif isinstance(options, list):
for v in options:
normalize_options(v)
# no JSON.stringigy in Python
SEP = "!!-_-____-_-!!"

class JS:
def __init__(self, js_code: str):
self.js_code = "%s%s%s" % (SEP, js_code, SEP)


def _json_dump_default(o: object):
if isinstance(o, (datetime.date, datetime.datetime)):
return o.isoformat()
if isinstance(o, JS):
return o.js_code
if isinstance(o, pd.DataFrame):
return o.to_dict(orient='records')
return o

def json_dump_options(options: object):
return simplejson.loads(simplejson.dumps(options, indent=2, default=_json_dump_default, ignore_nan=True))

0 comments on commit d18dfad

Please sign in to comment.