Skip to content

Commit

Permalink
Merge pull request #192 from EliotRagueneau/master
Browse files Browse the repository at this point in the history
Fix d3v7: transition, scale, toCircle/Stick
  • Loading branch information
colin-combe authored Nov 10, 2022
2 parents 06f99ca + 550354c commit 0d5635f
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 66 deletions.
6 changes: 3 additions & 3 deletions dist/complexviewer.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<link href="./css/style.css" rel="stylesheet" type='text/css'>
<link href="./css/demo.css" rel="stylesheet" type='text/css'>
<!--libraries-->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.2.2/d3.v3.min.js"></script>
<script src="./dist/complexviewer.js" type="text/javascript"></script>
<!-- example data info -->
<script src="./data/index.js" type="text/javascript"></script>
Expand Down
22 changes: 16 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "complexviewer",
"version": "2.1.17",
"version": "2.2.0",
"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.",
"author": {
"name": "Colin Combe",
Expand All @@ -22,6 +22,10 @@
{
"name": "Marine Dumousseau",
"email": ""
},
{
"name": "Eliot Ragueneau",
"email": "eragueneau@ebi.ac.uk"
}
],
"license": "Apache-2.0",
Expand Down Expand Up @@ -54,13 +58,19 @@
"node": ">=14.0.0"
},
"dependencies": {
"colorbrewer": "^1.3.0",
"colorbrewer": "1.3.0",
"core-js": "^3.15.2",
"d3": "~3.5.5",
"d3-scale-chromatic": "^1.5.0",
"intersectionjs": "^1.0.1",
"d3": "~7.1.1",
"d3-ease": "~3.0.1",
"d3-polygon": "~3.0.1",
"d3-fetch": "~3.0.1",
"d3-scale-chromatic": "~1.5.0",
"d3-scale": "~4.0.2",
"d3-interpolate": "^3.0.1",
"d3-selection": "^3.0.0",
"intersectionjs": "1.0.1",
"jquery": "^3.6.0",
"point2d": "^0.0.1",
"point2d": "0.0.1",
"rgb-color": "2.1.2"
},
"keywords": [
Expand Down
39 changes: 27 additions & 12 deletions src/js/annotation-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,47 +35,62 @@ export function fetchAnnotations(/*App*/ app, callback) {

function getUniProtFeatures(prot, callback) {
const url = "https://www.ebi.ac.uk/proteins/api/proteins/" + prot.json.identifier.id.trim();
d3.json(url, function (json) {
d3.json(url).then(json => {
let annotations = prot.annotationSets.get("UniprotKB");
if (typeof annotations === "undefined") {
annotations = [];
prot.annotationSets.set("UniprotKB", annotations);
}
if (json) {
const uniprotJsonFeatures = json.features.filter(function (ft) {
return ft.type === "DOMAIN";
});
for (let feature of uniprotJsonFeatures) {
const anno = new Annotation(feature.description, new SequenceDatum(null, feature.begin + "-" + feature.end));
for (let feature of json.features.filter(ft => ft.type === "DOMAIN")) {
const anno = new Annotation(feature.description, new SequenceDatum(null, `${feature.begin}-${feature.end}`));
annotations.push(anno);
}
}
callback();
});
}


function getSuperFamFeatures(prot, callback) {
const url = "https://supfam.mrc-lmb.cam.ac.uk/SUPERFAMILY/cgi-bin/das/up/features?segment=" + prot.json.identifier.id.trim();
d3.xml(url, function (xml) {
d3.xml(url).then(xml => {
let annotations = prot.annotationSets.get("Superfamily");
if (typeof annotations === "undefined") {
annotations = [];
prot.annotationSets.set("Superfamily", annotations);
}
if (xml) {
const xmlDoc = new DOMParser().parseFromString(new XMLSerializer().serializeToString(xml), "text/xml");
const xmlFeatures = xmlDoc.getElementsByTagName("FEATURE");
const xmlFeatures = xml.getElementsByTagName("FEATURE");
for (let xmlFeature of xmlFeatures) {
const type = xmlFeature.getElementsByTagName("TYPE")[0]; //might need to watch for text nodes getting mixed in here
const category = type.getAttribute("category");
if (category === "miscellaneous") {
const name = type.getAttribute("id");
const name = decodeHTML(type.getAttribute("id"));
const start = xmlFeature.getElementsByTagName("START")[0].textContent;
const end = xmlFeature.getElementsByTagName("END")[0].textContent;
annotations.push(new Annotation(name, new SequenceDatum(null, start + "-" + end)));
annotations.push(new Annotation(name, new SequenceDatum(null, `${start}-${end}`)));
}
}
}
callback();
});
}
}


function decodeHTML(text) {
return text.replace(/&([^;]+);/gm, (match, entity) => entities[entity] || match)
}

const entities = {
'amp': '&',
'apos': '\'',
'#x27': '\'',
'#x2F': '/',
'#39': '\'',
'#47': '/',
'lt': '<',
'gt': '>',
'nbsp': ' ',
'quot': '"'
}
64 changes: 36 additions & 28 deletions src/js/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "../css/xinet.css";
import packageInfo from "../../package.json";
import * as d3 from "d3";
import {scaleOrdinal} from "d3-scale";
import * as d3_chromatic from "d3-scale-chromatic";
import * as cola from "./cola";
import * as Rgb_color from "rgb-color";
Expand All @@ -21,7 +22,7 @@ export class App {
//avoids prob with 'save - web page complete'
this.el.textContent = ""; //https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript
this.maxCountInitiallyExpanded = maxCountInitiallyExpanded;
this.d3cola = cola.d3adaptor().groupCompactness(Number.MIN_VALUE).avoidOverlaps(true); //1e-5
this.d3cola = cola.d3adaptor(d3).groupCompactness(Number.MIN_VALUE).avoidOverlaps(true); //1e-5

const customMenuSel = d3.select(this.el)
.append("div").classed("custom-menu-margin", true)
Expand All @@ -31,7 +32,7 @@ export class App {
const self = this;
const collapse = customMenuSel.append("li").classed("collapse", true); //.append("button");
collapse.text("Collapse");
collapse[0][0].onclick = function (evt) {
collapse.node().onclick = function (evt) {
self.collapseProtein(evt);
};
const scaleButtonsListItemSel = customMenuSel.append("li").text("Scale: ");
Expand All @@ -54,7 +55,7 @@ export class App {
})
.attr("name", "scaleButtons")
.attr("type", "radio")
.on("change", function (d) {
.on("change", function (e, d) {
self.preventDefaultsAndStopPropagation(d);
self.contextMenuProt.setStickScale(d, self.contextMenuPoint);
});
Expand All @@ -75,27 +76,13 @@ export class App {
this.svgElement.classList.add("complexViewerSVG");

//add listeners
this.svgElement.onmousedown = function (evt) {
self.mouseDown(evt);
};
this.svgElement.onmousemove = function (evt) {
self.move(evt);
};
this.svgElement.onmouseup = function (evt) {
self.mouseUp(evt);
};
this.svgElement.onmouseout = function (evt) {
self.mouseOut(evt);
};
this.svgElement.ontouchstart = function (evt) {
self.touchStart(evt);
};
this.svgElement.ontouchmove = function (evt) {
self.move(evt);
};
this.svgElement.ontouchend = function (evt) {
self.mouseUp(evt);
};
this.svgElement.onmousedown = evt => self.mouseDown(evt);
this.svgElement.onmousemove = evt => self.move(evt);
this.svgElement.onmouseup = evt => self.mouseUp(evt);
this.svgElement.onmouseout = evt => self.mouseOut(evt);
this.svgElement.ontouchstart = evt => self.touchStart(evt);
this.svgElement.ontouchmove = evt => self.move(evt);
this.svgElement.ontouchend = evt => self.mouseUp(evt);
this.lastMouseUp = new Date().getTime();

this.el.oncontextmenu = function (evt) {
Expand Down Expand Up @@ -230,7 +217,7 @@ export class App {
hsl.l = 0.9;
complexColors.push(hsl + "");
}
NaryLink.naryColors = d3.scale.ordinal().range(complexColors);
NaryLink.naryColors = scaleOrdinal().range(complexColors);

this.z = 1;
this.hideTooltip();
Expand Down Expand Up @@ -648,10 +635,31 @@ export class App {
}
//choose appropriate color scheme
let colorScheme;
if (categories.size < 11) {
colorScheme = d3.scale.ordinal().range(d3_chromatic.schemeTableau10);
if (categories.size <= 10) {
colorScheme = scaleOrdinal().range(d3_chromatic.schemeTableau10);
} else {
colorScheme = d3.scale.category20();
colorScheme = d3.scaleOrdinal().range([
"#38cae3",
"#d4582b",
"#7d5fd7",
"#7cd352",
"#ce4bbb",
"#5aa33c",
"#93539d",
"#d2c33b",
"#5c83d4",
"#e19a46",
"#d891d7",
"#65da9a",
"#9d772f",
"#d43f4c",
"#4db186",
"#cf4b7e",
"#477c3a",
"#c46d5c",
"#b6c671",
"#798126"
]); // Generated from https://medialab.github.io/iwanthue/
}

const self = this;
Expand Down
51 changes: 51 additions & 0 deletions src/js/transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Taken from https://stackoverflow.com/a/38230545/11028828
export function transform(transform) {
// Create a dummy g for calculation purposes only. This will never
// be appended to the DOM and will be discarded once this function
// returns.
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");

// Set the transform attribute to the provided string value.
g.setAttributeNS(null, "transform", transform);

// consolidate the SVGTransformList containing all transformations
// to a single SVGTransform of type SVG_TRANSFORM_MATRIX and get
// its SVGMatrix.
const matrix = g.transform.baseVal.consolidate().matrix;

// Below calculations are taken and adapted from the private function
// transform/decompose.js of D3's module d3-interpolate.
let {a, b, c, d, e, f} = matrix; // ES6, if this doesn't work, use below assignment
// var a=matrix.a, b=matrix.b, c=matrix.c, d=matrix.d, e=matrix.e, f=matrix.f; // ES5
let scaleX = Math.sqrt(a * a + b * b),
scaleY = Math.sqrt(c * c + d * d),
skewX = a * c + b * d;
if (scaleX) {
a /= scaleX;
b /= scaleX;
}
if (skewX) {
c -= a * skewX;
d -= b * skewX;
}
if (scaleY) {
c /= scaleY;
d /= scaleY;
skewX /= scaleY;
}
if (a * d < b * c) {
a = -a;
b = -b;
skewX = -skewX;
scaleX = -scaleX;
}
return {
translate: [e,f],
translateX: e,
translateY: f,
rotate: Math.atan2(b, a) * 180 / Math.PI,
skewX: Math.atan(skewX) * 180 / Math.PI,
scaleX: scaleX,
scaleY: scaleY
};
}
30 changes: 15 additions & 15 deletions src/js/viz/interactor/polymer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as d3 from "d3"; // transitions and other stuff
import {transform} from "../../transform";
import {easeCubicInOut} from "d3-ease";
import {rotatePointAboutPoint} from "../../geom";
import {svgns} from "../../svgns";
import {Interactor} from "./interactor";
Expand Down Expand Up @@ -45,8 +47,8 @@ export class Polymer extends Interactor {
scale() {
const protLength = (this.size) * this.stickZoom;
if (this.expanded) {
const labelTransform = d3.transform(this.labelSVG.getAttribute("transform"));
const k = this.app.svgElement.createSVGMatrix().rotate(labelTransform.rotate)
const labelTransform = transform(this.labelSVG.getAttribute("transform"));
const k = this.app.svgElement.createSVGMatrix()
.translate((-(((this.size / 2) * this.stickZoom) + (this.nTermFeatures.length > 0 ? 25 : 10))), this.labelY); //.scale(z).translate(-c.x, -c.y);
this.labelSVG.transform.baseVal.initialize(this.app.svgElement.createSVGTransformFromMatrix(k));
this.updateAnnotationRectanglesNoTransition();
Expand Down Expand Up @@ -159,7 +161,6 @@ export class Polymer extends Interactor {
}

toCircle(transition = true, svgP) {

if (!svgP) {
const width = this.app.svgElement.parentNode.clientWidth;
const ctm = this.app.container.getCTM().inverse();
Expand Down Expand Up @@ -196,7 +197,7 @@ export class Polymer extends Interactor {
.duration(transitionTime);

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

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

const self = this;
d3.select(this.ticks).transition().attr("opacity", 0).duration(transitionTime / 4)
.each("end",
.on("end",
function () {
d3.select(this).selectAll("*").remove();
}
);

const originalStickZoom = this.stickZoom;
const originalRotation = this.rotation;
const cubicInOut = d3.ease("cubic-in-out");
const cubicInOut = easeCubicInOut;
if (transition) {
for (let [annotationType, annotations] of this.annotationSets) {
if (this.app.annotationSetsShown.get(annotationType) === true) {
Expand All @@ -242,16 +243,15 @@ export class Polymer extends Interactor {
}
}
}
d3.timer(function (elapsed) {
return update(elapsed / transitionTime);
const t = d3.timer(function (elapsed) {
if (update(elapsed / transitionTime)) t.stop();
});
} else {
update(1);
}

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

if (xInterpol !== null) {
Expand Down Expand Up @@ -339,7 +339,7 @@ export class Polymer extends Interactor {


const self = this;
const cubicInOut = d3.ease("cubic-in-out");
const cubicInOut = easeCubicInOut;
if (transition) {
for (let [annotationType, annotations] of this.annotationSets) {
if (this.app.annotationSetsShown.get(annotationType) === true) {
Expand Down Expand Up @@ -374,16 +374,16 @@ export class Polymer extends Interactor {
}
}
}
d3.timer(function (elapsed) {
return update(elapsed / transitionTime);
const t = d3.timer(function (elapsed) {
if (update(elapsed / transitionTime)) t.stop();
});
} else {
update(1);
}

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

const currentLength = lengthInterpol(cubicInOut(interp));
Expand Down
Loading

0 comments on commit 0d5635f

Please sign in to comment.