Skip to content

Instantly share code, notes, and snippets.

@ps23
Last active August 4, 2017 23:06
Show Gist options
  • Save ps23/7b13ad6f40d852f87e1cc6acbe4a90b2 to your computer and use it in GitHub Desktop.
Save ps23/7b13ad6f40d852f87e1cc6acbe4a90b2 to your computer and use it in GitHub Desktop.
valcri-entity-graph
# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so
# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# Logs and databases #
######################
*.log
*.sql
*.sqlite
# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

Description

=======

Reusable Chart component for D3 - Using factories

function avatar() {
}
avatar.draw = function() {
var args = Array.prototype.slice.call(arguments)
var fontWeight = 'bold', textColour = '#ffffff';
// TODO: CHECK RETURNS FALSE, WHY?
// if (args[args.length - 1] instanceof SVGElement)
var target = args.pop()
var mark = args.pop()
var radius = args.pop()
var id = args.pop()
var charCode = args.pop()
var secColour = args.pop()
var primColour = args.pop()
var text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute('class', 'avatar--text__main');
text.setAttributeNS(null, "fill", textColour);
text.setAttributeNS(null, "pointer-events", "auto");
text.setAttributeNS(null, "id", id);
text.setAttributeNS(null, "dy", '0.35em');
text.setAttributeNS(null, "text-anchor", "middle");
text.setAttributeNS(null, "font-weight", fontWeight);
text.setAttributeNS(null, "font-size", radius - 2);
text.innerHTML = charCode;
var circle = document.createElementNS("http://www.w3.org/2000/svg",
"circle");
var inner;
if (mark) {
circle.setAttribute('class', 'avatar--circle__mark-stroke');
circle.setAttributeNS(null, "stroke", secColour);
circle.setAttributeNS(null, "r", radius - 1);
circle.setAttributeNS(null, "stroke-width", 2);
circle.setAttributeNS(null, "fill", "none");
inner = document.createElementNS("http://www.w3.org/2000/svg",
"circle");
inner.setAttribute('class', 'avatar--circle__mark-main');
inner.setAttributeNS(null, "r", radius - 3);
inner.setAttributeNS(null, "stroke", "none");
inner.setAttributeNS(null, "fill", primColour);
inner.setAttributeNS(null, "id", id);
if(target != null) target.appendChild(inner);
} else {
circle.setAttribute('class', 'avatar--circle__mark-main');
circle.setAttributeNS(null, "r", radius);
circle.setAttributeNS(null, "stroke", "none");
circle.setAttributeNS(null, "fill", primColour);
}
circle.setAttributeNS(null, "id", id);
if(target != null) {
target.appendChild(circle);
target.appendChild(text);
return;
}
var a = new Array(circle, text);
if(inner != null) a.unshift(inner);
return a;
};
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-dispatch'), require('d3-drag'), require('d3-interpolate'), require('d3-selection'), require('d3-transition')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3-dispatch', 'd3-drag', 'd3-interpolate', 'd3-selection', 'd3-transition'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3,global.d3,global.d3));
}(this, (function (exports,d3Dispatch,d3Drag,d3Interpolate,d3Selection,d3Transition) { 'use strict';
var constant = function(x) {
return function() {
return x;
};
};
var BrushEvent = function(target, type, selection) {
this.target = target;
this.type = type;
this.selection = selection;
};
function nopropagation() {
d3Selection.event.stopImmediatePropagation();
}
var noevent = function() {
d3Selection.event.preventDefault();
d3Selection.event.stopImmediatePropagation();
};
var MODE_DRAG = {name: "drag"};
var MODE_SPACE = {name: "space"};
var MODE_HANDLE = {name: "handle"};
var MODE_CENTER = {name: "center"};
var X = {
name: "x",
handles: ["e", "w"].map(type),
input: function(x, e) { return x && [[x[0], e[0][1]], [x[1], e[1][1]]]; },
output: function(xy) { return xy && [xy[0][0], xy[1][0]]; }
};
var Y = {
name: "y",
handles: ["n", "s"].map(type),
input: function(y, e) { return y && [[e[0][0], y[0]], [e[1][0], y[1]]]; },
output: function(xy) { return xy && [xy[0][1], xy[1][1]]; }
};
var XY = {
name: "xy",
handles: ["n", "e", "s", "w", "nw", "ne", "se", "sw"].map(type),
input: function(xy) { return xy; },
output: function(xy) { return xy; }
};
var cursors = {
overlay: "crosshair",
selection: "move",
n: "ns-resize",
e: "ew-resize",
s: "ns-resize",
w: "ew-resize",
nw: "nwse-resize",
ne: "nesw-resize",
se: "nwse-resize",
sw: "nesw-resize"
};
var flipX = {
e: "w",
w: "e",
nw: "ne",
ne: "nw",
se: "sw",
sw: "se"
};
var flipY = {
n: "s",
s: "n",
nw: "sw",
ne: "se",
se: "ne",
sw: "nw"
};
var signsX = {
overlay: +1,
selection: +1,
n: null,
e: +1,
s: null,
w: -1,
nw: -1,
ne: +1,
se: +1,
sw: -1
};
var signsY = {
overlay: +1,
selection: +1,
n: -1,
e: null,
s: +1,
w: null,
nw: -1,
ne: -1,
se: +1,
sw: +1
};
function type(t) {
return {type: t};
}
// Ignore right-click, since that should open the context menu.
function defaultFilter() {
return !d3Selection.event.button;
}
function defaultExtent() {
var svg = this.ownerSVGElement || this;
return [[0, 0], [svg.width.baseVal.value, svg.height.baseVal.value]];
}
// Like d3.local, but with the name “__brush” rather than auto-generated.
function local(node) {
while (!node.__brush) if (!(node = node.parentNode)) return;
return node.__brush;
}
function empty(extent) {
return extent[0][0] === extent[1][0]
|| extent[0][1] === extent[1][1];
}
function brushSelection(node) {
var state = node.__brush;
return state ? state.dim.output(state.selection) : null;
}
function brushX() {
return brush$1(X);
}
function brushY() {
return brush$1(Y);
}
var brush = function() {
return brush$1(XY);
};
function brush$1(dim) {
var extent = defaultExtent,
filter = defaultFilter,
listeners = d3Dispatch.dispatch(brush, "start", "brush", "end"),
handleSize = 6,
touchending;
function brush(group) {
var overlay = group
.property("__brush", initialize)
.selectAll(".overlay")
.data([type("overlay")]);
overlay.enter().append("rect")
.attr("class", "overlay")
.attr("pointer-events", "all")
.attr("cursor", cursors.overlay)
.merge(overlay)
.each(function() {
var extent = local(this).extent;
d3Selection.select(this)
.attr("x", extent[0][0])
.attr("y", extent[0][1])
.attr("width", extent[1][0] - extent[0][0])
.attr("height", extent[1][1] - extent[0][1]);
});
group.selectAll(".selection")
.data([type("selection")])
.enter().append("rect")
.attr("class", "selection")
.attr("cursor", cursors.selection)
.attr("fill", "#777")
.attr("fill-opacity", 0.3)
.attr("stroke", "#fff")
.attr("shape-rendering", "crispEdges");
var handle = group.selectAll(".handle")
.data(dim.handles, function(d) { return d.type; });
handle.exit().remove();
handle.enter().append("rect")
.attr("class", function(d) { return "handle handle--" + d.type; })
.attr("cursor", function(d) { return cursors[d.type]; });
group
.each(redraw)
.attr("fill", "none")
.attr("pointer-events", "all")
.style("-webkit-tap-highlight-color", "rgba(0,0,0,0)")
.on("mousedown.brush touchstart.brush", started);
}
brush.move = function(group, selection) {
if (group.selection) {
group
.on("start.brush", function() { emitter(this, arguments).beforestart().start(); })
.on("interrupt.brush end.brush", function() { emitter(this, arguments).end(); })
.tween("brush", function() {
var that = this,
state = that.__brush,
emit = emitter(that, arguments),
selection0 = state.selection,
selection1 = dim.input(typeof selection === "function" ? selection.apply(this, arguments) : selection, state.extent),
i = d3Interpolate.interpolate(selection0, selection1);
function tween(t) {
state.selection = t === 1 && empty(selection1) ? null : i(t);
redraw.call(that);
emit.brush();
}
return selection0 && selection1 ? tween : tween(1);
});
} else {
group
.each(function() {
var that = this,
args = arguments,
state = that.__brush,
selection1 = dim.input(typeof selection === "function" ? selection.apply(that, args) : selection, state.extent),
emit = emitter(that, args).beforestart();
d3Transition.interrupt(that);
state.selection = selection1 == null || empty(selection1) ? null : selection1;
redraw.call(that);
emit.start().brush().end();
});
}
};
function redraw() {
var group = d3Selection.select(this),
selection = local(this).selection;
if (selection) {
group.selectAll(".selection")
.style("display", null)
.attr("x", selection[0][0])
.attr("y", selection[0][1])
.attr("width", selection[1][0] - selection[0][0])
.attr("height", selection[1][1] - selection[0][1]);
group.selectAll(".handle")
.style("display", null)
.attr("x", function(d) { return d.type[d.type.length - 1] === "e" ? selection[1][0] - handleSize / 2 : selection[0][0] - handleSize / 2; })
.attr("y", function(d) { return d.type[0] === "s" ? selection[1][1] - handleSize / 2 : selection[0][1] - handleSize / 2; })
.attr("width", function(d) { return d.type === "n" || d.type === "s" ? selection[1][0] - selection[0][0] + handleSize : handleSize; })
.attr("height", function(d) { return d.type === "e" || d.type === "w" ? selection[1][1] - selection[0][1] + handleSize : handleSize; });
}
else {
group.selectAll(".selection,.handle")
.style("display", "none")
.attr("x", null)
.attr("y", null)
.attr("width", null)
.attr("height", null);
}
}
function emitter(that, args) {
return that.__brush.emitter || new Emitter(that, args);
}
function Emitter(that, args) {
this.that = that;
this.args = args;
this.state = that.__brush;
this.active = 0;
}
Emitter.prototype = {
beforestart: function() {
if (++this.active === 1) this.state.emitter = this, this.starting = true;
return this;
},
start: function() {
if (this.starting) this.starting = false, this.emit("start");
return this;
},
brush: function() {
this.emit("brush");
return this;
},
end: function() {
if (--this.active === 0) delete this.state.emitter, this.emit("end");
return this;
},
emit: function(type) {
d3Selection.customEvent(new BrushEvent(brush, type, dim.output(this.state.selection)), listeners.apply, listeners, [type, this.that, this.args]);
}
};
function started() {
if (d3Selection.event.touches) { if (d3Selection.event.changedTouches.length < d3Selection.event.touches.length) return noevent(); }
else if (touchending) return;
if (!filter.apply(this, arguments)) return;
var that = this,
type = d3Selection.event.target.__data__.type,
mode = (d3Selection.event.metaKey ? type = "overlay" : type) === "selection" ? MODE_DRAG : (d3Selection.event.altKey ? MODE_CENTER : MODE_HANDLE),
signX = dim === Y ? null : signsX[type],
signY = dim === X ? null : signsY[type],
state = local(that),
extent = state.extent,
selection = state.selection,
W = extent[0][0], w0, w1,
N = extent[0][1], n0, n1,
E = extent[1][0], e0, e1,
S = extent[1][1], s0, s1,
dx,
dy,
moving,
lockX,
lockY,
point0 = d3Selection.mouse(that),
point = point0,
emit = emitter(that, arguments).beforestart();
if (type === "overlay") {
state.selection = selection = [
[w0 = dim === Y ? W : point0[0], n0 = dim === X ? N : point0[1]],
[e0 = dim === Y ? E : w0, s0 = dim === X ? S : n0]
];
} else {
w0 = selection[0][0];
n0 = selection[0][1];
e0 = selection[1][0];
s0 = selection[1][1];
}
w1 = w0;
n1 = n0;
e1 = e0;
s1 = s0;
var group = d3Selection.select(that)
.attr("pointer-events", "none");
var overlay = group.selectAll(".overlay")
.attr("cursor", cursors[type]);
if (d3Selection.event.touches) {
group
.on("touchmove.brush", moved, true)
.on("touchend.brush touchcancel.brush", ended, true);
} else {
var view = d3Selection.select(d3Selection.event.view)
.on("keydown.brush", keydowned, true)
.on("keyup.brush", keyupped, true)
.on("mousemove.brush", moved, true)
.on("mouseup.brush", ended, true);
d3Drag.dragDisable(d3Selection.event.view);
}
nopropagation();
d3Transition.interrupt(that);
redraw.call(that);
emit.start();
function moved() {
var point1 = d3Selection.mouse(that);
point = point1;
moving = true;
noevent();
move();
}
function move() {
var t;
dx = point[0] - point0[0];
dy = point[1] - point0[1];
switch (mode) {
case MODE_SPACE:
case MODE_DRAG: {
if (signX) dx = Math.max(W - w0, Math.min(E - e0, dx)), w1 = w0 + dx, e1 = e0 + dx;
if (signY) dy = Math.max(N - n0, Math.min(S - s0, dy)), n1 = n0 + dy, s1 = s0 + dy;
break;
}
case MODE_HANDLE: {
if (signX < 0) dx = Math.max(W - w0, Math.min(E - w0, dx)), w1 = w0 + dx, e1 = e0;
else if (signX > 0) dx = Math.max(W - e0, Math.min(E - e0, dx)), w1 = w0, e1 = e0 + dx;
if (signY < 0) dy = Math.max(N - n0, Math.min(S - n0, dy)), n1 = n0 + dy, s1 = s0;
else if (signY > 0) dy = Math.max(N - s0, Math.min(S - s0, dy)), n1 = n0, s1 = s0 + dy;
break;
}
case MODE_CENTER: {
if (signX) w1 = Math.max(W, Math.min(E, w0 - dx * signX)), e1 = Math.max(W, Math.min(E, e0 + dx * signX));
if (signY) n1 = Math.max(N, Math.min(S, n0 - dy * signY)), s1 = Math.max(N, Math.min(S, s0 + dy * signY));
break;
}
}
if (e1 < w1) {
signX *= -1;
t = w0, w0 = e0, e0 = t;
t = w1, w1 = e1, e1 = t;
if (type in flipX) overlay.attr("cursor", cursors[type = flipX[type]]);
}
if (s1 < n1) {
signY *= -1;
t = n0, n0 = s0, s0 = t;
t = n1, n1 = s1, s1 = t;
if (type in flipY) overlay.attr("cursor", cursors[type = flipY[type]]);
}
if (state.selection) selection = state.selection; // May be set by brush.move!
if (lockX) w1 = selection[0][0], e1 = selection[1][0];
if (lockY) n1 = selection[0][1], s1 = selection[1][1];
if (selection[0][0] !== w1
|| selection[0][1] !== n1
|| selection[1][0] !== e1
|| selection[1][1] !== s1) {
state.selection = [[w1, n1], [e1, s1]];
redraw.call(that);
emit.brush();
}
}
function ended() {
nopropagation();
if (d3Selection.event.touches) {
if (d3Selection.event.touches.length) return;
if (touchending) clearTimeout(touchending);
touchending = setTimeout(function() { touchending = null; }, 500); // Ghost clicks are delayed!
group.on("touchmove.brush touchend.brush touchcancel.brush", null);
} else {
d3Drag.dragEnable(d3Selection.event.view, moving);
view.on("keydown.brush keyup.brush mousemove.brush mouseup.brush", null);
}
group.attr("pointer-events", "all");
overlay.attr("cursor", cursors.overlay);
if (state.selection) selection = state.selection; // May be set by brush.move (on start)!
if (empty(selection)) state.selection = null, redraw.call(that);
emit.end();
}
function keydowned() {
switch (d3Selection.event.keyCode) {
case 18: { // ALT
if (mode === MODE_HANDLE) {
if (signX) e0 = e1 - dx * signX, w0 = w1 + dx * signX;
if (signY) s0 = s1 - dy * signY, n0 = n1 + dy * signY;
mode = MODE_CENTER;
move();
}
break;
}
case 32: { // SPACE; takes priority over ALT
if (mode === MODE_HANDLE || mode === MODE_CENTER) {
if (signX < 0) e0 = e1 - dx; else if (signX > 0) w0 = w1 - dx;
if (signY < 0) s0 = s1 - dy; else if (signY > 0) n0 = n1 - dy;
mode = MODE_SPACE;
overlay.attr("cursor", cursors.selection);
move();
}
break;
}
default: return;
}
noevent();
}
function keyupped() {
switch (d3Selection.event.keyCode) {
case 18: { // ALT
if (mode === MODE_CENTER) {
if (signX < 0) e0 = e1; else if (signX > 0) w0 = w1;
if (signY < 0) s0 = s1; else if (signY > 0) n0 = n1;
mode = MODE_HANDLE;
move();
}
break;
}
case 32: { // SPACE
if (mode === MODE_SPACE) {
if (d3Selection.event.altKey) {
if (signX) e0 = e1 - dx * signX, w0 = w1 + dx * signX;
if (signY) s0 = s1 - dy * signY, n0 = n1 + dy * signY;
mode = MODE_CENTER;
} else {
if (signX < 0) e0 = e1; else if (signX > 0) w0 = w1;
if (signY < 0) s0 = s1; else if (signY > 0) n0 = n1;
mode = MODE_HANDLE;
}
overlay.attr("cursor", cursors[type]);
move();
}
break;
}
default: return;
}
noevent();
}
}
function initialize() {
var state = this.__brush || {selection: null};
state.extent = extent.apply(this, arguments);
state.dim = dim;
return state;
}
brush.extent = function(_) {
return arguments.length ? (extent = typeof _ === "function" ? _ : constant([[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]]), brush) : extent;
};
brush.filter = function(_) {
return arguments.length ? (filter = typeof _ === "function" ? _ : constant(!!_), brush) : filter;
};
brush.handleSize = function(_) {
return arguments.length ? (handleSize = +_, brush) : handleSize;
};
brush.on = function() {
var value = listeners.on.apply(listeners, arguments);
return value === listeners ? brush : value;
};
return brush;
}
exports.brush = brush;
exports.brushX = brushX;
exports.brushY = brushY;
exports.brushSelection = brushSelection;
Object.defineProperty(exports, '__esModule', { value: true });
})));
{
"defs": {
"nominal": {
"color1": "#8DA0CB",
"color2": "#FC8D62",
"label": "N"
},
"crime": {
"color1": "#66C2A5",
"color2": "#FC8D62",
"label": "CR"
},
"location": {
"color1": "#D870AD",
"color2": "#D870AD",
"label": "L"
}
},
"nodes": [
{
"id": "http://ns.valcri.org/data/crimes#crime138640301",
"label": "138640301",
"type": "crime"
},
{
"hasTown": "CARSINGTON",
"hasStreet": "PEARN ROAD",
"id": "http://ns.valcri.org/data/locations#-1_8130219_52_46728",
"label": "http://ns.valcri.org/data/locations#-1_8130219_52_46728",
"type": "location",
"long": 52.467281341552734,
"lat": -1.8130218982696533
},
{
"id": "http://ns.valcri.org/data/crimes#crime162033325",
"hasLocation": "http://ns.valcri.org/data/locations#-1_8130219_52_46728",
"label": "162033325",
"type": "crime"
},
{
"victimOf": "http://ns.valcri.org/data/crimes#crime162033325",
"id": "http://ns.valcri.org/data/nominals#nominal9084944P",
"label": "9084944P",
"hasHome": "http://ns.valcri.org/data/locations#-1_8130219_52_46728",
"type": "nominal"
}
],
"links": [
{
"sourceType": "crime",
"sourceLabel": "162033325",
"targetLabel": "http://ns.valcri.org/data/locations#-1_8130219_52_46728",
"targetType": "location",
"id": "link0",
"source": "http://ns.valcri.org/data/crimes#crime162033325",
"target": "http://ns.valcri.org/data/locations#-1_8130219_52_46728"
},
{
"sourceType": "nominal",
"sourceLabel": "9084944P",
"targetLabel": "138640301",
"targetType": "crime",
"id": "link1",
"source": "http://ns.valcri.org/data/nominals#nominal9084944P",
"target": "http://ns.valcri.org/data/crimes#crime138640301"
},
{
"sourceType": "nominal",
"sourceLabel": "9084944P",
"targetLabel": "162033325",
"targetType": "crime",
"id": "link2",
"source": "http://ns.valcri.org/data/nominals#nominal9084944P",
"target": "http://ns.valcri.org/data/crimes#crime162033325"
},
{
"sourceType": "nominal",
"sourceLabel": "9084944P",
"targetLabel": "http://ns.valcri.org/data/locations#-1_8130219_52_46728",
"targetType": "location",
"id": "link3",
"source": "http://ns.valcri.org/data/nominals#nominal9084944P",
"target": "http://ns.valcri.org/data/locations#-1_8130219_52_46728"
}
]
}
{
"nodes": [
{
"isSolved": true,
"id": "http://ns.valcri.org/data/crimes#crime126467988",
"type": "crime"
},
{
"id": "http://ns.valcri.org/data/nominals#nominal7860282G",
"type": "nominal"
},
{
"isSolved": true,
"id": "http://ns.valcri.org/data/crimes#crime124047883",
"type": "crime"
},
{
"id": "http://ns.valcri.org/data/nominals#nominal82376756A",
"type": "nominal"
},
{
"isSolved": true,
"id": "http://ns.valcri.org/data/crimes#crime127780722",
"type": "crime"
},
{
"id": "http://ns.valcri.org/data/nominals#nominal8456992G",
"type": "nominal"
},
{
"isSolved": true,
"id": "http://ns.valcri.org/data/crimes#crime144160853",
"type": "crime"
},
{
"id": "http://ns.valcri.org/data/nominals#nominal87105490C",
"type": "nominal"
},
{
"isSolved": true,
"id": "http://ns.valcri.org/data/crimes#crime156633059",
"type": "crime"
},
{
"id": "http://ns.valcri.org/data/nominals#nominal93487729Z",
"type": "nominal"
}
],
"links": [
{
"sourceType": "crime",
"link": "similarity",
"targetType": "crime",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime162033325",
"target": "http://ns.valcri.org/data/crimes#crime126467988"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "nominal",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime126467988",
"target": "http://ns.valcri.org/data/nominals#nominal7860282G"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "crime",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime162033325",
"target": "http://ns.valcri.org/data/crimes#crime124047883"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "nominal",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime124047883",
"target": "http://ns.valcri.org/data/nominals#nominal82376756A"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "crime",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime162033325",
"target": "http://ns.valcri.org/data/crimes#crime127780722"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "nominal",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime127780722",
"target": "http://ns.valcri.org/data/nominals#nominal8456992G"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "crime",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime162033325",
"target": "http://ns.valcri.org/data/crimes#crime144160853"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "nominal",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime144160853",
"target": "http://ns.valcri.org/data/nominals#nominal87105490C"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "crime",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime162033325",
"target": "http://ns.valcri.org/data/crimes#crime156633059"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "nominal",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime156633059",
"target": "http://ns.valcri.org/data/nominals#nominal93487729Z"
}
]
}
{
"nodes": [
{
"isSolved": true,
"id": "http://ns.valcri.org/data/crimes#crime164040649",
"type": "crime"
},
{
"id": "http://ns.valcri.org/data/nominals#nominal51121931R",
"type": "nominal"
},
{
"isSolved": true,
"id": "http://ns.valcri.org/data/crimes#crime164042156",
"type": "crime"
},
{
"id": "http://ns.valcri.org/data/nominals#nominal86695964J",
"type": "nominal"
},
{
"isSolved": true,
"id": "http://ns.valcri.org/data/crimes#crime164043663",
"type": "crime"
},
{
"id": "http://ns.valcri.org/data/nominals#nominal5023564K",
"type": "nominal"
},
{
"isSolved": true,
"id": "http://ns.valcri.org/data/crimes#crime164056952",
"type": "crime"
},
{
"id": "http://ns.valcri.org/data/nominals#nominal621260409Y",
"type": "nominal"
},
{
"isSolved": true,
"id": "http://ns.valcri.org/data/crimes#crime164061610",
"type": "crime"
},
{
"id": "http://ns.valcri.org/data/nominals#nominal22538418L",
"type": "nominal"
}
],
"links": [
{
"sourceType": "crime",
"link": "similarity",
"targetType": "crime",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime138640301",
"target": "http://ns.valcri.org/data/crimes#crime164040649"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "nominal",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime164040649",
"target": "http://ns.valcri.org/data/nominals#nominal51121931R"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "crime",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime138640301",
"target": "http://ns.valcri.org/data/crimes#crime164042156"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "nominal",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime164042156",
"target": "http://ns.valcri.org/data/nominals#nominal86695964J"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "crime",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime138640301",
"target": "http://ns.valcri.org/data/crimes#crime164043663"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "nominal",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime164043663",
"target": "http://ns.valcri.org/data/nominals#nominal5023564K"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "crime",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime138640301",
"target": "http://ns.valcri.org/data/crimes#crime164056952"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "nominal",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime164056952",
"target": "http://ns.valcri.org/data/nominals#nominal621260409Y"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "crime",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime138640301",
"target": "http://ns.valcri.org/data/crimes#crime164061610"
},
{
"sourceType": "crime",
"link": "similarity",
"targetType": "nominal",
"id": "link",
"source": "http://ns.valcri.org/data/crimes#crime164061610",
"target": "http://ns.valcri.org/data/nominals#nominal22538418L"
}
]
}
#vis {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
overflow-x: hidden;
overflow-y: hidden;
}
.node .selected {
stroke: black;
}
(function() {
// ***************************************************
// Globals
// ***************************************************
var gBrushHolder;
var gBrush = null;
var gDraw = null;
var brushMode = false;
var brushing = false;
var shiftKey;
var node, link, n, l;
var brush = null;
var d3;
// ***************************************************
// Defaults
// ***************************************************
/**
* Define default variables here. Those are combined within
* the incoming user options which would overwrite the
* defaults.
**/
var defaults = {
data: {},
datalev2: {},
custnodedraw: null,
custlinkdraw: null
};
/**
* Static initialisation
**/
window.EntityGraph = {
instanceOf : function (d3version, options) {
// Publish instance
return new EntityGraph(d3version, options);
}
};
// ***************************************************
// Define Chart class and methods
// ***************************************************
function EntityGraph(d3version, options) {
d3 = d3version
this.options = extend({}, defaults, options != null ? options : {});
makeAccessor.call(this, this.options)
this.render = drawSelection.bind(this);
}
/**
* Define the D3 callback to render graph within the specified
* selection DOMs
*/
function drawSelection(selection) {
brush = d3.brush()
.on("start", brushstarted)
.on("brush", brushed)
.on("end", brushended);
d3.select('body').on('keydown', keydown);
d3.select('body').on('keyup', keyup);
_this = this;
selection.each(function() {
//TODO allow overwrite of width height
// Width/Height are sized to the selection's node size
var width = parseInt(d3.select(this).node().getBoundingClientRect().width, 10);
var height = parseInt(d3.select(this).node().getBoundingClientRect().height, 10);
// Retrieve local variables from class variables
var data = _this.data();
// Create base svg and add the data
var svg = d3.select(this).selectAll("svg").data([ data ]);
var gEnter = svg.enter().append('div').classed(
'svg-container', true).append('svg')
.attr("width", width)
.attr("height", height)
// Remove any previous graphs
gEnter.selectAll('.g-main').remove();
// Add our full zoomable graph group
var gMain = gEnter.append('g')
.classed('g-main', true);
var zoom = d3.zoom().on('zoom', zoomed)
gMain.call(zoom);
// Background, catches clicks to reset node selection
var rect = gMain.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', 'white')
.on('click', () => {
node.selectAll('.node-el').each(function(d) {
this.selected = false;
this.previouslySelected = false;
});
node.selectAll('.node-el').classed("selected", false);
});
// Container to draw our graph in
gDraw = gMain.append('g').attr("class", "drawContainer");
// Brush container
// The brush needs to go before the nodes so that it doesn't
// get called when the mouse is over a node
gBrushHolder = gDraw.append('g').attr("class", "brushHolder");
gBrush = null;
// Simulation forces
simulation = d3.forceSimulation()
.force("link", d3.forceLink()
.id(function(d) { return d.id; })
.distance(function(d) {
return 10;
})
)
.force("charge", d3.forceManyBody().strength(-120))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX(width/2))
.force("y", d3.forceY(height/2));
_this.update(this);
// ***************************************************
// Update methods
// Reacting automatically once the corresponding variable is set
// ***************************************************
_this.updateData = function() {
update();
};
_this.updateDatalev2 = function() {
_this.updateDataLevel2(this);
};
});
}
EntityGraph.prototype.updateDataLevel2 = function(selection)
{
_this = this;
var dataLev2 = _this.datalev2();
var data = _this.data();
// data.nodes = data.nodes.filter(function(n) {
// return n.level !== "level2"
// });
// data.links = data.links.filter(function(l) {
// return l.level !== "level2"
// });
var i;
for (i = 0; i < dataLev2.nodes.length; i++) {
data.nodes.push(dataLev2.nodes[i]);
}
for (i = 0; i < dataLev2.links.length; i++) {
data.links.push(dataLev2.links[i]);
}
_this.update(selection);
simulation.alpha(1).restart();
}
/**
* Draw the initial and update the existing graph with the full
* current data set
*/
EntityGraph.prototype.update = function(selection)
{
_this = this;
// Retrieve local variables from class variables
var data = _this.data();
var custnodedraw = _this.custnodedraw();
var custlinkdraw = _this.custlinkdraw();
// Update data
l = gDraw.selectAll(".link")
.data(data.links, function(d) {return d.source + "," + d.target});
n = gDraw.selectAll(".node").data(data.nodes, function(d) {return d.id})
// Apply general update pattern
exitLinks(l);
enterLinks(l, custlinkdraw);
exitNodes(n);
enterNodes(n, custnodedraw);
// Refernce to node and link selections
link = gDraw.selectAll(".link");
node = gDraw.selectAll(".node");
// Feed simulation with current data
simulation
.nodes(data.nodes)
.on("tick", ticked);
simulation.force("link")
.links(data.links);
}
/**
* Defines how new nodes are drawn
*/
function enterNodes(n, drawCallback) {
n = n.enter().append("g")
.attr("class", function(d){return "node"})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
n.each(function (d) {
if(drawCallback) {
drawCallback(this, d);
}
else {
d3.select(this).append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d) {return 10})
.classed("node-el", true);
}
});
n.selectAll('.avatar--circle__mark-main').classed("node-el", true);
}
/**
* Defines how new links are drawn
*/
function enterLinks(l, drawCallback) {
l = l.enter()
l.each(function(d) {
if(drawCallback) {
drawCallback(this, d);
}
else {
d3.select(this).insert("line", ".node")
.attr("class", "link")
.attr("stroke", function(d) { return d.color || '#999'; })
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
}
});
}
/**
* Removes nodes that have been removed using update id
*/
function exitNodes(n) {
n.exit().remove();
}
/**
* Removes links that have been removed using update id
*/
function exitLinks(l) {
l.exit().remove();
}
/**
* On zoom
*/
function zoomed() {
gDraw.attr("transform", d3.event.transform);
}
function brushstarted() {
// keep track of whether we're actively brushing so that we
// don't remove the brush on keyup in the middle of a selection
brushing = true;
node.selectAll('.node-el').each(function(d) {
this.previouslySelected = shiftKey && this.selected;
});
}
function brushed() {
if (!d3.event.sourceEvent) return;
if (!d3.event.selection) return;
var extent = d3.event.selection;
node.each(function(d){
d3.select(this).select('.node-el').classed("selected", function(){
return this.selected = this.previouslySelected ^
(extent[0][0] <= d.x && d.x < extent[1][0]
&& extent[0][1] <= d.y && d.y < extent[1][1]);
});
});
}
function brushended() {
if (!d3.event.sourceEvent) return;
if (!d3.event.selection) return;
if (!gBrush) return;
gBrush.call(brush.move, null);
if (!brushMode) {
// the shift key has been release before we ended our brushing
gBrush.remove();
gBrush = null;
}
brushing = false;
}
function keydown() {
shiftKey = d3.event.shiftKey;
if (shiftKey) {
// if we already have a brush, don't do anything
if (gBrush)
return;
brushMode = true;
if (!gBrush) {
gBrush = gBrushHolder.append('g');
gBrush.call(brush);
}
}
}
function ticked() {
// update node and line positions at every step of
// the force simulation
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function keyup() {
shiftKey = false;
brushMode = false;
if (!gBrush)
return;
if (!brushing) {
// only remove the brush if we're not actively brushing
// otherwise it'll be removed when the brushing ends
gBrush.remove();
gBrush = null;
}
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.9).restart();
if (!d.selected && !shiftKey) {
// if this node isn't selected, then we have to unselect every other node
node.selectAll('.node-el').classed("selected", function(p) { return this.selected = this.previouslySelected = false; });
}
d3.select(this).select('.node-el').classed("selected", function(p) { this.previouslySelected = this.selected; return d.selected = true; });
node.filter(function(d) { return d3.select(this).select('.node-el').classed('selected'); })
.each(function(d) {
d.fx = d.x;
d.fy = d.y;
})
}
function dragged(d) {
node.filter(function(d) { return d3.select(this).select('.node-el').classed('selected'); })
.each(function(d) {
d.fx += d3.event.dx;
d.fy += d3.event.dy;
})
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
node.filter(function(d) { return d3.select(this).select('.node-el').classed('selected'); })
.each(function(d) {
d.fx = null;
d.fy = null;
})
}
// ***************************************************
// Utility methods
// ***************************************************
function makeAccessor(properties) {
var _this = this;
for (var i in properties) {
(function (i) {
_this[i] = function (value) {
if(value === null || value === undefined)
{
return properties[i];
}
properties[i] = value;
updateFunc = ("update" + i.toCamelCase());
if (typeof _this[updateFunc] === 'function') _this[updateFunc]();
return _this;
}
})(i);
}
}
String.prototype.toCamelCase = function() {
return this.charAt(0).toUpperCase() + this.slice(1);
}
function extend() {
for (var i = 1; i < arguments.length; i++) {
for (var prop in arguments[i]) {
if (arguments[i].hasOwnProperty(prop)) {
arguments[0][prop] = arguments[i][prop];
}
}
}
return arguments[0];
}
})();
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Entity Graph</title>
<meta name="description" content="Entity Graph">
<meta name="author" content="Patrick Seidler">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="graph.css">
<script type="text/javascript">
window.onload = function() {
var chart;
var defs;
var avatars = {};
var id = "162033325";
var custNodeDrawCallback = function(selection, d)
{
var def = defs[d.type]
if(d.label == id) {
var mark = d.type == 'crime' && !d.isSolved
avatar.draw(def.color1, def.color2, def.label, d.id, 20, mark, selection);
return;
}
else if(!avatars.hasOwnProperty(d.type + d.isSolved))
{
var mark = d.type == 'crime' && !d.isSolved
avatars[d.type + d.isSolved] = avatar.draw(def.color1, def.color2, def.label, d.id, 10, mark, null);
}
var a = avatars[d.type + d.isSolved]
var i;
for(i = 0; i < a.length; i++) {
var savedAvatar = a[i].cloneNode(true)
savedAvatar.setAttributeNS(null, "id", d.id);
selection.appendChild(savedAvatar);
}
}
var custLinkDrawCallback = function(selection, d)
{
var line = d3.select(selection).insert("line", ".node");
line.attr("class", "link")
.attr("stroke", function(d) { return d.color || '#999'; })
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
if(d.link == 'similarity')
line.attr("stroke-dasharray", "5 5");
}
function display(data) {
defs = data.defs;
chart = EntityGraph
.instanceOf(d3)
.data(data)
.custnodedraw(custNodeDrawCallback)
.custlinkdraw(custLinkDrawCallback);
//Or like this
//var graph = Chart.instanceOf( { width : 500 } );
var svg = d3.select("#vis")
.call(chart.render);
}
d3.timeout(function() {
d3.json('data_add1.json', chart.datalev2);
d3.json('data_add2.json', chart.datalev2);
}, 3000);
d3.json('data.json', display);
};
</script>
</head>
<body>
<div class="container">
<div id="vis"></div>
<div class="footer">
</div>
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="d3v4-brush-lite.js"></script>
<script src="avatar.js"></script>
<script src="graph.js"></script>
</body>
</html>
/* HTML5 ✰ Boilerplate
* ==|== normalize ==========================================================
*/
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
audio:not([controls]) { display: none; }
[hidden] { display: none; }
html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
body { margin: 0; font-size: 13px; line-height: 1.231; }
body, button, input, select, textarea { font-family: sans-serif; color: #222; }
::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; }
::selection { background: #fe57a1; color: #fff; text-shadow: none; }
a { color: #00e; }
a:visited { color: #551a8b; }
a:hover { color: #06e; }
a:focus { outline: thin dotted; }
a:hover, a:active { outline: 0; }
abbr[title] { border-bottom: 1px dotted; }
b, strong { font-weight: bold; }
blockquote { margin: 1em 40px; }
dfn { font-style: italic; }
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
ins { background: #ff9; color: #000; text-decoration: none; }
mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
pre, code, kbd, samp { font-family: monospace, monospace; _font-family: 'courier new', monospace; font-size: 1em; }
pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
q { quotes: none; }
q:before, q:after { content: ""; content: none; }
small { font-size: 85%; }
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
sup { top: -0.5em; }
sub { bottom: -0.25em; }
ul, ol { margin: 1em 0; padding: 0 0 0 40px; }
dd { margin: 0 0 0 40px; }
nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; }
img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
svg:not(:root) { overflow: hidden; }
figure { margin: 0; }
form { margin: 0; }
fieldset { border: 0; margin: 0; padding: 0; }
label { cursor: pointer; }
legend { border: 0; *margin-left: -7px; padding: 0; }
button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
button, input { line-height: normal; *overflow: visible; }
table button, table input { *overflow: auto; }
button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; }
input[type="checkbox"], input[type="radio"] { box-sizing: border-box; }
input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
textarea { overflow: auto; vertical-align: top; resize: vertical; }
input:valid, textarea:valid { }
input:invalid, textarea:invalid { background-color: #f0dddd; }
table { border-collapse: collapse; border-spacing: 0; }
td { vertical-align: top; }
/* ==|== primary styles =====================================================
Author:
========================================================================== */
/* ==|== non-semantic helper classes ======================================== */
.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; }
.ir br { display: none; }
.hidden { display: none !important; visibility: hidden; }
.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
.invisible { visibility: hidden; }
.clearfix:before, .clearfix:after { content: ""; display: table; }
.clearfix:after { clear: both; }
.clearfix { zoom: 1; }
/* ==|== media queries ====================================================== */
@media only screen and (min-width: 480px) {
}
@media only screen and (min-width: 768px) {
}
/* ==|== print styles ======================================================= */
@media print {
* { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; }
a, a:visited { text-decoration: underline; }
a[href]:after { content: " (" attr(href) ")"; }
abbr[title]:after { content: " (" attr(title) ")"; }
.ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }
pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
thead { display: table-header-group; }
tr, img { page-break-inside: avoid; }
img { max-width: 100% !important; }
@page { margin: 0.5cm; }
p, h2, h3 { orphans: 3; widows: 3; }
h2, h3 { page-break-after: avoid; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment