in client/client/app/components/ngbTargetPanel/ngbIdentificationsTab/ngbDiseasesPanel/ngbDiseasesGraph/ngbDiseasesGraph.controller.js [121:409]
drawChart(svg) {
if (!svg || !this.results) {
return;
}
const width = svg.attr('width');
const height = svg.attr('height');
const data = makeGraph(
this.results || [],
this.scoreFilter,
this.ontology,
);
const maxLevel = Math.max(...data.map((item) => item.x));
const maxSize = Math.max(...data.map((item) => item.levelSize), 1);
const offset = {
top: 30 + MAX_RADIUS,
bottom: 10 + MAX_RADIUS,
left: 10 + MAX_RADIUS,
right: 10 + MAX_RADIUS
};
const widthCorrected = width - offset.left - offset.right;
const heightCorrected = height - offset.bottom - offset.top;
const dY = Math.max(
2.0 * (MIN_RADIUS + 1.0),
Math.min(heightCorrected / maxSize, 4.0 * MAX_RADIUS)
);
const dX = widthCorrected / (maxLevel + 1);
const cY = offset.top + heightCorrected / 2.0;
const maxAbsoluteYPosition = (heightCorrected / 2.0) / dY;
const filtered = data.filter((item) => Math.abs(item.y) < maxAbsoluteYPosition);
const hidden = data
.filter((item) => Math.abs(item.y) >= maxAbsoluteYPosition)
.reduce((levels, item) => {
let level = levels.find((aLevel) => aLevel.id === item.level);
if (!level) {
level = {
id: item.level,
items: []
};
levels.push(level);
}
level.items.push(item);
return levels;
}, []);
const more = [];
hidden.forEach((level) => {
if (level.items.length === 1) {
filtered.push(...level.items);
} else {
more.push({
x: level.id,
y: Math.ceil(maxAbsoluteYPosition) + 0.5,
data: {
id: `level-${level.id}`,
name: `+${level.items.length} diseases`,
items: level.items,
more: true
}
});
}
});
this.nodes = filtered.map((item) => item.data).filter(Boolean);
const r = Math.max(1, Math.floor(dY / 2.0 - 1.0));
const getNodeClassName = (node) => {
if (node.data && node.data.area) {
return 'area';
}
if (node.data && node.data.disease) {
return 'disease';
}
return undefined;
}
const getX = (node) => offset.left + dX * (node.x || 0);
const getY = (node) => cY + dY * (node.y || 0);
const aCircle = circlePath(r);
const aRectangle = rectanglePath(2.0 * r);
const getLinkedNode = (linkId) => filtered.find((o) => o.data && o.data.id === linkId);
const getPath = (node) => {
if (node.data && node.data.area) {
return aRectangle;
}
if (node.data && node.data.disease) {
return aCircle;
}
return '';
};
const getStroke = () => '#0f6496';
const getFill = (node) => {
if (node.data && node.data.area) {
return 'white';
}
if (node.data && node.data.score !== undefined) {
return this.ngbDiseasesChartService.getColorForScore(node.data.score) || 'white';
}
return 'transparent';
};
const getNodeId = (node, prefix) => {
if (node.data && node.data.area) {
return `${prefix}area-${node.data.id}`;
}
if (node.data && node.data.disease) {
return `${prefix}disease-${node.data.id}`;
}
if (node.data && node.data.more) {
return `${prefix}more-${node.data.id}`;
}
return undefined;
};
const showInfo = this.showInfo.bind(this);
const hideInfo = this.hideInfo.bind(this);
const edges = [];
if (filtered.length < MAX_DATA_TO_SHOW_LINKS) {
filtered.forEach((d) => {
const node = d.data;
if (node) {
const {
links = []
} = node;
links.forEach((linkId) => {
const linkNode = getLinkedNode(linkId);
if (linkNode) {
edges.push({
child: d,
parent: linkNode
});
}
});
}
});
}
svg
.selectAll('.node-link')
.data(edges)
.enter()
.append('path')
.attr('class', 'node-link')
.attr('d', (d) => {
const x1 = getX(d.parent);
const y1 = getY(d.parent);
const x2 = getX(d.child);
const y2 = getY(d.child);
return getCurve(x1, y1, x2, y2, r);
})
.attr('stroke', '#90c5e5')
.attr('fill', 'transparent')
.attr('stroke-opacity', 0.25)
.attr('stroke-width', 1);
const moreLabels = this.svg
.selectAll('.more-label')
.data(more)
.enter()
.append('g')
.attr('class', 'more-label')
.attr('transform', (d) => `translate(${getX(d)}, ${getY(d)})`);
const nodes = this.svg
.selectAll('.node')
.data(filtered)
.enter()
.append('g')
.attr('class', (d) => ['node', getNodeClassName(d)].filter(Boolean).join(' '))
.attr('transform', (d) => `translate(${getX(d)}, ${getY(d)})`)
.on('mouseover', (node) => {
if (node.data) {
showInfo(node.data, getX(node), getY(node));
}
this.highlightedTreeNode = node.data;
})
.on('mouseout', () => {
hideInfo();
this.highlightedTreeNode = undefined;
});
nodes
.append('path')
.attr('class', 'node-image')
.attr('id', (d) => getNodeId(d, 'node-image-'))
.attr('d', getPath)
.attr('stroke', getStroke)
.attr('fill', getFill);
const getNodeClipPathId = (node) => getNodeId(node, 'clip');
const getNodeClipPathUrl = (node) => {
const id = getNodeId(node, 'clip');
if (id) {
return `url(#${id})`;
}
return undefined;
};
moreLabels
.append('clipPath')
.attr('id', getNodeClipPathId)
.append('rect')
.attr('x', 0)
.attr('y', -dY / 2.0)
.attr('width', dX - r)
.attr('height', dY);
moreLabels
.append('text')
.attr('class', 'node-title')
.attr('x', 2)
.attr('y', 2)
.attr('fill', '#333333')
.attr('pointer-events', 'none')
.attr('text-anchor', 'start')
.attr('alignment-baseline', 'middle')
.attr('font-size', Math.min(2.0 * r, 10))
.attr('font-style', 'italic')
.text((node) => {
if (node.data && node.data.name) {
return node.data.name;
}
return undefined;
})
.attr('clip-path', getNodeClipPathUrl);
nodes
.append('clipPath')
.attr('id', getNodeClipPathId)
.append('rect')
.attr('x', r)
.attr('y', -dY / 2.0)
.attr('width', dX - 3.0 * r)
.attr('height', dY);
nodes
.append('text')
.attr('class', 'node-title')
.attr('x', r + 2)
.attr('y', 0)
.attr('fill', '#333333')
.attr('text-anchor', 'start')
.attr('alignment-baseline', 'middle')
.attr('font-size', Math.min(2.0 * r, 10))
.text((node) => {
if (node.data && node.data.name) {
return node.data.name;
}
return undefined;
})
.attr('clip-path', getNodeClipPathUrl);
const legendSize = {
width: 200,
height: offset.top
};
const legend = svg
.append('g')
.attr('transform', `translate(${width / 2.0 - legendSize.width / 2.0}, 0)`);
legend
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', legendSize.width)
.attr('height', legendSize.height)
.attr('stroke', 'transparent')
.attr('fill', 'rgba(255.0,255.0,255.0,50%)');
legend
.append('text')
.attr('x', 10.0)
.attr('y', legendSize.height / 2.0)
.attr('fill', '#333333')
.attr('pointer-events', 'none')
.attr('text-anchor', 'start')
.attr('alignment-baseline', 'middle')
.attr('font-size', 11)
.attr('font-weight', 'bold')
.text('General');
legend
.append('text')
.attr('x', legendSize.width - 10)
.attr('y', legendSize.height / 2.0)
.attr('fill', '#333333')
.attr('pointer-events', 'none')
.attr('text-anchor', 'end')
.attr('alignment-baseline', 'middle')
.attr('font-size', 11)
.attr('font-weight', 'bold')
.text('Specific');
const arrowX1 = 60;
const arrowX2 = legendSize.width - 60;
const arrowWidth = arrowX2 - arrowX1;
const arrow = legend
.append('g')
.attr('transform', `translate(${arrowX1}, ${legendSize.height / 2.0})`);
arrow
.append('path')
.attr('d', `M0 0 L${arrowWidth} 0`)
.attr('stroke', '#333333')
.attr('fill', 'transparent');
arrow
.append('path')
.attr('d', `M${arrowWidth} 0 L${arrowWidth - 5} -3 L${arrowWidth - 5} 3`)
.attr('stroke', '#333333')
.attr('fill', '#333333');
}