Compare commits
	
		
			2 Commits
		
	
	
		
			c64f93e912
			...
			e89a2eea20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e89a2eea20 | |||
| 55b7b6f206 | 
| @@ -1,5 +1,4 @@ | |||||||
| from datetime import datetime | from datetime import datetime | ||||||
| import numpy as np |  | ||||||
| import io | import io | ||||||
| import base64 | import base64 | ||||||
| from fastapi import APIRouter | from fastapi import APIRouter | ||||||
| @@ -81,19 +80,17 @@ class Params(BaseModel): | |||||||
|     font_size: int | None = Field(default=10, alias="fontSize") |     font_size: int | None = Field(default=10, alias="fontSize") | ||||||
|     arrow_size: int | None = Field(default=20, alias="arrowSize") |     arrow_size: int | None = Field(default=20, alias="arrowSize") | ||||||
|     edge_width: float | None = Field(default=1, alias="edgeWidth") |     edge_width: float | None = Field(default=1, alias="edgeWidth") | ||||||
|  |     distance: float | None = 0.2 | ||||||
|  |  | ||||||
|  |  | ||||||
| def sociogram_image(params: Params): | def sociogram_image(params: Params): | ||||||
|     print(params) |  | ||||||
|     plt.figure(figsize=(16, 10), facecolor="none") |     plt.figure(figsize=(16, 10), facecolor="none") | ||||||
|     ax = plt.gca() |     ax = plt.gca() | ||||||
|     ax.set_facecolor("none")  # Set the axis face color to none (transparent) |     ax.set_facecolor("none")  # Set the axis face color to none (transparent) | ||||||
|     ax.axis("off")  # Turn off axis ticks and frames |     ax.axis("off")  # Turn off axis ticks and frames | ||||||
|  |  | ||||||
|     G = sociogram_data() |     G = sociogram_data() | ||||||
|     pos = nx.spring_layout( |     pos = nx.spring_layout(G, scale=2, k=params.distance, iterations=50, seed=None) | ||||||
|         G, scale=2, k=1 / np.sqrt(G.number_of_edges()), iterations=50, seed=42 |  | ||||||
|     ) |  | ||||||
|     nx.draw_networkx_nodes( |     nx.draw_networkx_nodes( | ||||||
|         G, |         G, | ||||||
|         pos, |         pos, | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								main.py
									
									
									
									
									
								
							| @@ -10,6 +10,7 @@ from analysis import analysis_router | |||||||
|  |  | ||||||
|  |  | ||||||
| app = FastAPI(title="cutt") | app = FastAPI(title="cutt") | ||||||
|  | api_router = APIRouter(prefix="/api") | ||||||
| origins = [ | origins = [ | ||||||
|     "*", |     "*", | ||||||
|     "http://localhost", |     "http://localhost", | ||||||
| @@ -80,7 +81,16 @@ def submit_chemistry(chemistry: Chemistry): | |||||||
|         session.commit() |         session.commit() | ||||||
|  |  | ||||||
|  |  | ||||||
| app.include_router(player_router) | class SPAStaticFiles(StaticFiles): | ||||||
| app.include_router(team_router) |     async def get_response(self, path: str, scope): | ||||||
| app.include_router(analysis_router) |         response = await super().get_response(path, scope) | ||||||
| app.mount("/", StaticFiles(directory="dist", html=True), name="site") |         if response.status_code == 404: | ||||||
|  |             response = await super().get_response(".", scope) | ||||||
|  |         return response | ||||||
|  |  | ||||||
|  |  | ||||||
|  | api_router.include_router(player_router) | ||||||
|  | api_router.include_router(team_router) | ||||||
|  | api_router.include_router(analysis_router) | ||||||
|  | app.include_router(api_router) | ||||||
|  | app.mount("/", SPAStaticFiles(directory="dist", html=True), name="site") | ||||||
|   | |||||||
| @@ -1,20 +1,30 @@ | |||||||
| import { useEffect, useState } from "react"; | import { useEffect, useState } from "react"; | ||||||
| import { baseUrl } from "./api"; | import { baseUrl } from "./api"; | ||||||
|  |  | ||||||
|  | interface Prop { | ||||||
|  |   name: string; | ||||||
|  |   min: string; | ||||||
|  |   max: string; | ||||||
|  |   step: string; | ||||||
|  |   value: string; | ||||||
|  | } | ||||||
|  |  | ||||||
| interface Params { | interface Params { | ||||||
|   nodeSize: number; |   nodeSize: number; | ||||||
|   edgeWidth: number; |   edgeWidth: number; | ||||||
|   arrowSize: number; |   arrowSize: number; | ||||||
|   fontSize: number; |   fontSize: number; | ||||||
|  |   distance: number; | ||||||
| } | } | ||||||
|  |  | ||||||
| export default function Analysis() { | export default function Analysis() { | ||||||
|   const [image, setImage] = useState(""); |   const [image, setImage] = useState(""); | ||||||
|   const [params, setParams] = useState<Params>({ |   const [params, setParams] = useState<Params>({ | ||||||
|     nodeSize: 1600, |     nodeSize: 2000, | ||||||
|     edgeWidth: 1, |     edgeWidth: 1, | ||||||
|     arrowSize: 20, |     arrowSize: 20, | ||||||
|     fontSize: 10, |     fontSize: 10, | ||||||
|  |     distance: 0.2, | ||||||
|   }); |   }); | ||||||
|   const [showControlPanel, setShowControlPanel] = useState(false); |   const [showControlPanel, setShowControlPanel] = useState(false); | ||||||
|   const [loading, setLoading] = useState(false); |   const [loading, setLoading] = useState(false); | ||||||
| @@ -22,7 +32,7 @@ export default function Analysis() { | |||||||
|   // Function to generate and fetch the graph image |   // Function to generate and fetch the graph image | ||||||
|   async function loadImage() { |   async function loadImage() { | ||||||
|     setLoading(true); |     setLoading(true); | ||||||
|     await fetch(`${baseUrl}analysis/image`, { |     await fetch(`${baseUrl}api/analysis/image`, { | ||||||
|       method: "POST", |       method: "POST", | ||||||
|       headers: { |       headers: { | ||||||
|         "Content-Type": "application/json", |         "Content-Type": "application/json", | ||||||
| @@ -44,10 +54,23 @@ export default function Analysis() { | |||||||
|       <button onClick={() => setShowControlPanel(!showControlPanel)}> |       <button onClick={() => setShowControlPanel(!showControlPanel)}> | ||||||
|         Parameters {showControlPanel ? "⮝" : "⮟"} |         Parameters {showControlPanel ? "⮝" : "⮟"} | ||||||
|       </button> |       </button> | ||||||
|       {showControlPanel && ( |       <div id="control-panel" className={showControlPanel ? "opened" : "closed"}> | ||||||
|         <div id="control-panel"> |  | ||||||
|  |  | ||||||
|           <label>Node Size:</label> |         <div className="control"> | ||||||
|  |           <label>distance between nodes</label> | ||||||
|  |           <input | ||||||
|  |             type="range" | ||||||
|  |             min="0.01" | ||||||
|  |             max="3.001" | ||||||
|  |             step="0.05" | ||||||
|  |             value={params.distance} | ||||||
|  |             onChange={(evt) => setParams({ ...params, distance: Number(evt.target.value) })} | ||||||
|  |             onMouseUp={() => loadImage()} | ||||||
|  |           /> | ||||||
|  |           <span>{params.distance}</span></div> | ||||||
|  |  | ||||||
|  |         <div className="control"> | ||||||
|  |           <label>node size</label> | ||||||
|           <input |           <input | ||||||
|             type="range" |             type="range" | ||||||
|             min="500" |             min="500" | ||||||
| @@ -57,8 +80,10 @@ export default function Analysis() { | |||||||
|             onMouseUp={() => loadImage()} |             onMouseUp={() => loadImage()} | ||||||
|           /> |           /> | ||||||
|           <span>{params.nodeSize}</span> |           <span>{params.nodeSize}</span> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|           <label>Font Size:</label> |         <div className="control"> | ||||||
|  |           <label>font size</label> | ||||||
|           <input |           <input | ||||||
|             type="range" |             type="range" | ||||||
|             min="4" |             min="4" | ||||||
| @@ -68,8 +93,10 @@ export default function Analysis() { | |||||||
|             onMouseUp={() => loadImage()} |             onMouseUp={() => loadImage()} | ||||||
|           /> |           /> | ||||||
|           <span>{params.fontSize}</span> |           <span>{params.fontSize}</span> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|           <label>Edge Width:</label> |         <div className="control"> | ||||||
|  |           <label>edge width</label> | ||||||
|           <input |           <input | ||||||
|             type="range" |             type="range" | ||||||
|             min="1" |             min="1" | ||||||
| @@ -80,8 +107,10 @@ export default function Analysis() { | |||||||
|             onMouseUp={() => loadImage()} |             onMouseUp={() => loadImage()} | ||||||
|           /> |           /> | ||||||
|           <span>{params.edgeWidth}</span> |           <span>{params.edgeWidth}</span> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|           <label>Arrow Size:</label> |         <div className="control"> | ||||||
|  |           <label>arrow size</label> | ||||||
|           <input |           <input | ||||||
|             type="range" |             type="range" | ||||||
|             min="10" |             min="10" | ||||||
| @@ -91,9 +120,10 @@ export default function Analysis() { | |||||||
|             onMouseUp={() => loadImage()} |             onMouseUp={() => loadImage()} | ||||||
|           /> |           /> | ||||||
|           <span>{params.arrowSize}</span> |           <span>{params.arrowSize}</span> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|       </div> |       </div> | ||||||
|       )} |       <button onClick={() => loadImage()}>reload ↻</button> | ||||||
|       {loading ? ( |       {loading ? ( | ||||||
|         <span className="loader"></span> |         <span className="loader"></span> | ||||||
|       ) : ( |       ) : ( | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								src/App.css
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								src/App.css
									
									
									
									
									
								
							| @@ -60,15 +60,6 @@ h3 { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| #control-panel { |  | ||||||
|   display: flex; |  | ||||||
|   flex-direction: column; |  | ||||||
|  |  | ||||||
|   input { |  | ||||||
|     margin: auto |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .container { | .container { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-wrap: nowrap; |   flex-wrap: nowrap; | ||||||
| @@ -138,7 +129,43 @@ button { | |||||||
|   z-index: 1; |   z-index: 1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #control-panel { | ||||||
|  |   display: grid; | ||||||
|  |   overflow: hidden; | ||||||
|  |   margin: auto; | ||||||
|  |   gap: 16px; | ||||||
|  |   grid-template-columns: repeat(3, 1fr); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .opened { | ||||||
|  |   max-height: 100vw; | ||||||
|  |   transition: max-height 1s ease-in; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .closed { | ||||||
|  |   max-height: 0px; | ||||||
|  |   transition: max-height 0.5s ease-out; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .control { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   align-items: center; | ||||||
|  |   border: 1px solid #404040; | ||||||
|  |   padding: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @media only screen and (max-width: 1000px) { | ||||||
|  |   #control-panel { | ||||||
|  |     grid-template-columns: repeat(2, 1fr); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| @media only screen and (max-width: 768px) { | @media only screen and (max-width: 768px) { | ||||||
|  |   #control-panel { | ||||||
|  |     grid-template-columns: 1fr; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   .submit_text { |   .submit_text { | ||||||
|     display: none; |     display: none; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; | |||||||
| function App() { | function App() { | ||||||
|   //const [data, setData] = useState({ nodes: [], links: [] } as SociogramData); |   //const [data, setData] = useState({ nodes: [], links: [] } as SociogramData); | ||||||
|   //async function loadData() { |   //async function loadData() { | ||||||
|   //  await fetch(`${baseUrl}analysis/json`, { method: "GET" }).then(resp => resp.json() as unknown as SociogramData).then(json => { setData(json) }) |   //  await fetch(`${baseUrl}api/analysis/json`, { method: "GET" }).then(resp => resp.json() as unknown as SociogramData).then(json => { setData(json) }) | ||||||
|   //} |   //} | ||||||
|   //useEffect(() => { loadData() }, []) |   //useEffect(() => { loadData() }, []) | ||||||
|   // |   // | ||||||
| @@ -17,7 +17,7 @@ function App() { | |||||||
|       <Header /> |       <Header /> | ||||||
|       <Routes> |       <Routes> | ||||||
|         <Route index element={<Rankings />} /> |         <Route index element={<Rankings />} /> | ||||||
|         <Route path="analysis" element={<Analysis />} /> |         <Route path="/analysis" element={<Analysis />} /> | ||||||
|       </Routes> |       </Routes> | ||||||
|       <Footer /> |       <Footer /> | ||||||
|     </BrowserRouter> |     </BrowserRouter> | ||||||
|   | |||||||
| @@ -272,7 +272,7 @@ export default function Rankings() { | |||||||
|   const [openTab, setOpenTab] = useState("Chemistry"); |   const [openTab, setOpenTab] = useState("Chemistry"); | ||||||
|  |  | ||||||
|   async function loadPlayers() { |   async function loadPlayers() { | ||||||
|     const response = await fetch(`${baseUrl}player/list`, { |     const response = await fetch(`${baseUrl}api/player/list`, { | ||||||
|       method: "GET", |       method: "GET", | ||||||
|     }); |     }); | ||||||
|     const data = await response.json(); |     const data = await response.json(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user