diff --git a/src/App.css b/src/DatasetteVega.css similarity index 100% rename from src/App.css rename to src/DatasetteVega.css diff --git a/src/App.js b/src/DatasetteVega.js similarity index 54% rename from src/App.js rename to src/DatasetteVega.js index 1f9dd53..9fb3946 100644 --- a/src/App.js +++ b/src/DatasetteVega.js @@ -1,12 +1,12 @@ import React, { Component } from 'react'; import vegaEmbed from 'vega-embed'; -import './App.css'; +import './DatasetteVega.css'; const serialize = obj => Object.keys(obj).map( key => obj[key] ? `g.${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}` : '' ).join('&'); -const unserialize = s => { +const unserialize = (s, prefix) => { if (s && s[0] === '#') { s = s.slice(1); } @@ -14,19 +14,20 @@ const unserialize = s => { return {}; } var obj = {}; - s.split('&').forEach(bit => { + s.split('&').filter(bit => bit.slice(0, prefix.length + 1) === `{prefix).`).forEach(bit => { let pair = bit.split('='); obj[decodeURIComponent(pair[0]).replace(/^g\./, '')] = decodeURIComponent(pair[1]); }); return obj; }; -class App extends Component { +class DatasetteVega extends Component { state = { + columns: this.props.columns || [], mark: "bar", - x_column: this.props.columns[0], + x_column: null, x_type: "ordinal", - y_column: this.props.columns[1], + y_column: null, y_type: "quantitative", color_column: "", size_column: "" @@ -53,16 +54,61 @@ class App extends Component { this.renderGraph(); }); } + jsonUrl() { + let url = this.props.base_url; + if (/\?/.exec(url)) { + url += '&'; + } else { + url += '?'; + } + url += '_shape=array'; + return url; + } componentDidMount() { window.onpopstate = this.onPopStateChange.bind(this); - this.renderGraph(); + // Load the columns + let url = this.jsonUrl(); + if (this.props.columns) { + return; + } + fetch(url).then(r => r.json()).then(data => { + if (data.length > 1) { + // Set columns to first item's keys + const columns = Object.keys(data[0]); + console.log(columns); + this.setState({ + columns: columns, + x_column: columns[0], + y_column: columns[1], + }, this.renderGraph); + } + }); + } + serializeState() { + return serialize((({ + mark, + x_column, + x_type, + y_column, + y_type, + color_column, + size_column + }) => ({ + mark, + x_column, + x_type, + y_column, + y_type, + color_column, + size_column + }))(this.state)); } onPopStateChange(ev) { window.lastPopEv = ev; - let expected = '#' + serialize(this.state); + const expected = '#' + this.serializeState(); if (expected !== document.location.hash) { this.setState( - unserialize(document.location.hash), this.renderGraph.bind(this) + unserialize(document.location.hash, 'g'), this.renderGraph.bind(this) ); } } @@ -83,14 +129,15 @@ class App extends Component { } const spec = { data: { - url: this.props.json_url + url: this.jsonUrl() }, mark: this.state.mark, encoding: encoding } - if (spec.mark) { + console.log('spec= ', spec); + if (spec.mark && spec.encoding.x.field && spec.encoding.y.field) { vegaEmbed("#vis", spec, {theme: 'quartz', tooltip: true}); - document.location.hash = '#' + serialize(this.state); + document.location.hash = '#' + this.serializeState() this.setState({spec: spec}); } } @@ -105,52 +152,45 @@ class App extends Component { } render() { const onChangeSelect = this.onChangeSelect.bind(this); - if (!this.state.mark) { - return null; - } + const columns = this.state.columns; + console.log(columns); return ( -
+ (columns.length > 1) ?
- Mark: onChangeSelect("mark", ev)}> {this.markOptions.map(option => )} - +
- X column: -
-
- X type: onChangeSelect("x_column", ev)}> + {columns.map(column => )} + +
-
- Y column: +
+ Y type:
- Color column: onChangeSelect("color_column", ev)}> - {this.props.columns.map(column => )} - -
-
- Size column: +
-
+ : null ); } } -export default App; +export default DatasetteVega; diff --git a/src/App.test.js b/src/DatasetteVega.test.js similarity index 58% rename from src/App.test.js rename to src/DatasetteVega.test.js index a754b20..d94e3ee 100644 --- a/src/App.test.js +++ b/src/DatasetteVega.test.js @@ -1,9 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import App from './App'; +import DatasetteVega from './DatasetteVega'; it('renders without crashing', () => { const div = document.createElement('div'); - ReactDOM.render(, div); + ReactDOM.render(, div); ReactDOM.unmountComponentAtNode(div); }); diff --git a/src/index.css b/src/index.css index b4cc725..cbabe2a 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,14 @@ body { margin: 0; - padding: 0; + padding: 1em; font-family: sans-serif; } + + +form div { + margin-bottom: 0.5em +} + +label { + padding-right: 0.5em; +} diff --git a/src/index.js b/src/index.js index 3410be8..6137330 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; -import App from './App'; +import DatasetteVega from './DatasetteVega'; ReactDOM.render( /*, document.getElementById('root') );