Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/48 highlight edges #58

Merged
merged 10 commits into from
Mar 20, 2016
1 change: 1 addition & 0 deletions .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"beforeEach": false,
"it": false,
"expect": false,
"spyOn": false,
"TweenLite": false,
"Linear": false,
"Elastic": false,
Expand Down
26 changes: 18 additions & 8 deletions src/edge/edge.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
<template>

<svg>
<line class="edge" id="edge-${displayEdge.source.id}-${displayEdge.target.id}"
x1="${displayEdge.source.x}"
y1="${displayEdge.source.y}"
x2="${displayEdge.target.x}"
y2="${displayEdge.target.y}"
<defs>
<path id="${edgePathId}"
d="${edgePathForText}"/>
</defs>
<path class="edge" id="edge-${displayEdge.source.id}-${displayEdge.target.id}"
d="M${displayEdge.source.x} ${displayEdge.source.y} L${displayEdge.target.x} ${displayEdge.target.y}"
stroke-dasharray="${edgeStrokeDashArray()}"
stroke="${edgeColor()}"
stroke-width="${edgeWidth()}">
</line>
stroke="${edgeColor}"
stroke-width="${edgeWidth}"/>
<text
font-size="14"
x="40"
y="100"
stroke="#787878">
<textPath xlink:href="#${edgePathId}">
${displayEdge.edgeKind}
</textPath>
</text>

</svg>

</template>
80 changes: 70 additions & 10 deletions src/edge/edge.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import {inject, customElement, bindable, containerless} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';
import $ from 'jquery';
import 'npm:gsap@1.18.0/src/minified/TweenMax.min.js';
import {EdgeStyle} from './edge-style';

const EDGE_ANIMATE_DURATION = 0.5;
const EDGE_ANIMATE_EASE = Power1.easeIn;
const EDGE_ANIMATE_DELAY = 1;

const HIGHLIGHT_COLOR_SOURCE = 'orange';
const HIGHLIGHT_COLOR_TARGET = 'rgb(60, 234, 245)';
const HIGHLIGHT_WIDTH = 5;

