Skip to content

Commit

Permalink
Added JS dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
isumitjha committed Nov 29, 2024
1 parent c03d08f commit a1d1fb1
Show file tree
Hide file tree
Showing 35 changed files with 5,603 additions and 0 deletions.
66 changes: 66 additions & 0 deletions web/js_dashboard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Dependencies
node_modules
.pnp
.pnp.js

# Testing
coverage

# Production
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
build/
develop-eggs/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
venv/
.venv/

# Backend
*.parquet
127 changes: 127 additions & 0 deletions web/js_dashboard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# OpenSciMetrics Javascript Dashboard

## Features

- 📊 Interactive data visualization of open science trends
- 📈 Time series analysis of open code and data practices
- 🔍 Advanced filtering capabilities
- 📱 Responsive design for all devices
- ⚡ Real-time data processing
- 🎯 Performance-optimized for large datasets

## Tech Stack

### Frontend
- React with TypeScript
- Recharts for data visualization
- Tailwind CSS for styling
- Axios for API communication
- Lucide React for icons

### Backend
- FastAPI (Python)
- Pandas for data processing
- Uvicorn ASGI server
- Parquet file handling

## Getting Started

### Prerequisites

- Python 3.8 or higher
- Node.js 16 or higher
- npm or yarn
- Virtual environment (recommended)

### Data Setup

1. Place your `matches.parquet` file in the `backend` directory:
```
backend/
├── matches.parquet # Place your Parquet file here
├── main.py
├── routers/
└── services/
```

### Backend Setup

1. Create and activate virtual environment:
```bash
python -m venv venv
source venv/bin/activate # Unix
.\venv\Scripts\activate # Windows
```

2. Install dependencies:
```bash
pip install fastapi uvicorn pandas pyarrow
```

3. Start the server:
```bash
uvicorn main:app --reload
```

The API will be available at `http://localhost:8000`

### Frontend Setup

1. Install dependencies:
```bash
npm install
```

2. Start development server:
```bash
npm run dev
```

The application will be available at `http://localhost:5173`

## Project Structure

```
.
├── backend/
│ ├── main.py # FastAPI application entry point
│ ├── routers/
│ │ └── metrics.py # API route definitions
│ └── services/
│ └── data_service.py # Data processing logic
├── src/
│ ├── components/ # React components
│ │ ├── charts/ # Data visualization components
│ │ ├── filters/ # Filter components
│ │ ├── layout/ # Layout components
│ │ └── metrics/ # Metric display components
│ ├── hooks/ # Custom React hooks
│ ├── services/ # API services
│ ├── types/ # TypeScript type definitions
│ └── utils/ # Utility functions
└── public/ # Static assets
```

## API Endpoints

- `GET /api/summary` - Get overall metrics summary
- `GET /api/timeseries` - Get time series data
- `GET /api/country-distribution` - Get data distribution by country
- `GET /api/journal-distribution` - Get data distribution by journal

## Development

### Code Style

- Frontend follows TypeScript best practices
- Backend uses Python type hints
- ESLint and Prettier for code formatting
- Pre-commit hooks for code quality

## Acknowledgments

- Data source: [OpenSciMetrics Dataset](https://github.com/nimh-dsst/osm)
- Built with [FastAPI](https://fastapi.tiangolo.com/) and [React](https://reactjs.org/)
- Visualization powered by [Recharts](https://recharts.org/)
15 changes: 15 additions & 0 deletions web/js_dashboard/backend/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from routers import metrics

app = FastAPI(title="OpenSciMetrics API")

app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

app.include_router(metrics.router, prefix="/api")
21 changes: 21 additions & 0 deletions web/js_dashboard/backend/routers/metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from fastapi import APIRouter
from services.data_service import DataService

router = APIRouter()
data_service = DataService()

@router.get("/summary")
async def get_summary():
return data_service.get_summary()

@router.get("/timeseries")
async def get_timeseries():
return data_service.get_timeseries()

@router.get("/country-distribution")
async def get_country_distribution():
return data_service.get_country_distribution()

@router.get("/journal-distribution")
async def get_journal_distribution():
return data_service.get_journal_distribution()
42 changes: 42 additions & 0 deletions web/js_dashboard/backend/services/data_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import pandas as pd
from typing import List, Dict

class DataService:
def __init__(self):
self.df = pd.read_parquet("matches.parquet")

def get_summary(self) -> Dict:
return {
"totalRecords": len(self.df),
"openCodePercentage": float(self.df['is_open_code'].mean() * 100),
"openDataPercentage": float(self.df['is_open_data'].mean() * 100),
"uniqueJournals": int(self.df['journal'].nunique()),
"uniqueCountries": int(self.df['affiliation_country'].nunique()),
}

def get_timeseries(self) -> List[Dict]:
yearly_stats = self.df.groupby('year').agg({
'is_open_code': 'mean',
'is_open_data': 'mean'
}).reset_index()

return [
{
"year": int(row['year']),
"openCode": round(float(row['is_open_code'] * 100), 2),
"openData": round(float(row['is_open_data'] * 100), 2)
}
for _, row in yearly_stats.iterrows()
]

def get_country_distribution(self) -> List[Dict]:
return (self.df['affiliation_country']
.value_counts()
.head(10)
.to_dict())

def get_journal_distribution(self) -> List[Dict]:
return (self.df['journal']
.value_counts()
.head(10)
.to_dict())
28 changes: 28 additions & 0 deletions web/js_dashboard/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';

export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
);
13 changes: 13 additions & 0 deletions web/js_dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JS Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading

0 comments on commit a1d1fb1

Please sign in to comment.