Compare commits
15 Commits
8694d0d86c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ef3b9935a8 | |||
| 502121b536 | |||
| 4039a74fd7 | |||
|
|
b3358023f1 | ||
|
|
dc9c0d5e59 | ||
|
|
6bdb8b6174 | ||
|
|
97e628c4e1 | ||
|
|
b02c090d03 | ||
|
|
2dcb19c8ee | ||
|
|
2aae9ee455 | ||
|
|
0dcad337ed | ||
|
|
73c30d7f8e | ||
|
|
b2cff623b7 | ||
|
|
311180189f | ||
|
|
e1c6e4923c |
3
.env
Normal file
3
.env
Normal file
@@ -0,0 +1,3 @@
|
||||
GENERATE_SOURCEMAP=false
|
||||
REACT_APP_API_HOST="http://localhost:5000/test/"
|
||||
REACT_APP_API_DIR="/test/"
|
||||
2
.env.development
Normal file
2
.env.development
Normal file
@@ -0,0 +1,2 @@
|
||||
REACT_APP_API_HOST="http://localhost:3000/"
|
||||
REACT_APP_API_DIR="/"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,7 +7,7 @@
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
@@ -66,6 +66,7 @@ build/Release
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
build/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
4
public/.htaccess
Normal file
4
public/.htaccess
Normal file
@@ -0,0 +1,4 @@
|
||||
Options -MultiViews
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.html [QSA,L]
|
||||
@@ -17,13 +17,35 @@
|
||||
"fileName": "data.csv"
|
||||
},
|
||||
{
|
||||
"tabTitle": "Tab 4",
|
||||
"tabTitle": "Tab 6",
|
||||
"fileName": "data.csv"
|
||||
}
|
||||
],
|
||||
"chartList":[{
|
||||
"chartList": [{
|
||||
"chartTitle": "Status Plot",
|
||||
"fileName": "data.csv",
|
||||
"chartColumn": "status"
|
||||
},
|
||||
{
|
||||
"fileName": "data.csv",
|
||||
"chartColumn": "age"
|
||||
}, {
|
||||
"chartTitle": "Another Status Plot",
|
||||
"fileName": "data.csv",
|
||||
"chartColumn": "status"
|
||||
}, {
|
||||
"chartTitle": "Yet Another Status Plot",
|
||||
"fileName": "data.csv",
|
||||
"chartColumn": "status"
|
||||
}, {
|
||||
"fileName": "data.csv",
|
||||
"chartColumn": "status"
|
||||
}
|
||||
]
|
||||
],
|
||||
"colorMaps": {
|
||||
"status": {
|
||||
"complicated": "red",
|
||||
"single": "green"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/App.css
12
src/App.css
@@ -23,8 +23,16 @@
|
||||
}
|
||||
|
||||
.navbar-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
color: #3273dc;
|
||||
background-color: rgba(10, 10, 10, .1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* body {
|
||||
font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
} */
|
||||
|
||||
a,a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import 'bulma/css/bulma.css';
|
||||
import './App.css';
|
||||
import AppRoute from './RouteMenu';
|
||||
import RouteMenu from './RouteMenu';
|
||||
|
||||
class App extends React.Component {
|
||||
public render() {
|
||||
return (
|
||||
<div className="App">
|
||||
<AppRoute/>
|
||||
<RouteMenu />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
} from 'recharts';
|
||||
import * as React from 'react';
|
||||
import * as _ from 'lodash';
|
||||
import { Container, Title } from 'bloomer';
|
||||
import { Card, CardImage, CardHeader, CardHeaderTitle } from 'bloomer';
|
||||
const randomColor = require('randomcolor');
|
||||
import * as Papa from 'papaparse';
|
||||
|
||||
@@ -32,24 +32,49 @@ export class Chart extends React.Component<any, any> {
|
||||
const countMap = _.countBy(data, (o: any) => _.get<any>(o, this.props.chartColumn))
|
||||
const chartData = _.map(_.keys(countMap), (k: any) => {
|
||||
return {
|
||||
name: this.props.chartColumn + ' ' + k,
|
||||
name: k,
|
||||
value: countMap[k],
|
||||
}
|
||||
})
|
||||
// const RADIAN = Math.PI / 180;
|
||||
// const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, index }:any) => {
|
||||
// const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
|
||||
// const x = cx + radius * Math.cos(-midAngle * RADIAN);
|
||||
// const y = cy + radius * Math.sin(-midAngle * RADIAN);
|
||||
//
|
||||
// return (
|
||||
// <text x={x} y={y} fill="white" textAnchor='middle' dominantBaseline="central">
|
||||
// {chartData[index].name}
|
||||
// </text>
|
||||
// );
|
||||
// };
|
||||
const colorMap = this.props.colorMap;
|
||||
// const LabeText = (props: any) => {
|
||||
// return (<text>{props.name}</text>);
|
||||
// };
|
||||
return (
|
||||
<Container>
|
||||
<Title isSize={4}>Plotting {_.capitalize(this.props.chartColumn)}</Title>
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<PieChart>
|
||||
<Pie data={chartData} dataKey='value' outerRadius={100}>
|
||||
{
|
||||
chartData.map((_0, _1) => <Cell key={_1} fill={randomColor()} />)
|
||||
}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</Container>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardHeaderTitle className='is-centered'>
|
||||
{this.props.title}
|
||||
</CardHeaderTitle>
|
||||
</CardHeader>
|
||||
<CardImage>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<PieChart>
|
||||
<Pie data={chartData} dataKey='value' outerRadius={100} labelLine={true} label={true}>
|
||||
{
|
||||
chartData.map((_0, _1) => {
|
||||
const color = _.get(colorMap, _0.name, randomColor());
|
||||
return (<Cell key={_1} fill={color} />)
|
||||
})
|
||||
}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</CardImage>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,25 @@
|
||||
import * as React from 'react';
|
||||
import * as _ from 'lodash';
|
||||
import { Columns, Column } from 'bloomer';
|
||||
import * as local from 'localforage';
|
||||
import { Chart } from './Chart';
|
||||
|
||||
export default class ChartLoader extends React.Component<any, any> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
chartList: []
|
||||
}
|
||||
fetch('/config.json')
|
||||
.then((response) => response.text())
|
||||
.then((jsonStr) => {
|
||||
const jsonObj = JSON.parse(jsonStr);
|
||||
const chartList = _.get<any>(jsonObj, 'chartList', []);
|
||||
this.setState({ chartList });
|
||||
local.setItem('config', jsonObj).catch((err: any) => {
|
||||
console.error('error occurred when saving config', err);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('error occurred when loading config', e);
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
const colorMaps = this.props.colorMaps;
|
||||
return (
|
||||
<Columns>
|
||||
{this.state.chartList.map((o: any, i: number) =>
|
||||
<Column key={i} isSize={3}>
|
||||
<Chart csvFile={o.fileName} chartColumn={o.chartColumn} />
|
||||
</Column>
|
||||
)}
|
||||
<Columns isCentered={true} isMultiline={true} style={{
|
||||
backgroundColor:'#f0f0f0'
|
||||
}}>
|
||||
{this.props.chartList.map((o: any, i: number) => {
|
||||
const colorMap = colorMaps[o.chartColumn];
|
||||
return (
|
||||
<Column key={i} isSize={4}>
|
||||
<Chart
|
||||
csvFile={o.fileName} chartColumn={o.chartColumn}
|
||||
colorMap={colorMap} title={o.chartTitle ? o.chartTitle: o.chartColumn}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
})}
|
||||
</Columns>
|
||||
)
|
||||
}
|
||||
|
||||
2
src/Config.tsx
Normal file
2
src/Config.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export const apiHost = process.env.REACT_APP_API_HOST
|
||||
export const apiDir = process.env.REACT_APP_API_DIR
|
||||
@@ -1,34 +1,65 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Route,
|
||||
Route, Redirect,
|
||||
Link
|
||||
} from 'react-router-dom';
|
||||
import { Container, Navbar, NavbarItem, NavbarMenu, Section } from 'bloomer';
|
||||
import 'bulma/css/bulma.css';
|
||||
import * as local from 'localforage';
|
||||
import ServerTabLoader from './ServerTabLoader';
|
||||
import ChartLoader from './ChartLoader';
|
||||
import { apiDir } from './Config';
|
||||
|
||||
const navButton = (name:string,link:string) => (
|
||||
const navButton = (name: string, link: string) => (
|
||||
<Link to={link}>{name}</Link>
|
||||
);
|
||||
|
||||
const AppRoute = () => (
|
||||
<Router>
|
||||
<div>
|
||||
<Container isFluid={true} isMarginless={true} isFullWidth={true}>
|
||||
<Navbar style={{ border: 'solid 1px #00D1B2', margin: '0' }}>
|
||||
<NavbarMenu isActive={true}>
|
||||
<NavbarItem >{navButton('Charts','/')}</NavbarItem>
|
||||
<NavbarItem>{navButton('Tables','/tables')}</NavbarItem>
|
||||
</NavbarMenu>
|
||||
</Navbar>
|
||||
</Container>
|
||||
<Section isPaddingless={true}>
|
||||
<Route exact={true} path="/" component={ChartLoader} />
|
||||
<Route path="/tables" component={ServerTabLoader} />
|
||||
</Section>
|
||||
</div>
|
||||
</Router>
|
||||
)
|
||||
export default AppRoute
|
||||
const RedirectCharts = () => (<Redirect to={apiDir+'charts'} />);
|
||||
|
||||
class RouteMenu extends React.Component {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tabList: [],
|
||||
chartList: [],
|
||||
colorMaps: {}
|
||||
}
|
||||
fetch(apiDir+'config.json')
|
||||
.then((response) => response.text())
|
||||
.then((jsonStr) => {
|
||||
const jsonObj = JSON.parse(jsonStr);
|
||||
this.setState({ ...jsonObj });
|
||||
local.setItem('config', jsonObj).catch((err: any) => {
|
||||
console.error('error occurred when saving config', err);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('error occurred when loading config', e);
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
const ConfiguredChart = () => (<ChartLoader {...this.state} />);
|
||||
const ConfiguredServerTab = () => (<ServerTabLoader {...this.state} />);
|
||||
return (
|
||||
<Router>
|
||||
<div>
|
||||
<Container isFluid={true} isMarginless={true} isFullWidth={true}>
|
||||
<Navbar style={{ backgroundColor: '#00D1B2' }}>
|
||||
<NavbarMenu isActive={true}>
|
||||
<NavbarItem >{navButton('Charts', apiDir + 'charts')}</NavbarItem>
|
||||
<NavbarItem>{navButton('Tables', apiDir + 'tables')}</NavbarItem>
|
||||
</NavbarMenu>
|
||||
</Navbar>
|
||||
</Container>
|
||||
<Section isPaddingless={true}>
|
||||
<Route exact={true} path={apiDir} component={RedirectCharts} />
|
||||
<Route path={apiDir + "charts"} component={ConfiguredChart} />
|
||||
<Route path={apiDir + "tables"} component={ConfiguredServerTab} />
|
||||
</Section>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default RouteMenu
|
||||
|
||||
@@ -1,75 +1,29 @@
|
||||
import * as React from 'react';
|
||||
import * as _ from 'lodash';
|
||||
import ServerTable from './ServerTable';
|
||||
import { Tabs, TabList, TabPanel, Tab } from 'react-tabs';
|
||||
import { Hero, HeroBody } from 'bloomer';
|
||||
import * as local from 'localforage';
|
||||
import './Tabs.css';
|
||||
|
||||
|
||||
export default class ServerTabLoader extends React.Component<any, any> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tabList: []
|
||||
}
|
||||
fetch('/config.json')
|
||||
.then((response) => response.text())
|
||||
.then((jsonStr) => {
|
||||
const jsonObj = JSON.parse(jsonStr);
|
||||
const tabList = _.get<any>(jsonObj, 'tabList', []);
|
||||
this.setState({ tabList });
|
||||
local.setItem('config', jsonObj).catch((err: any) => {
|
||||
console.error('error occurred when saving config', err);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('error occurred when loading config', e);
|
||||
});
|
||||
// TODO: enable localstore to load config
|
||||
// const onLocal = (config: any) => {
|
||||
// if (config) {
|
||||
// const tabList = _.get<any>(config, 'tabList', []);
|
||||
// this.setState({ tabList });
|
||||
// } else {
|
||||
// fetch('/config.json')
|
||||
// .then((response) => response.text())
|
||||
// .then((jsonStr) => {
|
||||
// const jsonObj = JSON.parse(jsonStr);
|
||||
// const tabList = _.get<any>(jsonObj, 'tabList', []);
|
||||
// this.setState({ tabList });
|
||||
// local.setItem('config', jsonObj).then((val: any) => {
|
||||
// console.log('saved to localstore ',val);
|
||||
// }).catch((err:any) => {
|
||||
// console.error('error occurred when saving config', err);
|
||||
// });
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// this.setState({ message: e.stack, showMessage: true });
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
// local.getItem('config').then(onLocal).catch((err: any) => {
|
||||
// // This code runs if there were any errors
|
||||
// console.error('error occurred when getting config from localstore', err);
|
||||
// });
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Hero isColor='light' isSize='large' isFullWidth={true} isFullHeight={true}>
|
||||
<HeroBody isPaddingless={true} isDisplay='block'>
|
||||
<Tabs forceRenderTabPanel={true}>
|
||||
<TabList>
|
||||
{this.state.tabList.map((o: any, i: number) =>
|
||||
{this.props.tabList.map((o: any, i: number) =>
|
||||
(<Tab key={i}>{o.tabTitle}</Tab>)
|
||||
)}
|
||||
</TabList>
|
||||
{this.state.tabList.map((o: any, i: number) =>
|
||||
(<TabPanel key={i}>
|
||||
<ServerTable csvFile={o.fileName} colorColumn={o.colorColumn} />
|
||||
</TabPanel>
|
||||
)
|
||||
{this.props.tabList.map((o: any, i: number) => {
|
||||
const colorMap = this.props.colorMaps[o.colorColumn];
|
||||
return (
|
||||
<TabPanel key={i}>
|
||||
<ServerTable csvFile={o.fileName} colorColumn={o.colorColumn} colorMap={colorMap} />
|
||||
</TabPanel>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</Tabs>
|
||||
</HeroBody>
|
||||
|
||||
@@ -2,7 +2,8 @@ import * as React from 'react';
|
||||
import { Tips } from "./Utils";
|
||||
import * as _ from 'lodash';
|
||||
import { Button } from 'bloomer';
|
||||
const randomColor = require('randomcolor');
|
||||
// const randomColor = require('randomcolor');
|
||||
import { apiDir } from './Config';
|
||||
const reactcsv = require('react-csv');
|
||||
const CSVLink = reactcsv.CSVLink;
|
||||
import * as Papa from 'papaparse';
|
||||
@@ -19,15 +20,15 @@ export default class ServerTable extends React.Component<any, any> {
|
||||
}
|
||||
const loaded = (results: any) => {
|
||||
const local = this;
|
||||
const colsUnique = _.keys(_.groupBy(results.data, (o: any) => _.get<any>(o, this.props.colorColumn)));
|
||||
const colColorMap = _.zipObject(colsUnique, _.map(colsUnique, () => randomColor()));
|
||||
// const colsUnique = _.keys(_.groupBy(results.data, (o: any) => _.get<any>(o, this.props.colorColumn)));
|
||||
const colColorMap = this.props.colorMap; // _.zipObject(colsUnique, _.map(colsUnique, () => randomColor()));
|
||||
local.setState({
|
||||
headers: results.meta.fields,
|
||||
data: results.data,
|
||||
colColorMap
|
||||
});
|
||||
}
|
||||
Papa.parse(this.props.csvFile, {
|
||||
Papa.parse(apiDir + this.props.csvFile, {
|
||||
header: true,
|
||||
download: true,
|
||||
skipEmptyLines: true,
|
||||
@@ -40,8 +41,8 @@ export default class ServerTable extends React.Component<any, any> {
|
||||
const data = this.state.data;
|
||||
const headers = this.state.headers;
|
||||
const filterPart = (filter: any, row: any) =>
|
||||
String(row[filter.id]).startsWith(filter.value) ||
|
||||
String(row[filter.id]).endsWith(filter.value)
|
||||
String(_.toLower(row[filter.id])).startsWith(_.toLower(filter.value)) ||
|
||||
String(_.toLower(row[filter.id])).endsWith(_.toLower(filter.value))
|
||||
const headerGen = (headerName: string) => {
|
||||
// const title = headerName
|
||||
// .replace(/([A-Z])/g, ' $1')
|
||||
@@ -61,13 +62,18 @@ export default class ServerTable extends React.Component<any, any> {
|
||||
return {};
|
||||
}
|
||||
const colValue = _.get<any>(rowInfo.row, colorCol, '');
|
||||
const colColor = _.get<any>(colColorMap, colValue, 'green');
|
||||
const colColor = _.get<any>(colColorMap, colValue, 'white');
|
||||
return {
|
||||
style: {
|
||||
background: colColor
|
||||
}
|
||||
};
|
||||
};
|
||||
const colStyle = () => ({
|
||||
style: {
|
||||
textAlign: 'left'
|
||||
}
|
||||
});
|
||||
const headerCols = headers.map(headerGen);
|
||||
return (
|
||||
<ReactTable
|
||||
@@ -79,6 +85,7 @@ export default class ServerTable extends React.Component<any, any> {
|
||||
defaultPageSize={100}
|
||||
className="-striped -highlight"
|
||||
getTrProps={rowStyle}
|
||||
getTdProps={colStyle}
|
||||
style={{
|
||||
height: "75vh"
|
||||
}}
|
||||
@@ -88,7 +95,6 @@ export default class ServerTable extends React.Component<any, any> {
|
||||
return (
|
||||
<div>
|
||||
{makeTable()}
|
||||
{/* <Chart data={lastFiltered} /> */}
|
||||
<Button>
|
||||
<CSVLink data={lastFiltered} filename={"data.csv"}>
|
||||
Download CSV
|
||||
|
||||
@@ -2,10 +2,10 @@ import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
import registerServiceWorker from './registerServiceWorker';
|
||||
// import registerServiceWorker from './registerServiceWorker';
|
||||
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
registerServiceWorker();
|
||||
// registerServiceWorker();
|
||||
|
||||
Reference in New Issue
Block a user