@customElement('edge')
@containerless
@inject(Element, EdgeStyle)
@inject(Element, EventAggregator, EdgeStyle)
export class Edge {
@bindable data;
@bindable sourcex;
@bindable sourcey;
@bindable targetx;
@bindable targety;

constructor(element, edgeStyle) {
constructor(element, eventAggregator, edgeStyle) {
this.element = element;
this.eventAggregator = eventAggregator;
this.edgeStyle = edgeStyle;
}

Expand All @@ -27,23 +34,35 @@ export class Edge {
}
}

get edgePathId() {
return `#edge-path-${this.displayEdge.source.id}-${this.displayEdge.target.id}`;
}

// FIXME handle upside down https://github.com/CANVE/canve-viz/issues/54
get edgePathForText() {
return `M${this.displayEdge.source.x} ${this.displayEdge.source.y} L${this.displayEdge.target.x} ${this.displayEdge.target.y}`;
}

// FIXME Now that have switched from line to path, redo animation to animate path, GSAP may have plugin for this
attached() {
// Selector
this.$edge = $(`#edge-${this.displayEdge.source.id}-${this.displayEdge.target.id}`);

// Animate edge target from source point
TweenLite.from(this.$edge[0], EDGE_ANIMATE_DURATION, {
attr: {x2: this.displayEdge.source.x, y2: this.displayEdge.source.y},
ease: EDGE_ANIMATE_EASE
ease: EDGE_ANIMATE_EASE,
delay: EDGE_ANIMATE_DELAY
});

this.registerEvents();
}

sourcexChanged(newVal, oldVal) {
if (newVal && oldVal) {
TweenLite.from(this.$edge[0], EDGE_ANIMATE_DURATION, {
attr: {x1: oldVal},
ease: EDGE_ANIMATE_EASE,
ease: EDGE_ANIMATE_EASE
});
}
}
Expand All @@ -52,7 +71,7 @@ export class Edge {
if (newVal && oldVal) {
TweenLite.from(this.$edge[0], EDGE_ANIMATE_DURATION, {
attr: {y1: oldVal},
ease: EDGE_ANIMATE_EASE,
ease: EDGE_ANIMATE_EASE
});
}
}
Expand Down Expand Up @@ -83,13 +102,54 @@ export class Edge {
return this.edgeStyle.strokeDash(this.displayEdge.edgeKind);
}

edgeColor() {
return this.edgeStyle.strokeColor(this.displayEdge.edgeKind);
get edgeColor() {
if (this.displayEdge.highlightSource) {
return HIGHLIGHT_COLOR_SOURCE;
} else if (this.displayEdge.highlightTarget) {
return HIGHLIGHT_COLOR_TARGET;
} else {
return this.edgeStyle.strokeColor(this.displayEdge.edgeKind);
}
}

get edgeWidth() {
if (this.displayEdge.highlightSource || this.displayEdge.highlightTarget) {
return HIGHLIGHT_WIDTH;
} else {
return 1;
}
}

registerEvents() {
this.nodeHoverInSub = this.eventAggregator.subscribe('node.hover.in', this.highlightEdges.bind(this));
this.nodeHoverOutSub = this.eventAggregator.subscribe('node.hover.out', this.unHighlightEdges.bind(this));
}

highlightEdges(node) {
if (this.displayEdge.source.id === node.id) {
this.displayEdge.highlightSource = true;
}
if (this.displayEdge.target.id === node.id) {
this.displayEdge.highlightTarget = true;
}
}

unHighlightEdges(node) {
if (this.displayEdge.source.id === node.id) {
this.displayEdge.highlightSource = false;
}
if (this.displayEdge.target.id === node.id) {
this.displayEdge.highlightTarget = false;
}
}

detached() {
this.deregisterEvents();
}

// This may vary with highlight status?
edgeWidth() {
return 1;
deregisterEvents() {
this.nodeHoverInSub.dispose();
this.nodeHoverOutSub.dispose();
}

}
19 changes: 17 additions & 2 deletions src/node/node.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {inject, customElement, bindable, containerless, TaskQueue} from 'aurelia-framework';
import {BindingEngine} from 'aurelia-binding';
import {EventAggregator} from 'aurelia-event-aggregator';
import $ from 'jquery';
import 'npm:gsap@1.18.0/src/minified/TweenMax.min.js';
import d3 from 'd3';
Expand All @@ -9,13 +10,14 @@ import {fillColor} from './node-style';

@customElement('node')
@containerless()
@inject(Element, BindingEngine, GraphTextService, TaskQueue, NodeCalculator)
@inject(Element, BindingEngine, EventAggregator, GraphTextService, TaskQueue, NodeCalculator)
export class Node {
@bindable data;

constructor(element, bindingEngine, graphTextService, taskQueue, nodeCalculator) {
constructor(element, bindingEngine, eventAggregator, graphTextService, taskQueue, nodeCalculator) {
this.element = element;
this.bindingEngine = bindingEngine;
this.eventAggregator = eventAggregator;
this.graphTextService = graphTextService;
this.taskQueue = taskQueue;
this.nodeCalculator = nodeCalculator;
Expand Down Expand Up @@ -81,6 +83,9 @@ export class Node {
this.yChangeSub = this.bindingEngine.propertyObserver(this.displayNode, 'y').subscribe((newValue, oldValue) => {
this.animateY(this.$node, oldValue, newValue);
});

this.$node.on('mouseenter.node', this.handleMouseIn.bind(this));
this.$node.on('mouseleave.node', this.handleMouseOut.bind(this));
}

animateX(selector, fromPos, toPos) {
Expand All @@ -97,6 +102,14 @@ export class Node {
);
}

handleMouseIn() {
this.eventAggregator.publish('node.hover.in', this.displayNode);
}

handleMouseOut() {
this.eventAggregator.publish('node.hover.out', this.displayNode);
}

/**
* Toggle node selected status
*/
Expand Down Expand Up @@ -131,6 +144,8 @@ export class Node {
unregisterEvents() {
this.xChangeSub.dispose();
this.yChangeSub.dispose();
this.$node.off('mouseenter.node');
this.$node.off('mouseleave.node');
}

}
13 changes: 12 additions & 1 deletion styles/edge.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
/* FIXME https://github.com/CANVE/canve-viz/issues/55 */
/* Animate edges but this breaks dash styling to indicate edgeKind */
.edge {
stroke-opacity: .6
stroke-opacity: .6;
/*stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: dash 5s linear forwards;*/
}

@keyframes dash {
to {
stroke-dashoffset: 0;
}
}
87 changes: 87 additions & 0 deletions test/unit/edge/edge-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {Edge} from '../../../src/edge/edge';

describe('Edge', function() {
const edgeStyleColor = 'grey';
let edge, mockElement, mockEventAggregator, mockEdgeStyle;

beforeEach( () => {
mockElement = {};
mockEventAggregator = {};
mockEdgeStyle = {
strokeColor: function() { return edgeStyleColor; }
};
edge = new Edge(mockElement, mockEventAggregator, mockEdgeStyle);
});

describe('edgeColor', () => {

it('Returns highlight color for source', () => {
// Given
let displayEdge = { edgeKind: 'extends', highlightSource: true };

// When
edge.dataChanged(displayEdge);

// Then
expect(edge.edgeColor).toEqual('orange');
});

it('Returns highlight color for target', () => {
// Given
let displayEdge = { edgeKind: 'extends', highlightTarget: true };

// When
edge.dataChanged(displayEdge);

// Then
expect(edge.edgeColor).toEqual('rgb(60, 234, 245)');
});

it('Returns color based on edge style', () => {
// Given
spyOn(mockEdgeStyle, 'strokeColor').and.callThrough();
let displayEdge = { edgeKind: 'extends'};

// When
edge.dataChanged(displayEdge);

// Then
expect(edge.edgeColor).toEqual(edgeStyleColor);
expect(mockEdgeStyle.strokeColor).toHaveBeenCalled();
});

});

describe('highlightEdges', () => {

it('Sets edge to be highlighted as source', () => {
// Given
let node = { id: '111'};
let displayEdge = { source: {id: '111'}, target: {id: '222'}};

// When
edge.dataChanged(displayEdge);
edge.highlightEdges(node);

// Then
expect(edge.displayEdge.highlightSource).toBe(true);
expect(edge.displayEdge.highlightTarget).toBeUndefined();
});

it('Sets edge to be highlighted as target', () => {
// Given
let node = { id: '222'};
let displayEdge = { source: {id: '111'}, target: {id: '222'}};

// When
edge.dataChanged(displayEdge);
edge.highlightEdges(node);

// Then
expect(edge.displayEdge.highlightTarget).toBe(true);
expect(edge.displayEdge.highlightSource).toBeUndefined();
});

});

});