Skip to content

Commit d18dfad

Browse files
authored
feat: add JS api (#13)
* feat: add JS api * fix: web app error
1 parent 0bc8fba commit d18dfad

File tree

4 files changed

+54
-18
lines changed

4 files changed

+54
-18
lines changed

setup.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ def read(*names, **kwargs):
4545

4646
setup(
4747
name="streamlit-g2",
48-
version="0.1.0",
48+
version="0.1.1",
4949
author="hustcc",
5050
author_email="[email protected]",
5151
description="Render G2 charts in Streamlit",
5252
long_description=read('readme.md'),
53-
long_description_content_type="text/plain",
53+
long_description_content_type="text/markdown",
5454
keywords=["antv", "g2", "streamlit-component", "streamlit-g2"],
5555
url="https://github.com/hustcc/streamlit-g2",
5656
packages=find_packages(),
@@ -60,6 +60,7 @@ def read(*names, **kwargs):
6060
install_requires=[
6161
"streamlit >= 0.63",
6262
"pandas>=1.0.0",
63+
"simplejson"
6364
],
6465
cmdclass={"upload": UploadCommand},
6566
)

streamlit_g2/__init__.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
import streamlit.components.v1 as components
3-
import spec
3+
from streamlit_g2.spec import JS, json_dump_options
44

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

7575
def st_g2(options, style=None, key=None):
@@ -106,8 +106,11 @@ def st_g2(options, style=None, key=None):
106106
"encode": {
107107
"x": "genre",
108108
"y": "sold",
109-
"color": "genre",
109+
"color": JS('''(d) => d.sold > 200 ? "red" : "green"'''),
110110
},
111+
"scale": {
112+
"color": { "type": "identity" }
113+
}
111114
},
112115
{
113116
"type": "line",

streamlit_g2/frontend/src/g2.tsx

+22-1
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,42 @@ import {
77
} from 'streamlit-component-lib';
88
import { Chart } from '@berryv/g2-react';
99

10+
const SEP = '!!-_-____-_-!!';
11+
1012
/**
1113
* G2 Theme follow streamlit.
1214
*/
1315
function getDefaultTheme(theme: Theme | undefined): 'dark' | 'light' {
1416
return theme?.base === 'dark' ? 'dark' : 'light';
1517
}
1618

19+
function processJSString(options: any): any {
20+
if (typeof options === 'string' && options.includes(SEP)) return eval(options.replaceAll('!!-_-____-_-!!', ''));
21+
if (Array.isArray(options)) {
22+
return options.map((o) => processJSString(o));
23+
}
24+
if (typeof options === 'object') {
25+
const o = { ...options };
26+
for (const key in options) {
27+
if (Object.prototype.hasOwnProperty.call(options, key)) {
28+
o[key] = processJSString(options[key])
29+
}
30+
}
31+
return o;
32+
}
33+
return options;
34+
}
35+
1736
const G2Component: React.FC<ComponentProps> = (props) => {
1837
const { theme, args } = props;
1938
const { style, options } = args;
2039

40+
const o = processJSString(options);
41+
2142
return (
2243
<Chart
2344
style={{width: '100%', height: 400, ...style}}
24-
options={{ theme: getDefaultTheme(theme), ...options }}
45+
options={{ theme: getDefaultTheme(theme), ...o }}
2546
onInit={() => Streamlit.setFrameHeight()}
2647
/>
2748
)

streamlit_g2/spec.py

+22-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1+
import datetime
2+
import re
3+
import simplejson
14
import pandas as pd
25

3-
# Loop to process dataFrame to data dict.
4-
def normalize_options(options):
5-
if isinstance(options, dict):
6-
for k, v in options.items():
7-
if k == "data" and isinstance(v, pd.DataFrame):
8-
options["data"] = v.to_dict(orient='records')
9-
else:
10-
normalize_options(v)
11-
elif isinstance(options, list):
12-
for v in options:
13-
normalize_options(v)
6+
# no JSON.stringigy in Python
7+
SEP = "!!-_-____-_-!!"
8+
9+
class JS:
10+
def __init__(self, js_code: str):
11+
self.js_code = "%s%s%s" % (SEP, js_code, SEP)
12+
13+
14+
def _json_dump_default(o: object):
15+
if isinstance(o, (datetime.date, datetime.datetime)):
16+
return o.isoformat()
17+
if isinstance(o, JS):
18+
return o.js_code
19+
if isinstance(o, pd.DataFrame):
20+
return o.to_dict(orient='records')
21+
return o
22+
23+
def json_dump_options(options: object):
24+
return simplejson.loads(simplejson.dumps(options, indent=2, default=_json_dump_default, ignore_nan=True))

0 commit comments

Comments
 (0)