mirror of https://github.com/aziis98/ro-vis
initial commit
commit
be437c2858
@ -0,0 +1,15 @@
|
||||
# Local files
|
||||
.env
|
||||
*.local*
|
||||
|
||||
# NodeJS
|
||||
node_modules/
|
||||
|
||||
# Binaries
|
||||
bin/
|
||||
.out/
|
||||
out/
|
||||
dist/
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source "https://rubygems.org"
|
||||
|
||||
# gem "rails"
|
||||
@ -0,0 +1,12 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.17
|
||||
@ -0,0 +1,50 @@
|
||||
# Ricerca Operativa
|
||||
|
||||
Un piccolo sito con alcuni degli algoritmi presentati nel corso di Ricerca Operativa. Gli algoritmi sono scritti in Ruby e
|
||||
eseguiti nel browser con WASM (uso il Ruby perché è l'unico linguaggio con i numeri razionali e buon supporto per WASM).
|
||||
|
||||
## Algoritmi
|
||||
|
||||
### Programmazione Lineare
|
||||
|
||||
- [ ] Metodo del simplesso
|
||||
|
||||
- [ ] Metodo del simplesso (con i conti)
|
||||
|
||||
- [ ] Metodo del simplesso (con disegnini)
|
||||
|
||||
- [ ] Metodo del simplesso duale
|
||||
|
||||
- [ ] Metodo del simplesso duale (con i conti)
|
||||
|
||||
- [ ] Metodo del simplesso duale (con disegnini)
|
||||
|
||||
### Programmazione Lineare Intera
|
||||
|
||||
- [ ] Branch and Bound
|
||||
|
||||
- [ ] Branch and Cut
|
||||
|
||||
- [ ] Branch and Price
|
||||
|
||||
### Programmazione Dinamica
|
||||
|
||||
- [ ] Algoritmo di Bellman
|
||||
|
||||
- [ ] Algoritmo di Dijkstra
|
||||
|
||||
- [ ] Algoritmo di Floyd
|
||||
|
||||
### Altri
|
||||
|
||||
- [ ] Algoritmo di Kruskal
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<meta property="og:title" content="Visualizzazioni di Ricerca Operativa" />
|
||||
<meta property="og:description" content="Visualizzazioni di Ricerca Operativa" />
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
<title>Visualizzazioni di Ricerca Operativa</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="./src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "ricerca-operativa",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.9.0",
|
||||
"@types/bun": "latest",
|
||||
"@types/katex": "^0.16.7",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"vite": "^5.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^5.0.20",
|
||||
"@ruby/3.3-wasm-wasi": "^2.6.2",
|
||||
"@ruby/wasm-wasi": "^2.6.2",
|
||||
"katex": "^0.16.11",
|
||||
"lodash": "^4.17.21",
|
||||
"vite-plugin-wasm": "^3.3.0"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import { Steps } from '../../components/Steps.jsx'
|
||||
import { evalRuby } from '../../ruby.js'
|
||||
import algorithmCode from './algorithm.rb?raw'
|
||||
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
|
||||
export const metadata = {
|
||||
group: 'Programmazione Lineare',
|
||||
title: 'Simplesso Primale Algebrico',
|
||||
description: 'Algoritmo principale della programmazione lineare',
|
||||
}
|
||||
|
||||
export const View = ({}) => {
|
||||
const [steps, setSteps] = useState([])
|
||||
|
||||
useEffect(async () => {
|
||||
const { outputs } = await evalRuby(algorithmCode)
|
||||
setSteps(outputs)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Simplesso Primale Algebrico</h1>
|
||||
<p>TODO</p>
|
||||
|
||||
<Steps steps={steps} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
require "json"
|
||||
require "js"
|
||||
|
||||
def print_step(label, state)
|
||||
puts "Ruby: #{label} #{state}"
|
||||
JS.global[:rubyOutputs].push({
|
||||
label: label,
|
||||
state: state
|
||||
})
|
||||
end
|
||||
|
||||
def run(config)
|
||||
i = config["i"] || 1
|
||||
j = config["j"] || 1
|
||||
n = config["n"] || 10
|
||||
|
||||
print_step "Initial", { i: i, j: j, n: n }
|
||||
|
||||
n.times do
|
||||
m = i + j
|
||||
i = j
|
||||
j = m
|
||||
|
||||
print_step "Step", { i: i, j: j }
|
||||
end
|
||||
|
||||
return i
|
||||
end
|
||||
|
||||
run({ "n" => 10 })
|
||||
@ -0,0 +1,16 @@
|
||||
import 'katex/dist/katex.min.css'
|
||||
import katex from 'katex'
|
||||
|
||||
export const KaTeX = ({ source }) => {
|
||||
console.log(source)
|
||||
|
||||
return (
|
||||
<div
|
||||
class="katex"
|
||||
ref={el => {
|
||||
if (!el) return
|
||||
katex.render(source.toString(), el, { throwOnError: false })
|
||||
}}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import { KaTeX } from './KaTeX.jsx'
|
||||
|
||||
export const Steps = ({ steps }) => {
|
||||
return (
|
||||
<div class="steps">
|
||||
{steps.map((step, i) => (
|
||||
<div class="step">
|
||||
<div class="label">{step.label}</div>
|
||||
<div class="state">
|
||||
{Object.entries(step.state).map(([key, value]) => (
|
||||
<KaTeX source={`${key} = ${value}`} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
import './style.css'
|
||||
import '@fontsource/inter/latin.css'
|
||||
|
||||
import _ from 'lodash'
|
||||
|
||||
import { render } from 'preact'
|
||||
import { useState } from 'preact/hooks'
|
||||
|
||||
const ViewRegistry = Object.fromEntries(
|
||||
Object.entries(
|
||||
import.meta.glob('./algorithms/*/View.jsx', {
|
||||
eager: true,
|
||||
})
|
||||
/* fix for broken syntax highlighting */
|
||||
).map(([path, module]) => {
|
||||
const [id] = path.match(/(?<=\/)[^/]*(?=\/View\.jsx$)/)
|
||||
return [id, { id, ...module }]
|
||||
})
|
||||
)
|
||||
|
||||
const NewAlgorithmBox = ({ title, description, onClick }) => (
|
||||
<div class="algorithm-box" onClick={onClick}>
|
||||
<h3>{title}</h3>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
const AlgorithmChooserView = ({ setCurrentView }) => {
|
||||
const sections = _.groupBy(ViewRegistry, 'metadata.group')
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Algoritmi</h1>
|
||||
{Object.entries(sections).map(([group, algorithms]) => (
|
||||
<section>
|
||||
<h2>{group}</h2>
|
||||
<div class="boxes">
|
||||
{algorithms.map(({ id, metadata }) => (
|
||||
<NewAlgorithmBox
|
||||
title={metadata.title}
|
||||
description={metadata.description}
|
||||
onClick={() => setCurrentView(id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
<section>
|
||||
<h2>Flussi su Grafi</h2>
|
||||
<div class="boxes">
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Flussi su Grafi</h2>
|
||||
<div class="boxes">
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Flussi su Grafi</h2>
|
||||
<div class="boxes">
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
<NewAlgorithmBox title="TODO" description="lorem ipsum dolor sit amet" />
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Main = ({}) => {
|
||||
const [currentView, setCurrentView] = useState(null)
|
||||
|
||||
if (!currentView) {
|
||||
return <AlgorithmChooserView setCurrentView={setCurrentView} />
|
||||
}
|
||||
|
||||
const View = ViewRegistry[currentView].View
|
||||
|
||||
return <View />
|
||||
}
|
||||
|
||||
const App = ({}) => {
|
||||
return (
|
||||
<>
|
||||
<header>Visualizzazioni di Ricerca Operativa</header>
|
||||
<aside>
|
||||
<h2>History</h2>
|
||||
</aside>
|
||||
<main>
|
||||
<Main />
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
render(<App />, document.body)
|
||||
@ -0,0 +1,26 @@
|
||||
import rubyWasmModuleUrl from '@ruby/3.3-wasm-wasi/dist/ruby+stdlib.wasm?url'
|
||||
import { DefaultRubyVM } from '@ruby/wasm-wasi/dist/browser'
|
||||
|
||||
window.rubyOutputs = []
|
||||
let vmInstance = null
|
||||
|
||||
async function preloadRuby() {
|
||||
if (vmInstance) return
|
||||
|
||||
const response = await fetch(rubyWasmModuleUrl)
|
||||
const module = await WebAssembly.compileStreaming(response)
|
||||
const { vm } = await DefaultRubyVM(module)
|
||||
vmInstance = vm
|
||||
}
|
||||
|
||||
export async function evalRuby(code) {
|
||||
await preloadRuby()
|
||||
|
||||
window.rubyOutputs = []
|
||||
const result = vmInstance.eval(code)
|
||||
|
||||
return {
|
||||
result,
|
||||
outputs: window.rubyOutputs,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,176 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
font-family: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Components */
|
||||
|
||||
.boxes {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
align-items: start;
|
||||
|
||||
.algorithm-box {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
gap: 0.25rem;
|
||||
padding: 1rem;
|
||||
border-radius: 0.75rem;
|
||||
|
||||
background: #fff;
|
||||
border: 2px solid #333;
|
||||
|
||||
width: 10rem;
|
||||
min-height: calc(10rem * 3 / 4);
|
||||
|
||||
p {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: #f4f4f4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.steps {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
place-self: center;
|
||||
|
||||
gap: 1rem;
|
||||
|
||||
.step {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
border-radius: 0.75rem;
|
||||
|
||||
background: #fff;
|
||||
border: 2px solid #333;
|
||||
|
||||
min-width: 20rem;
|
||||
|
||||
& > .label {
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
& > .state {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Structure */
|
||||
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-columns: 20% 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
|
||||
background: #f0f0f0;
|
||||
|
||||
& > header,
|
||||
aside,
|
||||
main,
|
||||
footer {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
& > header {
|
||||
height: 3rem;
|
||||
|
||||
grid-column: span 2;
|
||||
|
||||
border-bottom: 2px solid #333;
|
||||
background: #fff;
|
||||
|
||||
font-weight: 300;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
& > aside {
|
||||
border-right: 2px solid #333;
|
||||
background: #e0e0e0;
|
||||
|
||||
position: sticky;
|
||||
height: 100vh;
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
& > main {
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
|
||||
padding: 6rem 1rem;
|
||||
gap: 1.5rem;
|
||||
|
||||
place-content: center;
|
||||
align-content: start;
|
||||
|
||||
overflow: auto;
|
||||
|
||||
& > section {
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
import wasm from 'vite-plugin-wasm'
|
||||
import preactPlugin from '@preact/preset-vite'
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outDir: 'out',
|
||||
emptyOutDir: true,
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
plugins: [preactPlugin(), wasm()],
|
||||
})
|
||||
Loading…
Reference in New Issue