diff --git a/src/algorithms/flussi-su-grafi/View.jsx b/src/algorithms/flussi-su-grafi/View.jsx
new file mode 100644
index 0000000..a232f72
--- /dev/null
+++ b/src/algorithms/flussi-su-grafi/View.jsx
@@ -0,0 +1,59 @@
+// import { evalRuby } from '../../ruby.js'
+// import algorithmCode from './algorithm.rb?raw'
+
+import { useEffect, useState } from 'preact/hooks'
+import { GraphInput, useGraph } from '../../components/GraphInput.jsx'
+
+const [id1, id2, id3] = Array(3)
+ .fill(null)
+ .map(() => crypto.randomUUID(9).split('-')[0])
+
+export const metadata = {
+ group: '02 - Flussi su Grafi',
+ title: 'Flussi su Grafi',
+ description: 'Algoritmo per Flussi su Grafi tramite PL',
+}
+
+export const View = ({}) => {
+ const [graph, setGraph] = useGraph({
+ nodes: [
+ {
+ id: id1,
+ label: '1',
+ x: 100 + Math.random() * 500,
+ y: 100 + Math.random() * 300,
+ },
+ {
+ id: id2,
+ label: '2',
+ x: 100 + Math.random() * 500,
+ y: 100 + Math.random() * 300,
+ },
+ {
+ id: id3,
+ label: '3',
+ x: 100 + Math.random() * 500,
+ y: 100 + Math.random() * 300,
+ },
+ ],
+ edges: [
+ { from: id1, to: id2, label: 'a' },
+ { from: id1, to: id3, label: 'b' },
+ ],
+ })
+
+ return (
+
+
Flussi su Grafi
+
Input
+
+
+ Doppio click per aggiungere un nodo o modificarne uno esistente. Trascina per
+ spostare i nodi. Inizia a trascinare da un nodo tenendo Ctrl premuto per creare un
+ arco.
+
+
Svolgimento
+
Output
+
+ )
+}
diff --git a/src/algorithms/simplesso-primale-algebrico/View.jsx b/src/algorithms/simplesso-primale-algebrico/View.jsx
index 0c5ee2c..cac7261 100644
--- a/src/algorithms/simplesso-primale-algebrico/View.jsx
+++ b/src/algorithms/simplesso-primale-algebrico/View.jsx
@@ -5,7 +5,7 @@ import algorithmCode from './algorithm.rb?raw'
import { useEffect, useState } from 'preact/hooks'
export const metadata = {
- group: 'Programmazione Lineare',
+ group: '01 - Programmazione Lineare',
title: 'Simplesso Primale Algebrico',
description: 'Algoritmo principale della programmazione lineare',
}
@@ -21,8 +21,6 @@ export const View = ({}) => {
return (
<>
Simplesso Primale Algebrico
- TODO
-
>
)
diff --git a/src/algorithms/simplesso-primale-algebrico/algorithm.rb b/src/algorithms/simplesso-primale-algebrico/algorithm.rb
index 4ffc412..f1f816c 100644
--- a/src/algorithms/simplesso-primale-algebrico/algorithm.rb
+++ b/src/algorithms/simplesso-primale-algebrico/algorithm.rb
@@ -77,12 +77,11 @@ def run(config)
print_card "Initial" do
math <<-LATEX
- \\begin{aligned}
- c &= #{c.to_latex} \\\\
- A &= #{mat.to_latex} \\\\
- b &= #{b.to_latex} \\\\
- \\mathcal B &= \\{ #{basis.map { |i| i + 1 }.join(", ")} \\}
- \\end{aligned}
+ \\begin{array}{rlcl}
+ c &= #{c.to_matrix.transpose.to_latex}^T & & \\\\[5pt]
+ A &= #{mat.to_latex} & \\leq & #{b.to_latex} = b \\\\[28pt]
+ \\mathcal B &= \\{ #{basis.map { |i| i + 1 }.join(", ")} \\} &&
+ \\end{array}
LATEX
end
@@ -131,7 +130,6 @@ def run(config)
\\end{aligned}
LATEX
end
-
break
end
@@ -150,7 +148,7 @@ def run(config)
LATEX
end
- u = Vector.basis(size: basis.size, index: h)
+ u = Vector.basis(size: basis.size, index: basis_inv[h])
xi = -mat_basis_inv * u
@@ -172,7 +170,7 @@ def run(config)
print_card "Step" do
math <<-LATEX
\\begin{aligned}
- N &= \\{ #{basis.map { |i| i + 1 }.join(", ")} \\} \\setminus \\{ #{h + 1} \\} \\\\
+ N &= \\{ #{(0...mat.row_count).to_a.map { |i| i + 1 }.join(", ")} \\} \\setminus \\{ #{h + 1} \\} \\\\
A_N &= #{mat_basis_op.to_latex} \\\\
d &= A_N \\xi = #{mat_basis_op.to_latex} #{xi.to_latex} = #{d.to_latex}
\\end{aligned}
@@ -180,8 +178,44 @@ def run(config)
end
if d.all? { |d| d <= 0 }
+ print_card "Step" do
+ text "Problem is unbounded."
+ math <<-LATEX
+ \\begin{aligned}
+ A_N \\xi = #{d.to_latex} \\leq 0
+ \\end{aligned}
+ LATEX
+ end
break
end
+
+ # k := \min \left\{ \arg\min_{i \in N, A_i \xi > 0} \frac{b_i - A_i \bar{x}}{A_i \xi} \right\}
+
+ k = (0...mat.row_count).to_a
+ .select { |i| !basis.include?(i) && mat.row(i).inner_product(xi) > 0 }
+ .min_by { |i| (b[i] - mat.row(i).inner_product(x_bar)) / mat.row(i).inner_product(xi) }
+
+ print_card "Step" do
+ math <<-LATEX
+ \\begin{aligned}
+ N &= \\{ #{(0...mat.row_count).to_a.map { |i| i + 1 }.join(", ")} \\} \\setminus \\{ #{h + 1} \\} \\\\
+ k &= \\min \\left\\{ i \\in N : A_i \\xi > 0 \\right\\} = #{k + 1}
+ \\end{aligned}
+ LATEX
+ end
+
+ p basis
+ basis = basis - [h] + [k]
+ p basis
+
+ print_card "Step" do
+ text "Update basis."
+ math <<-LATEX
+ \\begin{aligned}
+ \\mathcal B &= \\{ #{basis.map { |i| i + 1 }.join(", ")} \\}
+ \\end{aligned}
+ LATEX
+ end
end
end
diff --git a/src/components/GraphInput.jsx b/src/components/GraphInput.jsx
new file mode 100644
index 0000000..16e011f
--- /dev/null
+++ b/src/components/GraphInput.jsx
@@ -0,0 +1,348 @@
+import _ from 'lodash'
+import { useState, useEffect } from 'preact/hooks'
+
+export const useGraph = (initialGraph = { nodes: [], edges: [] }) => {
+ return useState(initialGraph)
+}
+
+export const GraphInput = ({ graph, setGraph }) => {
+ const dict = _.keyBy(graph.nodes, 'id')
+
+ const lines = graph.edges.map(({ from, to, label }) => {
+ return shrinkLine({
+ from: dict[from],
+ to: dict[to],
+ midpoint: computeMidpoint(dict[from], dict[to]),
+ label,
+ })
+ })
+
+ const [interacting, setInteracting] = useState(false)
+ // console.log(interacting)
+
+ const onMouseMove = e => {
+ if (interacting && interacting.type === 'drag') {
+ const deltaX = e.x - interacting.initialDragPos.x
+ const deltaY = e.y - interacting.initialDragPos.y
+
+ setGraph(g => {
+ const newNodes = [...g.nodes]
+ newNodes[interacting.index] = {
+ ...g.nodes[interacting.index],
+ x: interacting.initialPos.x + deltaX,
+ y: interacting.initialPos.y + deltaY,
+ }
+
+ return {
+ ...g,
+ nodes: newNodes,
+ }
+ })
+ }
+ if (interacting && interacting.type === 'arrow') {
+ setInteracting(i => ({
+ ...i,
+ x: i.initialPos.x + e.x - i.initialDragPos.x,
+ y: i.initialPos.y + e.y - i.initialDragPos.y,
+ }))
+ }
+ }
+
+ const addNode = (x, y) => {
+ setGraph(g => ({
+ ...g,
+ nodes: [
+ ...g.nodes,
+ {
+ id: crypto.randomUUID(9).split('-')[0],
+ label: '?',
+ x,
+ y,
+ },
+ ],
+ }))
+ }
+
+ return (
+
+ )
+}
+
+//
+// Utils
+//
+
+const shrinkLine = (line, offset = 28) => {
+ const dx = line.to.x - line.from.x
+ const dy = line.to.y - line.from.y
+
+ const length = Math.sqrt(dx * dx + dy * dy)
+ const ratio = offset / length
+
+ const newStart = {
+ x: line.from.x + dx * ratio,
+ y: line.from.y + dy * ratio,
+ }
+
+ const newEnd = {
+ x: line.to.x - dx * ratio,
+ y: line.to.y - dy * ratio,
+ }
+
+ return { ...line, from: newStart, to: newEnd }
+}
+
+const computeMidpoint = (start, end) => {
+ return {
+ x: (start.x + end.x) / 2,
+ y: (start.y + end.y) / 2,
+ }
+}
+
+const useCtrlClick = () => {
+ const [isCtrlClick, setIsCtrlClick] = useState(false)
+
+ useEffect(() => {
+ const handleKeydown = event => {
+ setIsCtrlClick(event.ctrlKey)
+ }
+
+ document.addEventListener('keydown', handleKeydown)
+
+ return () => {
+ document.removeEventListener('keydown', handleKeydown)
+ }
+ }, [])
+
+ return isCtrlClick
+}
+
+const useShiftClick = () => {
+ const [isShiftClick, setIsShiftClick] = useState(false)
+
+ useEffect(() => {
+ const handleKeydown = event => {
+ setIsShiftClick(event.shiftKey)
+ }
+
+ document.addEventListener('keydown', handleKeydown)
+
+ return () => {
+ document.removeEventListener('keydown', handleKeydown)
+ }
+ }, [])
+
+ return isShiftClick
+}
diff --git a/src/main.jsx b/src/main.jsx
index c330965..699d66a 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -31,20 +31,22 @@ const AlgorithmChooserView = ({ setCurrentView }) => {
return (
<>
Algoritmi
- {Object.entries(sections).map(([group, algorithms]) => (
-
- {group}
-
- {algorithms.map(({ id, metadata }) => (
- setCurrentView(id)}
- />
- ))}
-
-
- ))}
+ {Object.entries(sections)
+ .toSorted((a, b) => a[0].localeCompare(b[0]))
+ .map(([group, algorithms]) => (
+
+ {group}
+
+ {algorithms.map(({ id, metadata }) => (
+ setCurrentView(id)}
+ />
+ ))}
+
+
+ ))}
Flussi su Grafi
diff --git a/src/style.css b/src/style.css
index d7b3c63..2ee53a7 100644
--- a/src/style.css
+++ b/src/style.css
@@ -29,6 +29,16 @@ img {
/* Typography */
+.content {
+ display: block;
+ margin: 0 auto;
+ max-width: 100ch;
+
+ > * + * {
+ margin-top: 1rem;
+ }
+}
+
h1,
h2,
h3,
@@ -54,10 +64,10 @@ h6 {
gap: 0.25rem;
padding: 1rem;
- border-radius: 0.75rem;
background: #fff;
border: 2px solid #888;
+ border-radius: 0.75rem;
width: 10rem;
min-height: calc(10rem * 3 / 4);
@@ -91,7 +101,7 @@ h6 {
border-radius: 0.75rem;
background: #fff;
- border: 2px solid #333;
+ border: 2px solid #888;
min-width: 20rem;
@@ -100,10 +110,88 @@ h6 {
font-size: 15px;
}
- & > .state {
+ & > .content {
display: grid;
grid-template-columns: 1fr;
gap: 0.5rem;
+
+ font-size: 16px;
+ }
+ }
+}
+
+.graph-input {
+ width: 100%;
+ height: 30rem;
+
+ background: #fff;
+ border: 2px solid #333;
+ border-radius: 0.75rem;
+
+ position: relative;
+
+ overflow: hidden;
+
+ > * {
+ inset: 0;
+ position: absolute;
+ pointer-events: none;
+
+ input[type='text'] {
+ max-width: 5rem;
+ }
+ }
+
+ > .edge-labels {
+ .edge-label {
+ position: absolute;
+
+ z-index: 10;
+
+ left: calc(var(--x) * 1px);
+ top: calc(var(--y) * 1px);
+
+ transform: translate(-50%, -50%);
+
+ background: #fff;
+ border: 2px solid #333;
+ border-radius: 0.25rem;
+ padding: 0.25rem;
+
+ cursor: pointer;
+
+ pointer-events: all;
+ }
+ }
+
+ > .nodes {
+ > .node {
+ position: absolute;
+ z-index: 10;
+
+ left: calc(var(--x) * 1px);
+ top: calc(var(--y) * 1px);
+
+ transform: translate(-50%, -50%);
+
+ display: grid;
+ place-content: center;
+
+ width: 2.5rem;
+ height: 2.5rem;
+
+ background: #f0f0f0;
+ border: 2px solid #333;
+ border-radius: 1.25rem;
+
+ pointer-events: all;
+
+ cursor: move;
+
+ &.targeted {
+ color: #fff;
+ background: green;
+ }
}
}
}
@@ -118,6 +206,7 @@ body {
font-family: 'Inter', sans-serif;
font-weight: 400;
font-size: 16px;
+ line-height: 1.5;
background: #f0f0f0;