Skip to content
Snippets Groups Projects
Commit c78cc945 authored by Adam Procter's avatar Adam Procter
Browse files

start of node connectors

parent db4cead5
No related tags found
No related merge requests found
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" />
name="viewport"
content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"
/>
<!-- Open Graph --> <!-- Open Graph -->
<meta <meta property="og:image" content="https://alpha.nodenogg.in/files/logo_nt.png" />
property="og:image"
content="https://alpha.nodenogg.in/files/logo_nt.png"
/>
<meta property="og:image:width" content="216" /> <meta property="og:image:width" content="216" />
<meta property="og:image:height" content="226" /> <meta property="og:image:height" content="226" />
<meta property="og:image:type" content="image/png" /> <meta property="og:image:type" content="image/png" />
<meta property="og:title" content="nodenogg.in - ALPHA build" /> <meta property="og:title" content="nodenogg.in - ALPHA build" />
<meta <meta property="og:description"
property="og:description" content="nodenoggin is a Free Open Source Software (FOSS) project that empowers communities of practice to build and create knowledge by bridging the physical and digital studio spaces. designers and makers gather and spatially arrange materials, links and thoughts within the shared digital platform." />
content="nodenoggin is a Free Open Source Software (FOSS) project that empowers communities of practice to build and create knowledge by bridging the physical and digital studio spaces. designers and makers gather and spatially arrange materials, links and thoughts within the shared digital platform."
/>
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://alpha.nodenogg.in" /> <meta property="og:url" content="https://alpha.nodenogg.in" />
<!-- Twitter --> <!-- Twitter -->
<meta name="twitter:card" content="summary" /> <meta name="twitter:card" content="summary" />
<meta <meta property="twitter:image" content="https://alpha.nodenogg.in/files/logo_nt.jpg" />
property="twitter:image"
content="https://alpha.nodenogg.in/files/logo_nt.jpg"
/>
<meta property="twitter:image:width" content="216" /> <meta property="twitter:image:width" content="216" />
<meta property="twitter:image:height" content="226" /> <meta property="twitter:image:height" content="226" />
<meta property="twitter:image:type" content="image/png" /> <meta property="twitter:image:type" content="image/png" />
<meta property="twitter:title" content="nodenogg.in - ALPHA build" /> <meta property="twitter:title" content="nodenogg.in - ALPHA build" />
<meta <meta property="twitter:description"
property="twitter:description" content="nodenoggin is a Free Open Source Software (FOSS) project that empowers communities of practice to build and create knowledge by bridging the physical and digital studio spaces. designers and makers gather and spatially arrange materials, links and thoughts within the shared digital platform." />
content="nodenoggin is a Free Open Source Software (FOSS) project that empowers communities of practice to build and create knowledge by bridging the physical and digital studio spaces. designers and makers gather and spatially arrange materials, links and thoughts within the shared digital platform."
/>
<meta property="twitter:url" content="https://alpha.nodenogg.in" /> <meta property="twitter:url" content="https://alpha.nodenogg.in" />
<meta property="twitter:site" content="@nodenoggin" /> <meta property="twitter:site" content="@nodenoggin" />
...@@ -46,17 +34,17 @@ ...@@ -46,17 +34,17 @@
<link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title> <title></title>
</head> </head>
<body> <body>
<noscript> <noscript>
<strong <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to properly without JavaScript enabled. Please enable it to
continue.</strong continue.</strong>
>
</noscript> </noscript>
<div id="app"></div> <div id="app"></div>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
</body> </body>
</html> </html>
\ No newline at end of file
<template>
<canvas ref="canvas"></canvas>
</template>
<script>
//import { mapState } from 'vuex'
//import { generateLinkHandles } from './mixins/nodes.js'
// import { drawLinkBezierCurve, drawArrowHead } from './mixins/canvas.js'
import { color } from './mixins/color.js'
export default {
name: 'ConnectorsLayer',
mixins: [color],
mounted() {
this.getPalette(1, 1)
}
}
// export default {
// name: 'ConnectorsLayer',
// mixins: [generateLinkHandles, drawLinkBezierCurve, drawArrowHead, getPalette],
// props: {
// nodes: {
// type: Array
// },
// connections: {
// type: Array
// }
// },
// mounted() {
// this.canvas = this.$refs.canvas
// this.context = this.canvas.getContext('2d')
// this.draw()
// },
// data() {
// return {
// canvas: {},
// context: {},
// defaultLinkProps: {
// // these are our initial 'default' settings for each link
// hue: 'dark',
// tension: 0.25,
// lineWidth: 3,
// lineDash: [0, 0]
// }
// }
// },
// // computed: mapState({
// // configConnect: state => state.configConnect
// // }),
// // Watch the props for changes and, if necessary, redraw canvas
// watch: {
// nodes() {
// this.draw()
// },
// links() {
// this.draw()
// }
// },
// methods: {
// /**
// * Clears the context, renders links and nodes on initial load
// * and when new data has been provided
// **/
// draw() {
// this.clear()
// this.context.save()
// this.renderAllLinks()
// this.renderAllNodes()
// this.context.restore()
// },
// /**
// * Clears drawing context
// **/
// clear() {
// this.context.clearRect(0, 0, this.width, this.height)
// },
// /**
// * Where a node can be rendered
// * @param {Node} node - Target node
// **/
// renderNode({ x, y }) {
// this.context.setTransform(1, 0, 0, 1, x, y)
// // this renders nothing, but if we wanted to we
// // could render nodes in the canvas as well
// this.context.setTransform(1, 0, 0, 1, 0, 0)
// },
// /**
// * Renders a link between two nodes
// **/
// renderLink({ from, to, color, lineDash, lineWidth, hue, tension, arrow }) {
// // fetch the nodes based on their id reference provided in the link
// const fromNode = this.findNode(from)
// const toNode = this.findNode(to)
// if (fromNode && toNode) {
// // apply the link color, falling back to defaultLinkProps
// this.context.strokeStyle = getPalette(
// color,
// hue || this.defaultLinkProps.hue
// )
// // apply link styling, falling back to defaultLinkProps
// this.context.setLineDash(lineDash || this.defaultLinkProps.lineDash)
// this.context.lineWidth = lineWidth || this.defaultLinkProps.lineWidth
// // establish link tension, falling back to defaultLinkProps
// const curveTension = !isNaN(tension) || this.defaultLinkProps.tension
// // add the curve to our drawing context
// drawLinkBezierCurve(this.context, fromNode, toNode, curveTension)
// if (arrow) drawArrowHead(this.context, fromNode, toNode)
// // render the curve
// this.context.stroke()
// }
// },
// /**
// * Helper to fetch a single node based on its id
// *
// * @param {string} id - Reference to node id
// * @returns {Node}
// **/
// findNode(id) {
// return [...this.nodes].find(pt => pt.id === id)
// },
// /**
// * Iterate through the array of links, rendering each one
// **/
// renderAllLinks() {
// for (let link of this.links) {
// this.renderLink(link)
// }
// },
// /**
// * Included for demo only, doesn't actually render anything
// * iterate through the array of nodes, rendering each one
// **/
// renderAllNodes() {
// for (let node of this.nodes) {
// this.renderNode(node)
// }
// }
// }
// }
</script>
<style scoped>
canvas {
z-index: 1;
position: absolute;
}
</style>
// import * as win from "@/constants/window"
// import { generateLinkHandles } from "@/utils/nodes"
// import { mapRange, distance } from "@/utils/numbers"
//////////////////////////////////////////////////////////////////////
// Canvas Utilities
//////////////////////////////////////////////////////////////////////
// Because these helpers directly manipulate the drawing context,
// their methods don't need to return anything
/**
* Adds a bezier curve to the drawing context
*
* @param {CanvasRenderingContext2D} context - 2D rendering context
* @param {Node} from - Origin node
* @param {Node} to - Target node
* @param {number} tension - Tension of the curve to be drawn
*
* */
export const drawLinkBezierCurve = (context, from, to, tension) => {
const [fromHandle, toHandle] = generateLinkHandles(from, to)
// This is a simple way to adjust the link tension depending
// on the distance the link covers
const adjustedTension = mapRange(
distance(fromHandle, toHandle),
0,
win.width,
tension * 0.01,
tension * 2
)
context.beginPath()
context.moveTo(fromHandle.x, fromHandle.y)
context.bezierCurveTo(
fromHandle.x * (1 + adjustedTension),
fromHandle.y,
toHandle.x * (1 - adjustedTension),
toHandle.y,
toHandle.x,
toHandle.y
)
}
export const drawArrowHead = (context, from, to, size = 10, rotation = 0.0) => {
const [fromHandle, toHandle] = generateLinkHandles(from, to)
const angle = angleBetween(fromHandle, toHandle)
context.setLineDash([0, 0])
context.setTransform(1, 0, 0, 1, toHandle.x - size, toHandle.y)
context.rotate(angle * 0.65)
context.moveTo(-size, -size / 1.25)
context.lineTo(0, 0)
context.lineTo(-size, size / 1.25)
context.setTransform(1, 0, 0, 1, 0, 0)
}
export const color = {
data() {
return {
palette: {
coral: {
light: '#FFD8E1',
dark: '#F56789'
},
lime: {
light: '#EEFFBC',
dark: '#B9DF4E'
},
blue: {
light: '#CDEAFF',
dark: '#4EB4FF'
},
purple: {
light: '#DAD9FE',
dark: '#8A80F6'
},
pink: {
light: '#FBE9FF',
dark: '#E47EFD'
},
yellow: {
light: '#FFF3CA',
dark: '#FFD84F'
},
mono: {
light: '#FFFFFF',
dark: '#A3A3A3'
}
}
}
},
methods: {
getPalette(color, hue) {
this.palette[(color, hue)] //palette[color][hue]
//console.log(this.palette[(1, 1)])
// const getPalette = (color = 'mono', type) =>
// palette[color] ? palette[color][type] : palette.mono[type]
//
}
}
}
/**
* Selects a HEX color value depending on a supplied key reference,
* providing a default (mono) value if none is found.
*
* @param {string} color - Target palette (from above)
* @param {string} type - Target color type (light or dark)
* @returns {string} hex color value
*
* */
// const getPalette = (color = 'mono', type) =>
// palette[color] ? palette[color][type] : palette.mono[type]
//
export const drag = {
methods: {
makeDraggable(incoming) {
var active = false
var currentX
var currentY
var initialX
var initialY
var xOffset = 0
var yOffset = 0
incoming.addEventListener('mousedown', startDrag)
incoming.addEventListener('mousemove', drag)
incoming.addEventListener('mouseup', endDrag)
incoming.addEventListener('mouseleave', endDrag)
incoming.addEventListener('touchstart', startDrag)
incoming.addEventListener('touchmove', drag)
incoming.addEventListener('touchend', endDrag)
incoming.addEventListener('touchleave', endDrag)
incoming.addEventListener('touchcancel', endDrag)
function startDrag(e) {
if (e.type === 'touchstart') {
initialX = e.touches[0].clientX - xOffset
initialY = e.touches[0].clientY - yOffset
} else {
initialX = e.clientX - xOffset
initialY = e.clientY - yOffset
}
if (e.target.parentNode.classList.contains('node')) {
active = true
}
}
function drag(e) {
if (active) {
e.preventDefault()
if (e.type === 'touchmove') {
currentX = e.touches[0].clientX - initialX
currentY = e.touches[0].clientY - initialY
} else {
currentX = e.clientX - initialX
currentY = e.clientY - initialY
}
xOffset = currentX
yOffset = currentY
setTranslate(currentX, currentY, incoming)
}
}
function endDrag() {
initialX = currentX
initialY = currentY
active = false
}
function setTranslate(xPos, yPos, el) {
el.style.transform = 'translate3d(' + xPos + 'px, ' + yPos + 'px, 0)'
}
}
}
}
export const draw = {
methods: {
draw() {
this.box(this.ctx, this.x, this.y)
},
box(ctx, x, y) {
ctx.setTransform(1, 0, 0, 1, x, y)
ctx.fillStyle = this.configConnect.fill
ctx.fillRect(
this.configConnect.x,
this.configConnect.y,
this.configConnect.height,
this.configConnect.width
)
ctx.fillStyle = this.configHandle.fill
ctx.fillRect(
this.configHandle.x,
this.configHandle.y,
this.configHandle.height,
this.configHandle.width
)
ctx.stroke()
ctx.setTransform(1, 0, 0, 1, 0, 0)
}
}
}
//////////////////////////////////////////////////////////////////////
// Type Definitions
//////////////////////////////////////////////////////////////////////
/**
* A representation of a point
* @typedef {Object} Point
* @property {number} x - Horizontal (x) position in pixels
* @property {number} y - Vertical (y) position in pixels
*/
/**
* A representation of an area
* @typedef {Object} Area
* @property {number} x1 - Top left (x) position in pixels
* @property {number} y1 - Top left (y) position in pixels
* @property {number} x2 - Bottom right (x) position in pixels
* @property {number} y2 - Bottom right (y) position in pixels
*/
/**
* A representation of a node
* @typedef {Object} Node
* @property {number} x - Horizontal (x) position in pixels
* @property {number} y - Vertical (y) position in pixels
* @property {number} width - Node width in pixels
* @property {number} height - Node height in pixels
*/
//////////////////////////////////////////////////////////////////////
// NODES
//////////////////////////////////////////////////////////////////////
// Helpers for working with nodes, points and areas
/**
* Converts a @Node into the @Area it covers
*
* @param {Node} node - Origin node
* @param {number} threshold - Additional threshold area (optional)
* @return {Area} Area covered by box, including threshold
*
* */
export const nodeToArea = (node, threshold = 0.0) => {
return {
x1: node.x * (1.0 - threshold),
y1: node.y * (1.0 - threshold),
x2: (node.x + node.width) * (1.0 + threshold),
y2: (node.y + node.height) * (1.0 + threshold)
}
}
/**
* Converts an @Area into a node
*
* @param {Area} area - Origin node
* @return {Node}
*
* */
export const areaToNode = area => {
return {
x: area.x1,
y: area.y1,
width: Math.abs(area.x2 - area.x1),
height: Math.abs(area.y2 - area.y1)
}
}
/**
* Returns whether one @Area completely contains another @Area
*
* @param {Area} a
* @param {Area} b
* @return {boolean}
*
* */
export const areaContains = (a, b) =>
!(b.x1 < a.x1 || b.y1 < a.y1 || b.x2 > a.x2 || b.y2 > a.y2)
/**
* Returns whether one @Area overlaps another @Area
*
* @param {Area} a
* @param {Area} b
* @return {boolean}
*
* */
export const areaOverlaps = (a, b) => {
if (a.x1 >= b.x2 || b.x1 >= a.x2) return false
if (a.y1 >= b.y2 || b.y1 >= a.y2) return false
return true
}
/**
* Returns anchor positions for drawing links between two Nodes.
*
* @param {Node} from - Origin node
* @param {Node} from - Target node
* @return {array} Array of {x,y} positions
*
* */
export const generateLinkHandles = (from, to) => {
const toLeft = to.x > from.x
return [
{
x: from.x + from.width,
y: from.y + from.height / 2
},
{
// x: to.x + to.width / 2,
x: to.x + 10,
y: to.y + to.height / 2
}
]
}
/**
* Returns a list of nodes which overlap with a given {x1,y1,x2,y2} @Area
*
* @param {Area} area - Origin node
* @param {array} node - List of nodes to check against
* @param {number} threshold - Additional threshold area (optional)
* @return {array} Array of @Node objects which match
*
* */
export const areaNodeIntersections = (targetNode, nodes, threshold = 0) => {
return nodes
.filter(node =>
areaOverlaps(nodeToArea(targetNode), nodeToArea(node, threshold))
)
.map(({ id }) => id)
}
/**
* Returns a list of nodes which overlap with a given {x,y} @Point
*
* @param {Point} point
* @param {array} node - List of nodes to check against
* @return {array} Array of @Node objects which match
*
* */
export const pointNodeIntersections = (point, nodes) => {
return nodes.filter(node => pointWithinNode(point, node)).map(({ id }) => id)
}
/**
* Checks whether a @Point is within a @Node
*
* @param {Point} point
* @param {Node} node
* @return {boolean}
*
* */
export const pointWithinNode = (point, node) => {
const { x1, y1, x2, y2 } = nodeToArea(node)
return x1 <= point.x && point.x <= x2 && y1 <= point.y && point.y <= y2
}
/**
* Converts an array of nodes into a single large @Area
*
* @param {array} node - List of nodes
* @return {Area}
*
* */
export const generateAreaFromNodes = nodes => {
const sum = {}
for (let node of nodes) {
const area = nodeToArea(node)
if (!sum.x1 || area.x1 < sum.x1) {
sum.x1 = area.x1
}
if (!sum.y1 || area.y1 < sum.y1) {
sum.y1 = area.y1
}
if (!sum.x2 || area.x2 > sum.x2) {
sum.x2 = area.x2
}
if (!sum.y2 || area.y2 > sum.y2) {
sum.y2 = area.y2
}
}
return sum
}
/**
* Generates a node shape from two given points
* */
export const generateNode = (origin, point) => {
const minusX = origin.x > point.x
const minusY = origin.y > point.y
const width = Math.abs(point.x - origin.x)
const height = Math.abs(point.y - origin.y)
return {
x: minusX ? origin.x - width : origin.x,
y: minusY ? origin.y - height : origin.y,
width,
height
}
}
/**
* Transforms a grouped set of nodes based on a base transformation
*
* @param {Node[]} nodes - List of nodes
* @param {Point} transform – transform
* @return {Node[]}
* */
export const transformNodeSelection = (nodes, transform) => {
const baseCoordinates = {}
// iterate through all nodes to get underlying coordinates of group
for (let node of nodes) {
if (!baseCoordinates.x || node.x < baseCoordinates.x) {
baseCoordinates.x = node.x
}
if (!baseCoordinates.y || node.y < baseCoordinates.y) {
baseCoordinates.y = node.y
}
}
return nodes.map(node => {
const relativeTransform = {
x: node.x - baseCoordinates.x,
y: node.y - baseCoordinates.y
}
return Object.assign({}, node, {
x: transform.x + relativeTransform.x,
y: transform.y + relativeTransform.y
})
})
}
//////////////////////////////////////////////////////////////////////
// Maths and Numbers
//////////////////////////////////////////////////////////////////////
/**
* Returns the distance between two {x,y} points
*
* @param {Node} from - Origin node
* @param {Node} from - Target node
* @return {number} Distance between points
*
* */
export const distance = (from, to) => Math.hypot(to.x - from.x, to.y - from.y)
/**
* Returns an interpolated value between two numbers
*
* @param {number} from
* @param {number} to
* @param {number} extent - between 0.0 and 1.0
* @return {number}
*
* */
export const lerp = (from, to, extent) => from * (1 - extent) + to * extent
/**
* Returns an interpolated value between two points
*
* @param {Point} from
* @param {Point} to
* @param {number} extent - between 0.0 and 1.0
* @return {Point}
*
* */
export const lerpPoint = (from, to, extent) => {
return {
x: lerp(from.x, to.x, extent),
x: lerp(from.y, to.y, extent)
}
}
/**
* Map a @number from one range to another
*
* @param {number} value - value to map
* @param {number} from1
* @param {number} to1
* @param {number} from2
* @param {number} to2
* @return {number}
*
* */
export const mapRange = (value, from1, to1, from2, to2) =>
((value - from1) * (to2 - from2)) / (to1 - from1) + from2
/**
* Calculates the angle between two points
*
* @param {Point} point1
* @param {Point} point2
* @return {number}
*
* */
export const angleBetween = (point1, point2) =>
Math.atan2(point2.y - point1.y, point2.x - point1.x)
...@@ -110,6 +110,7 @@ const store = new Vuex.Store({ ...@@ -110,6 +110,7 @@ const store = new Vuex.Store({
SET_OTHER_NODES(state) { SET_OTHER_NODES(state) {
state.otherNodes = [] state.otherNodes = []
var i var i
var j var j
for (i = 0; i < Object.keys(state.allNodes).length; i++) { for (i = 0; i < Object.keys(state.allNodes).length; i++) {
...@@ -126,7 +127,6 @@ const store = new Vuex.Store({ ...@@ -126,7 +127,6 @@ const store = new Vuex.Store({
nodeid: state.allNodes[i].doc.nodes[j].nodeid, nodeid: state.allNodes[i].doc.nodes[j].nodeid,
nodetext: state.allNodes[i].doc.nodes[j].nodetext nodetext: state.allNodes[i].doc.nodes[j].nodetext
} }
state.otherNodes.push(newNode) state.otherNodes.push(newNode)
} }
} }
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
v-bind:nodetext="value.nodetext" v-bind:nodetext="value.nodetext"
/> />
<CanvasLayer /> <ConnectorsLayer />
<ControlsLayer /> <ControlsLayer />
</div> </div>
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<script> <script>
// @ is an alias to /src // @ is an alias to /src
import OnBoard from '@/components/OnBoard.vue' import OnBoard from '@/components/OnBoard.vue'
import CanvasLayer from '@/components/CanvasLayer.vue' import ConnectorsLayer from '@/components/ConnectorsLayer.vue'
import NodesLayer from '@/components/NodesLayer.vue' import NodesLayer from '@/components/NodesLayer.vue'
import OtherNodeslayer from '@/components/OtherNodeslayer.vue' import OtherNodeslayer from '@/components/OtherNodeslayer.vue'
import DeBug from '@/components/DeBug.vue' import DeBug from '@/components/DeBug.vue'
...@@ -47,7 +47,7 @@ export default { ...@@ -47,7 +47,7 @@ export default {
components: { components: {
OnBoard, OnBoard,
CanvasLayer, ConnectorsLayer,
NodesLayer, NodesLayer,
OtherNodeslayer, OtherNodeslayer,
DeBug, DeBug,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment