summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2020-06-07 16:51:27 +0200
committerJules Laplace <julescarbon@gmail.com>2020-06-07 16:51:27 +0200
commit9041401b73f93228cc9b5183a9ef30e5f7f171a6 (patch)
treeaad1d009a537aaab4bd3f923718b5d1e030cf356
parent3550f0ee37ee12179404f721b5a55be37992603f (diff)
drawing graphs with backlinks
-rw-r--r--cli/app/sql/models/graph.py2
-rw-r--r--cli/app/sql/models/page.py6
-rw-r--r--frontend/util/index.js1
-rw-r--r--frontend/views/graph/components/graph.editor.js108
-rw-r--r--frontend/views/graph/graph.css5
5 files changed, 117 insertions, 5 deletions
diff --git a/cli/app/sql/models/graph.py b/cli/app/sql/models/graph.py
index 88a15ef..fdea32a 100644
--- a/cli/app/sql/models/graph.py
+++ b/cli/app/sql/models/graph.py
@@ -37,7 +37,7 @@ class Graph(Base):
def toFullJSON(self):
data = self.toJSON()
- data['pages'] = [ page.toJSON() for page in self.pages ]
+ data['pages'] = [ page.toLinkJSON() for page in self.pages ]
return data
class GraphForm(ModelForm):
diff --git a/cli/app/sql/models/page.py b/cli/app/sql/models/page.py
index 960ffd7..4ca758d 100644
--- a/cli/app/sql/models/page.py
+++ b/cli/app/sql/models/page.py
@@ -23,6 +23,7 @@ class Page(Base):
updated_at = Column(UtcDateTime(), onupdate=utcnow())
tiles = relationship("Tile", foreign_keys="Tile.page_id", lazy='dynamic', order_by="asc(Tile.sort_order)")
+ backlinks = relationship("Tile", foreign_keys="Tile.target_page_id", lazy='dynamic')
def toJSON(self):
return {
@@ -37,6 +38,11 @@ class Page(Base):
'updated_at': self.updated_at,
}
+ def toLinkJSON(self):
+ data = self.toJSON()
+ data['backlinks'] = [ tile.toJSON() for tile in self.backlinks ]
+ return data
+
def toFullJSON(self):
data = self.toJSON()
data['tiles'] = [ tile.toJSON() for tile in self.tiles ]
diff --git a/frontend/util/index.js b/frontend/util/index.js
index e951182..4a6cef8 100644
--- a/frontend/util/index.js
+++ b/frontend/util/index.js
@@ -68,6 +68,7 @@ export const px = (n, w) => Math.round(n * w) + 'px'
export const clamp = (n, a=0, b=1) => n < a ? a : n < b ? n : b
export const dist = (x1, y1, x2, y2) => Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
+export const mod = (n, m) => n - (m * Math.floor(n / m))
/* URLs */
diff --git a/frontend/views/graph/components/graph.editor.js b/frontend/views/graph/components/graph.editor.js
index 5093327..c21c624 100644
--- a/frontend/views/graph/components/graph.editor.js
+++ b/frontend/views/graph/components/graph.editor.js
@@ -9,7 +9,7 @@ import actions from '../../../actions'
import * as graphActions from '../graph.actions'
import { Loader } from '../../../common'
-import { clamp, dist } from '../../../util'
+import { clamp, dist, mod } from '../../../util'
const defaultState = {
dragging: false,
@@ -144,19 +144,24 @@ class GraphEditor extends Component {
render(){
// console.log(this.props.graph.show.res)
- const currentPage = this.state.page
- const currentBox = this.state.box
+ const { page: currentPage, box } = this.state
const { res: graph } = this.props.graph.show
// console.log(res.pages)
return (
<div className='graph' ref={this.graphRef}>
+ <GraphCanvas
+ bounds={this.state.bounds}
+ pages={graph.pages}
+ currentPage={currentPage}
+ box={box}
+ />
{this.state.bounds && graph.pages.map(page => (
<PageHandle
key={page.id}
graph={graph}
page={page}
bounds={this.state.bounds}
- box={currentPage && page.id === currentPage.id && currentBox}
+ box={currentPage && page.id === currentPage.id && box}
onMouseDown={e => this.handleMouseDown(e, page)}
/>
))}
@@ -165,6 +170,101 @@ class GraphEditor extends Component {
}
}
+class GraphCanvas extends Component {
+ constructor(props) {
+ super(props)
+ this.canvasRef = React.createRef()
+ }
+
+ componentDidMount() {
+ if (this.props.bounds) {
+ this.draw({})
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ this.draw(prevProps)
+ }
+
+ draw(prevProps) {
+ const { current: canvas } = this.canvasRef
+ const { bounds, pages, currentPage, box } = this.props
+ const { width, height } = bounds
+ if (prevProps.bounds !== bounds) {
+ canvas.width = width
+ canvas.height = height
+ }
+ const ctx = canvas.getContext('2d')
+ ctx.clearRect(0, 0, width, height)
+ ctx.lineWidth = 2
+ const coordsLookup = pages.reduce((a,b) => {
+ if (currentPage && box && b.id === currentPage.id) {
+ a[b.id] = {
+ x: box.x,
+ y: box.y,
+ backlinks: new Set([]),
+ }
+ } else {
+ a[b.id] = {
+ x: b.settings.x,
+ y: b.settings.y,
+ backlinks: new Set([]),
+ }
+ }
+ return a
+ }, {})
+ pages.map(page => {
+ const sourceCoord = coordsLookup[page.id]
+ page.backlinks.map(tile => {
+ if (tile.target_page_id <= 0) return
+ const targetCoord = coordsLookup[tile.page_id]
+ let xOffset = 16
+ let yOffset = 16
+ // console.log(tile.page_id, tile.target_page_id, sourceCoord.backlinks, targetCoord.backlinks)
+ if (targetCoord.backlinks.has(tile.target_page_id)) {
+ xOffset += 10
+ ctx.strokeStyle = "#88ffff"
+ } else {
+ sourceCoord.backlinks.add(tile.page_id)
+ ctx.strokeStyle = "#ff88ff"
+ }
+ ctx.beginPath()
+ const x1 = targetCoord.x * width + xOffset
+ const y1 = targetCoord.y * height + yOffset
+ const x2 = sourceCoord.x * width + xOffset
+ const y2 = sourceCoord.y * height + yOffset
+ this.arrow(ctx, x1, y1, x2, y2)
+ ctx.stroke()
+ })
+ })
+ }
+
+ arrow(ctx, x1, y1, x2, y2) {
+ const headlen = 10 // length of head in pixels
+ const dx = x2 - x1
+ const dy = y2 - y1
+ const angle = Math.atan2(dy, dx)
+ const farOffset = 20
+ x1 += Math.cos(angle) * 0
+ x2 -= Math.cos(angle) * farOffset
+ y1 += Math.sin(angle) * 0
+ y2 -= Math.sin(angle) * farOffset
+ const leftAngle = mod(angle - Math.PI / 6, Math.PI * 2)
+ const rightAngle = mod(angle + Math.PI / 6, Math.PI * 2)
+ ctx.moveTo(x1, y1)
+ ctx.lineTo(x2, y2)
+ ctx.lineTo(x2 - headlen * Math.cos(leftAngle), y2 - headlen * Math.sin(leftAngle))
+ ctx.moveTo(x2, y2)
+ ctx.lineTo(x2 - headlen * Math.cos(rightAngle), y2 - headlen * Math.sin(rightAngle))
+ }
+
+ render() {
+ return (
+ <canvas ref={this.canvasRef} />
+ )
+ }
+}
+
const PageHandle = ({ graph, page, bounds, box, onMouseDown }) => {
let style;
if (box) {
diff --git a/frontend/views/graph/graph.css b/frontend/views/graph/graph.css
index 511d15e..52f1a7e 100644
--- a/frontend/views/graph/graph.css
+++ b/frontend/views/graph/graph.css
@@ -12,6 +12,11 @@
rgba(128, 0, 64, 0.2)
);
}
+.graph canvas {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
/* Sidebar boxes */