Compare commits

...

15 Commits

Author SHA1 Message Date
ef3b9935a8 Update '.gitignore' 2018-08-04 21:13:14 +00:00
502121b536 Apache htaccess redirection 2018-08-04 21:12:20 +00:00
4039a74fd7 Update '.env' 2018-08-04 21:11:01 +00:00
Malar Kannan
b3358023f1 static serving under path 2018-08-03 21:57:25 +05:30
Malar Kannan
dc9c0d5e59 added configurable root directory in .env* files 2018-08-03 00:11:20 +05:30
Malar Kannan
6bdb8b6174 menu aesthetics 2018-07-02 00:43:06 +05:30
Malar Kannan
97e628c4e1 added plot titles to config 2018-07-02 00:06:04 +05:30
Malar Kannan
b02c090d03 add multiple lines if number of charts are more 2018-07-02 00:01:28 +05:30
Malar Kannan
2dcb19c8ee added card for charts with borders 2018-07-01 22:57:37 +05:30
Malar Kannan
2aae9ee455 centered charts 2018-07-01 22:50:23 +05:30
Malar Kannan
0dcad337ed 1. fixed hover issue
2. search is case insensitive on tables
3. text aligned left
2018-07-01 22:46:47 +05:30
Malar Kannan
73c30d7f8e made config load at root and updated color loading logic 2018-07-01 22:11:00 +05:30
Malar Kannan
b2cff623b7 configurable color map for rows 2018-07-01 21:25:23 +05:30
Malar Kannan
311180189f removed line from chart 2018-07-01 21:12:54 +05:30
Malar Kannan
e1c6e4923c added static labels on chart 2018-07-01 21:01:27 +05:30
14 changed files with 184 additions and 138 deletions

3
.env Normal file
View 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
View File

@@ -0,0 +1,2 @@
REACT_APP_API_HOST="http://localhost:3000/"
REACT_APP_API_DIR="/"

3
.gitignore vendored
View File

@@ -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
View File

@@ -0,0 +1,4 @@
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]

View File

@@ -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"
}
}
}

View File

@@ -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 {

View File

@@ -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>
);
}

View File

@@ -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>
)
}
}

View File

@@ -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
View File

@@ -0,0 +1,2 @@
export const apiHost = process.env.REACT_APP_API_HOST
export const apiDir = process.env.REACT_APP_API_DIR

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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();