Skip to content

Commit 0d5635f

Browse files
authored
Merge pull request #192 from EliotRagueneau/master
Fix d3v7: transition, scale, toCircle/Stick
2 parents 06f99ca + 550354c commit 0d5635f

8 files changed

Lines changed: 152 additions & 66 deletions

File tree

dist/complexviewer.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<link href="./css/style.css" rel="stylesheet" type='text/css'>
1111
<link href="./css/demo.css" rel="stylesheet" type='text/css'>
1212
<!--libraries-->
13+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.2.2/d3.v3.min.js"></script>
1314
<script src="./dist/complexviewer.js" type="text/javascript"></script>
1415
<!-- example data info -->
1516
<script src="./data/index.js" type="text/javascript"></script>

package.json

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "complexviewer",
3-
"version": "2.1.17",
3+
"version": "2.2.0",
44
"description": "A network visualisation that displays molecular interaction data, including detailed residue-level information such as binding sites. Used in EBI's Complex Portal and elsewhere.",
55
"author": {
66
"name": "Colin Combe",
@@ -22,6 +22,10 @@
2222
{
2323
"name": "Marine Dumousseau",
2424
"email": ""
25+
},
26+
{
27+
"name": "Eliot Ragueneau",
28+
"email": "eragueneau@ebi.ac.uk"
2529
}
2630
],
2731
"license": "Apache-2.0",
@@ -54,13 +58,19 @@
5458
"node": ">=14.0.0"
5559
},
5660
"dependencies": {
57-
"colorbrewer": "^1.3.0",
61+
"colorbrewer": "1.3.0",
5862
"core-js": "^3.15.2",
59-
"d3": "~3.5.5",
60-
"d3-scale-chromatic": "^1.5.0",
61-
"intersectionjs": "^1.0.1",
63+
"d3": "~7.1.1",
64+
"d3-ease": "~3.0.1",
65+
"d3-polygon": "~3.0.1",
66+
"d3-fetch": "~3.0.1",
67+
"d3-scale-chromatic": "~1.5.0",
68+
"d3-scale": "~4.0.2",
69+
"d3-interpolate": "^3.0.1",
70+
"d3-selection": "^3.0.0",
71+
"intersectionjs": "1.0.1",
6272
"jquery": "^3.6.0",
63-
"point2d": "^0.0.1",
73+
"point2d": "0.0.1",
6474
"rgb-color": "2.1.2"
6575
},
6676
"keywords": [

src/js/annotation-utils.js

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,47 +35,62 @@ export function fetchAnnotations(/*App*/ app, callback) {
3535

3636
function getUniProtFeatures(prot, callback) {
3737
const url = "https://www.ebi.ac.uk/proteins/api/proteins/" + prot.json.identifier.id.trim();
38-
d3.json(url, function (json) {
38+
d3.json(url).then(json => {
3939
let annotations = prot.annotationSets.get("UniprotKB");
4040
if (typeof annotations === "undefined") {
4141
annotations = [];
4242
prot.annotationSets.set("UniprotKB", annotations);
4343
}
4444
if (json) {
45-
const uniprotJsonFeatures = json.features.filter(function (ft) {
46-
return ft.type === "DOMAIN";
47-
});
48-
for (let feature of uniprotJsonFeatures) {
49-
const anno = new Annotation(feature.description, new SequenceDatum(null, feature.begin + "-" + feature.end));
45+
for (let feature of json.features.filter(ft => ft.type === "DOMAIN")) {
46+
const anno = new Annotation(feature.description, new SequenceDatum(null, `${feature.begin}-${feature.end}`));
5047
annotations.push(anno);
5148
}
5249
}
5350
callback();
5451
});
5552
}
5653

54+
5755
function getSuperFamFeatures(prot, callback) {
5856
const url = "https://supfam.mrc-lmb.cam.ac.uk/SUPERFAMILY/cgi-bin/das/up/features?segment=" + prot.json.identifier.id.trim();
59-
d3.xml(url, function (xml) {
57+
d3.xml(url).then(xml => {
6058
let annotations = prot.annotationSets.get("Superfamily");
6159
if (typeof annotations === "undefined") {
6260
annotations = [];
6361
prot.annotationSets.set("Superfamily", annotations);
6462
}
6563
if (xml) {
66-
const xmlDoc = new DOMParser().parseFromString(new XMLSerializer().serializeToString(xml), "text/xml");
67-
const xmlFeatures = xmlDoc.getElementsByTagName("FEATURE");
64+
const xmlFeatures = xml.getElementsByTagName("FEATURE");
6865
for (let xmlFeature of xmlFeatures) {
6966
const type = xmlFeature.getElementsByTagName("TYPE")[0]; //might need to watch for text nodes getting mixed in here
7067
const category = type.getAttribute("category");
7168
if (category === "miscellaneous") {
72-
const name = type.getAttribute("id");
69+
const name = decodeHTML(type.getAttribute("id"));
7370
const start = xmlFeature.getElementsByTagName("START")[0].textContent;
7471
const end = xmlFeature.getElementsByTagName("END")[0].textContent;
75-
annotations.push(new Annotation(name, new SequenceDatum(null, start + "-" + end)));
72+
annotations.push(new Annotation(name, new SequenceDatum(null, `${start}-${end}`)));
7673
}
7774
}
7875
}
7976
callback();
8077
});
81-
}
78+
}
79+
80+
81+
function decodeHTML(text) {
82+
return text.replace(/&([^;]+);/gm, (match, entity) => entities[entity] || match)
83+
}
84+
85+
const entities = {
86+
'amp': '&',
87+
'apos': '\'',
88+
'#x27': '\'',
89+
'#x2F': '/',
90+
'#39': '\'',
91+
'#47': '/',
92+
'lt': '<',
93+
'gt': '>',
94+
'nbsp': ' ',
95+
'quot': '"'
96+
}

src/js/app.js

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import "../css/xinet.css";
22
import packageInfo from "../../package.json";
33
import * as d3 from "d3";
4+
import {scaleOrdinal} from "d3-scale";
45
import * as d3_chromatic from "d3-scale-chromatic";
56
import * as cola from "./cola";
67
import * as Rgb_color from "rgb-color";
@@ -21,7 +22,7 @@ export class App {
2122
//avoids prob with 'save - web page complete'
2223
this.el.textContent = ""; //https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript
2324
this.maxCountInitiallyExpanded = maxCountInitiallyExpanded;
24-
this.d3cola = cola.d3adaptor().groupCompactness(Number.MIN_VALUE).avoidOverlaps(true); //1e-5
25+
this.d3cola = cola.d3adaptor(d3).groupCompactness(Number.MIN_VALUE).avoidOverlaps(true); //1e-5
2526

2627
const customMenuSel = d3.select(this.el)
2728
.append("div").classed("custom-menu-margin", true)
@@ -31,7 +32,7 @@ export class App {
3132
const self = this;
3233
const collapse = customMenuSel.append("li").classed("collapse", true); //.append("button");
3334
collapse.text("Collapse");
34-
collapse[0][0].onclick = function (evt) {
35+
collapse.node().onclick = function (evt) {
3536
self.collapseProtein(evt);
3637
};
3738
const scaleButtonsListItemSel = customMenuSel.append("li").text("Scale: ");
@@ -54,7 +55,7 @@ export class App {
5455
})
5556
.attr("name", "scaleButtons")
5657
.attr("type", "radio")
57-
.on("change", function (d) {
58+
.on("change", function (e, d) {
5859
self.preventDefaultsAndStopPropagation(d);
5960
self.contextMenuProt.setStickScale(d, self.contextMenuPoint);
6061
});
@@ -75,27 +76,13 @@ export class App {
7576
this.svgElement.classList.add("complexViewerSVG");
7677

7778
//add listeners
78-
this.svgElement.onmousedown = function (evt) {
79-
self.mouseDown(evt);
80-
};
81-
this.svgElement.onmousemove = function (evt) {
82-
self.move(evt);
83-
};
84-
this.svgElement.onmouseup = function (evt) {
85-
self.mouseUp(evt);
86-
};
87-
this.svgElement.onmouseout = function (evt) {
88-
self.mouseOut(evt);
89-
};
90-
this.svgElement.ontouchstart = function (evt) {
91-
self.touchStart(evt);
92-
};
93-
this.svgElement.ontouchmove = function (evt) {
94-
self.move(evt);
95-
};
96-
this.svgElement.ontouchend = function (evt) {
97-
self.mouseUp(evt);
98-
};
79+
this.svgElement.onmousedown = evt => self.mouseDown(evt);
80+
this.svgElement.onmousemove = evt => self.move(evt);
81+
this.svgElement.onmouseup = evt => self.mouseUp(evt);
82+
this.svgElement.onmouseout = evt => self.mouseOut(evt);
83+
this.svgElement.ontouchstart = evt => self.touchStart(evt);
84+
this.svgElement.ontouchmove = evt => self.move(evt);
85+
this.svgElement.ontouchend = evt => self.mouseUp(evt);
9986
this.lastMouseUp = new Date().getTime();
10087

10188
this.el.oncontextmenu = function (evt) {
@@ -230,7 +217,7 @@ export class App {
230217
hsl.l = 0.9;
231218
complexColors.push(hsl + "");
232219
}
233-
NaryLink.naryColors = d3.scale.ordinal().range(complexColors);
220+
NaryLink.naryColors = scaleOrdinal().range(complexColors);
234221

235222
this.z = 1;
236223
this.hideTooltip();
@@ -648,10 +635,31 @@ export class App {
648635
}
649636
//choose appropriate color scheme
650637
let colorScheme;
651-
if (categories.size < 11) {
652-
colorScheme = d3.scale.ordinal().range(d3_chromatic.schemeTableau10);
638+
if (categories.size <= 10) {
639+
colorScheme = scaleOrdinal().range(d3_chromatic.schemeTableau10);
653640
} else {
654-
colorScheme = d3.scale.category20();
641+
colorScheme = d3.scaleOrdinal().range([
642+
"#38cae3",
643+
"#d4582b",
644+
"#7d5fd7",
645+
"#7cd352",
646+
"#ce4bbb",
647+
"#5aa33c",
648+
"#93539d",
649+
"#d2c33b",
650+
"#5c83d4",
651+
"#e19a46",
652+
"#d891d7",
653+
"#65da9a",
654+
"#9d772f",
655+
"#d43f4c",
656+
"#4db186",
657+
"#cf4b7e",
658+
"#477c3a",
659+
"#c46d5c",
660+
"#b6c671",
661+
"#798126"
662+
]); // Generated from https://medialab.github.io/iwanthue/
655663
}
656664

657665
const self = this;

src/js/transform.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Taken from https://stackoverflow.com/a/38230545/11028828
2+
export function transform(transform) {
3+
// Create a dummy g for calculation purposes only. This will never
4+
// be appended to the DOM and will be discarded once this function
5+
// returns.
6+
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
7+
8+
// Set the transform attribute to the provided string value.
9+
g.setAttributeNS(null, "transform", transform);
10+
11+
// consolidate the SVGTransformList containing all transformations
12+
// to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get
13+
// its SVGMatrix.
14+
const matrix = g.transform.baseVal.consolidate().matrix;
15+
16+
// Below calculations are taken and adapted from the private function
17+
// transform/decompose.js of D3's module d3-interpolate.
18+
let {a, b, c, d, e, f} = matrix; // ES6, if this doesn't work, use below assignment
19+
// var a=matrix.a, b=matrix.b, c=matrix.c, d=matrix.d, e=matrix.e, f=matrix.f; // ES5
20+
let scaleX = Math.sqrt(a * a + b * b),
21+
scaleY = Math.sqrt(c * c + d * d),
22+
skewX = a * c + b * d;
23+
if (scaleX) {
24+
a /= scaleX;
25+
b /= scaleX;
26+
}
27+
if (skewX) {
28+
c -= a * skewX;
29+
d -= b * skewX;
30+
}
31+
if (scaleY) {
32+
c /= scaleY;
33+
d /= scaleY;
34+
skewX /= scaleY;
35+
}
36+
if (a * d < b * c) {
37+
a = -a;
38+
b = -b;
39+
skewX = -skewX;
40+
scaleX = -scaleX;
41+
}
42+
return {
43+
translate: [e,f],
44+
translateX: e,
45+
translateY: f,
46+
rotate: Math.atan2(b, a) * 180 / Math.PI,
47+
skewX: Math.atan(skewX) * 180 / Math.PI,
48+
scaleX: scaleX,
49+
scaleY: scaleY
50+
};
51+
}

src/js/viz/interactor/polymer.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import * as d3 from "d3"; // transitions and other stuff
2+
import {transform} from "../../transform";
3+
import {easeCubicInOut} from "d3-ease";
24
import {rotatePointAboutPoint} from "../../geom";
35
import {svgns} from "../../svgns";
46
import {Interactor} from "./interactor";
@@ -45,8 +47,8 @@ export class Polymer extends Interactor {
4547
scale() {
4648
const protLength = (this.size) * this.stickZoom;
4749
if (this.expanded) {
48-
const labelTransform = d3.transform(this.labelSVG.getAttribute("transform"));
49-
const k = this.app.svgElement.createSVGMatrix().rotate(labelTransform.rotate)
50+
const labelTransform = transform(this.labelSVG.getAttribute("transform"));
51+
const k = this.app.svgElement.createSVGMatrix()
5052
.translate((-(((this.size / 2) * this.stickZoom) + (this.nTermFeatures.length > 0 ? 25 : 10))), this.labelY); //.scale(z).translate(-c.x, -c.y);
5153
this.labelSVG.transform.baseVal.initialize(this.app.svgElement.createSVGTransformFromMatrix(k));
5254
this.updateAnnotationRectanglesNoTransition();
@@ -159,7 +161,6 @@ export class Polymer extends Interactor {
159161
}
160162

161163
toCircle(transition = true, svgP) {
162-
163164
if (!svgP) {
164165
const width = this.app.svgElement.parentNode.clientWidth;
165166
const ctm = this.app.container.getCTM().inverse();
@@ -196,7 +197,7 @@ export class Polymer extends Interactor {
196197
.duration(transitionTime);
197198

198199
const stickZoomInterpol = d3.interpolate(this.stickZoom, 0);
199-
const labelTransform = d3.transform(this.labelSVG.getAttribute("transform"));
200+
const labelTransform = transform(this.labelSVG.getAttribute("transform"));
200201
const labelStartPoint = labelTransform.translate[0];
201202
const labelTranslateInterpol = d3.interpolate(labelStartPoint, -(r + 5));
202203

@@ -209,15 +210,15 @@ export class Polymer extends Interactor {
209210

210211
const self = this;
211212
d3.select(this.ticks).transition().attr("opacity", 0).duration(transitionTime / 4)
212-
.each("end",
213+
.on("end",
213214
function () {
214215
d3.select(this).selectAll("*").remove();
215216
}
216217
);
217218

218219
const originalStickZoom = this.stickZoom;
219220
const originalRotation = this.rotation;
220-
const cubicInOut = d3.ease("cubic-in-out");
221+
const cubicInOut = easeCubicInOut;
221222
if (transition) {
222223
for (let [annotationType, annotations] of this.annotationSets) {
223224
if (this.app.annotationSetsShown.get(annotationType) === true) {
@@ -242,16 +243,15 @@ export class Polymer extends Interactor {
242243
}
243244
}
244245
}
245-
d3.timer(function (elapsed) {
246-
return update(elapsed / transitionTime);
246+
const t = d3.timer(function (elapsed) {
247+
if (update(elapsed / transitionTime)) t.stop();
247248
});
248249
} else {
249250
update(1);
250251
}
251252

252253
function update(interp) {
253-
const labelTransform = d3.transform(self.labelSVG.getAttribute("transform"));
254-
const k = self.app.svgElement.createSVGMatrix().rotate(labelTransform.rotate).translate(labelTranslateInterpol(cubicInOut(interp)), self.labelY); //.scale(z).translate(-c.x, -c.y);
254+
const k = self.app.svgElement.createSVGMatrix().translate(labelTranslateInterpol(cubicInOut(interp)), self.labelY); //.scale(z).translate(-c.x, -c.y);
255255
self.labelSVG.transform.baseVal.initialize(self.app.svgElement.createSVGTransformFromMatrix(k));
256256

257257
if (xInterpol !== null) {
@@ -339,7 +339,7 @@ export class Polymer extends Interactor {
339339

340340

341341
const self = this;
342-
const cubicInOut = d3.ease("cubic-in-out");
342+
const cubicInOut = easeCubicInOut;
343343
if (transition) {
344344
for (let [annotationType, annotations] of this.annotationSets) {
345345
if (this.app.annotationSetsShown.get(annotationType) === true) {
@@ -374,16 +374,16 @@ export class Polymer extends Interactor {
374374
}
375375
}
376376
}
377-
d3.timer(function (elapsed) {
378-
return update(elapsed / transitionTime);
377+
const t = d3.timer(function (elapsed) {
378+
if (update(elapsed / transitionTime)) t.stop();
379379
});
380380
} else {
381381
update(1);
382382
}
383383

384384
function update(interp) {
385-
const labelTransform = d3.transform(self.labelSVG.getAttribute("transform"));
386-
const k = self.app.svgElement.createSVGMatrix().rotate(labelTransform.rotate).translate(labelTranslateInterpol(cubicInOut(interp)), self.labelY); //.scale(z).translate(-c.x, -c.y);
385+
const labelTransform = transform(self.labelSVG.getAttribute("transform"));
386+
const k = self.app.svgElement.createSVGMatrix().rotate(labelTransform.rotate).translate(labelTranslateInterpol(cubicInOut(interp)), self.labelY)
387387
self.labelSVG.transform.baseVal.initialize(self.app.svgElement.createSVGTransformFromMatrix(k));
388388

389389
const currentLength = lengthInterpol(cubicInOut(interp));

0 commit comments

Comments
 (0)