diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-06-07 16:51:27 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-06-07 16:51:27 +0200 |
| commit | 9041401b73f93228cc9b5183a9ef30e5f7f171a6 (patch) | |
| tree | aad1d009a537aaab4bd3f923718b5d1e030cf356 | |
| parent | 3550f0ee37ee12179404f721b5a55be37992603f (diff) | |
drawing graphs with backlinks
| -rw-r--r-- | cli/app/sql/models/graph.py | 2 | ||||
| -rw-r--r-- | cli/app/sql/models/page.py | 6 | ||||
| -rw-r--r-- | frontend/util/index.js | 1 | ||||
| -rw-r--r-- | frontend/views/graph/components/graph.editor.js | 108 | ||||
| -rw-r--r-- | frontend/views/graph/graph.css | 5 |
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 */ |
