Related
I would like to add icons or images to nodes. That is, the nodes people with an icon or image, the nodes, cell phones with an icon image and so on.
So far my code is as follows. It should be noted that the data is obtained directly from my base in NE4OJ
Cada color representa una propiedad, Quisera añadir un icono o imagen a cada nodo
var graphWidth = 1024;
var graphHeight = 450;
var neo4jAPIURL = 'http://localhost:7474';
var neo4jLogin = 'USER';
var neo4jPassword = 'PASSWORD';
var circleSize = 30;
var textPosOffsetY = 5;
var arrowWidth = 5;
var arrowHeight = 5;
var collideForceSize = circleSize * 1.5;
var linkForceSize = 150;
var iconPosOffset = {'lock': [-40, -50], 'cross': [18, -50]};
var linkTypeMapping = {'OUT_ADD': '+', 'OUT_SUB': '-', 'IN_AND': 'AND', 'IN_OR': 'OR'};
var lockIconSVG = 'm18,8l-1,0l0,-2c0,-2.76 -3.28865,-5.03754 -5,-5c-1.71135,0.03754 -5.12064,0.07507 -5,4l1.9,0c0,-1.71 1.39,-2.1 3.1,-2.1c1.71,0 3.1,1.39 3.1,3.1l0,2l-9.1,0c-1.1,0 -2,0.9 -2,2l0,10c0,1.1 0.9,2 2,2l12,0c1.1,0 2,-0.9 2,-2l0,-10c0,-1.1 -0.9,-2 -2,-2zm0,12l-12,0l0,-10l12,0l0,10z';
var crossIconSVG = 'M14.59 8L12 10.59 9.41 8 8 9.41 10.59 12 8 14.59 9.41 16 12 13.41 14.59 16 16 14.59 13.41 12 16 9.41 14.59 8zM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z';
var boxSVG = 'M0 0 L23 0 L23 23 L0 23 Z';
//######################### variable #########################
var nodeItemMap = {};
var linkItemMap = {};
var d3Simulation = null;
var circles;
var circleText;
var lines;
var lineText;
var iconLock;
var iconCross;
var itemColorMap = {};
var colorScale = d3.scaleOrdinal(d3.schemeSet2);
var drag_handler = d3.drag()
.on('start', drag_start)
.on('drag', drag_move)
.on('end', drag_end);
var zoom_handler = d3.zoom()
.filter(function() {
//Only enable wheel zoom and mousedown to pan
return (d3.event.type == 'wheel' | d3.event.type == 'mousedown');
})
.on('zoom', zoom_actions);
function unfreezeItms() {
var nodeItmArray = d3Simulation.nodes();
if (nodeItmArray != null) {
nodeItmArray.forEach(function(nodeItm) {
if (nodeItm.fx != null) {
nodeItm.fx = null;
nodeItm.fy = null;
}
});
}
}
function drag_start(d) {
//if (!d3.event.active && d3Simulation != null)
d3Simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag_move(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active && d3Simulation != null)
d3Simulation.alphaTarget(0);
//d.fx = null;
//d.fy = null;
}
function zoom_actions(){
d3.select('#resultSvg').select('g').attr('transform', d3.event.transform);
}
function initGraph() {
var svg = d3.select('#resultSvg');
var zoomGLayer = svg.append('g');
var centerX = graphWidth / 2;
var centerY = graphHeight / 2;
svg.attr('width', graphWidth)
.attr('height', graphHeight);
/*
var defs = svg.append('defs');
Not use marker as IE does not support it and so embed the arrow in the path directly
// define arrow markers for graph links
defs.append('marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 10)
.attr('markerWidth', 5)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5');
*/
zoomGLayer.append('g').attr('id', 'circle-group').attr('transform', 'translate(' + centerX + ',' + centerY + ')');
zoomGLayer.append('g').attr('id', 'text-group').attr('transform', 'translate(' + centerX + ',' + centerY + ')');
zoomGLayer.append('g').attr('id', 'path-group').attr('transform', 'translate(' + centerX + ',' + centerY + ')');
zoomGLayer.append('g').attr('id', 'path-label-group').attr('transform', 'translate(' + centerX + ',' + centerY + ')');
zoomGLayer.append('g').attr('id', 'control-icon-group').attr('transform', 'translate(' + centerX + ',' + centerY + ')');
zoom_handler(svg);
}
function stopSimulation() {
if (d3Simulation != null) {
d3Simulation.stop()
.on('tick', null);
d3Simulation = null;
}
}
function tick() {
lines.attr('d', drawLine);
lineText.attr('transform', transformPathLabel);
circles.attr('transform', transform);
circleText.attr('transform', transform);
iconLock.attr('transform', function(d) {return transformIcon(d, 'lock');});
iconCross.attr('transform', function(d) {return transformIcon(d, 'cross');});
}
function transformIcon(d, type) {
var sourceX = d.x + iconPosOffset[type][0];
var sourceY = d.y + iconPosOffset[type][1];
return 'translate(' + sourceX + ',' + sourceY + ')';
}
function transformPathLabel(d) {
var sourceX = d.source.x + ((d.target.x - d.source.x) / 2);
var sourceY = d.source.y + ((d.target.y - d.source.y) / 2);
return 'translate(' + sourceX + ',' + sourceY + ')';
}
function transform(d) {
return 'translate(' + d.x + ',' + d.y + ')';
}
function drawLine(d) {
var deltaX, deltaY, dist, cosTheta, sinTheta, sourceX, sourceY, targetX, targetY;
deltaX = d.target.x - d.source.x;
deltaY = d.target.y - d.source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
cosTheta = deltaX / dist;
sinTheta = deltaY / dist;
sourceX = d.source.x + (circleSize * cosTheta);
sourceY = d.source.y + (circleSize * sinTheta);
targetX = d.target.x - (circleSize * cosTheta);
targetY = d.target.y - (circleSize * sinTheta);
//Not use marker as IE does not support it and so embed the arrow in the path directly
var arrowLeftX, arrowLeftY, arrowRightX, arrowRightY;
arrowLeftX = targetX - (arrowHeight * sinTheta) - (arrowWidth * cosTheta);
arrowLeftY = targetY + (arrowHeight * cosTheta) - (arrowWidth * sinTheta);
arrowRightX = targetX + (arrowHeight * sinTheta) - (arrowWidth * cosTheta);
arrowRightY = targetY - (arrowHeight * cosTheta) - (arrowWidth * sinTheta);
return 'M' + sourceX + ' ' + sourceY + ' L' + targetX + ' ' + targetY
+ ' M' + targetX + ' ' + targetY + ' L' + arrowLeftX + ' ' + arrowLeftY
+ ' L' + arrowRightX + ' ' + arrowRightY + ' Z';
}
function clearProperties() {
$('#propertiesBox').empty();
}
function showProperties(d) {
clearProperties();
var propertiesText = 'id: ' + d.id;
//For nodes
//if (d.labels != null)
// propertiesText += ', labels: ' + d.labels.join(', ');
//For links
//if (d.type != null)
// propertiesText += ', type: ' + d.type;
$.map(d.properties, function(value, key) {
propertiesText += ', ' + key + ': ' + value;
});
$('#propertiesBox').append($('<p></p>').text(propertiesText));
}
function replaceLinkTypeName(d) {
var linkTypeName = linkTypeMapping[d.type];
if (linkTypeName == null)
return d.type;
return linkTypeName;
}
function generateCircleClasses(d) {
if (d.properties != null && d.properties.cyclic == '1')
return 'Cyclic ' + d.labels.join(' ');
return d.labels.join(' ');
}
function removeNode(d) {
delete nodeItemMap[d.id];
$.map(linkItemMap, function(value, key) {
if (value.startNode == d.id || value.endNode == d.id)
delete linkItemMap[key];
});
}
function updateGraph() {
var d3LinkForce = d3.forceLink()
.distance(linkForceSize)
.links(mapToArray(linkItemMap))
.id(function(d) {return d.id;});
d3Simulation = d3.forceSimulation()
//.force('chargeForce', d3.forceManyBody())//.strength(-300)
.force('collideForce', d3.forceCollide(collideForceSize))
.nodes(mapToArray(nodeItemMap))
.force('linkForce', d3LinkForce);
circles = d3.select('#circle-group').selectAll('circle')
.data(d3Simulation.nodes(), function(d) {return d.id;});
circleText = d3.select('#text-group').selectAll('text')
.data(d3Simulation.nodes(), function(d) {return d.id;});
lines = d3.select('#path-group').selectAll('path')
.data(d3LinkForce.links(), function(d) {return d.id;});
lineText = d3.select('#path-label-group').selectAll('text')
.data(d3LinkForce.links(), function(d) {return d.id;});
iconLock = d3.select('#control-icon-group').selectAll('g.lockIcon')
.data([], function(d) {return d.id;});
iconCross = d3.select('#control-icon-group').selectAll('g.crossIcon')
.data([], function(d) {return d.id;});
iconLock.exit().remove();
iconCross.exit().remove();
circles.exit().remove();
circles = circles.enter().append('circle')
.attr('r', circleSize)
.attr('fill', getItemColor)
.attr('title', function(d) {return d.labels.join('-');})
.attr('class', function(d) {return generateCircleClasses(d);})
.call(drag_handler)
.on('mouseover', function(d) {
d3.select(this)
showProperties(d);
})
.on('dblclick', function(d) {
d.fx = d.x;
d.fy = d.y;
submitQuery(d.id);
})
.on('click', function(d) {
iconLock = d3.select('#control-icon-group').selectAll('g.lockIcon')
.data([d], function(d) {return d.id;});
iconCross = d3.select('#control-icon-group').selectAll('g.crossIcon')
.data([d], function(d) {return d.id;});
iconLock.exit().remove();
iconLock.remove();
iconCross.exit().remove();
iconCross.remove();
var iconLockEnter = iconLock.enter().append('g')
.attr('class', 'lockIcon')
.attr('transform', function(d) {
return transformIcon(d, 'lock');
})
.on('click', function(d) {
d.fx = null;
d.fy = null;
iconLock.remove();
iconCross.remove();
});
iconLockEnter.append('path').attr('class', 'overlay').attr('d', boxSVG);
iconLockEnter.append('path').attr('d', lockIconSVG);
var iconCrossEnter = iconCross.enter().append('g')
.attr('class', 'crossIcon')
.attr('transform', function(d) {
return transformIcon(d, 'cross');
})
.on('click', function(d) {
removeNode(d);
updateGraph();
});
iconCrossEnter.append('path').attr('class', 'overlay').attr('d', boxSVG);
iconCrossEnter.append('path').attr('d', crossIconSVG);
iconLock = iconLockEnter
.merge(iconLock);
iconCross = iconCrossEnter
.merge(iconCross);
})
.merge(circles);
circleText.exit().remove();
circleText = circleText.enter().append('text')
.attr('y', textPosOffsetY)
.attr('text-anchor', 'middle')
.text(function(d) {
return [
d.properties.name
];})
.merge(circleText);
lines.exit().remove();
lines = lines.enter().append('path')
//.attr('marker-end', 'url(#end-arrow)')
.attr('title', function(d) {return d.type;})
.attr('class', function(d) {return d.type;})
.on('mouseover', function(d) {
showProperties(d);
})
.merge(lines);
lineText.exit().remove();
lineText = lineText.enter().append('text')
.attr('y', textPosOffsetY)
.attr('text-anchor', 'middle')
.text(function(d) {return replaceLinkTypeName(d);})
.merge(lineText);
d3Simulation
.on('tick', tick);
}
function submitQuery(nodeID) {
removeAlert();
var queryStr = null;
if (nodeID == null || !nodeID) {
queryStr = $.trim($('#queryText').val());
if (queryStr == '') {
promptAlert($('#graphContainer'), 'Error: el texto de consulta no puede estar vacío !', true);
return;
}
if ($('#chkboxCypherQry:checked').val() != 1)
queryStr = 'match (n) where n.Cedula =~ \'(?i).*' + queryStr + '.*\' return n';
} else
queryStr = 'match (n)-[j]-(k) where id(n) = ' + nodeID + ' return n,j,k';
stopSimulation();
if (nodeID == null || !nodeID) {
nodeItemMap = {};
linkItemMap = {};
}
var jqxhr = $.post(neo4jAPIURL, '{"statements":[{"statement":"' + queryStr + '", "resultDataContents":["graph"]}]}',
function(data) {
//console.log(JSON.stringify(data));
if (data.errors != null && data.errors.length > 0) {
promptAlert($('#graphContainer'), 'Error: ' + data.errors[0].message + '(' + data.errors[0].code + ')', true);
return;
}
if (data.results != null && data.results.length > 0 && data.results[0].data != null && data.results[0].data.length > 0) {
var neo4jDataItmArray = data.results[0].data;
neo4jDataItmArray.forEach(function(dataItem) {
//Node
if (dataItem.graph.nodes != null && dataItem.graph.nodes.length > 0) {
var neo4jNodeItmArray = dataItem.graph.nodes;
neo4jNodeItmArray.forEach(function(nodeItm) {
if (!(nodeItm.id in nodeItemMap))
nodeItemMap[nodeItm.id] = nodeItm;
});
}
//Link
if (dataItem.graph.relationships != null && dataItem.graph.relationships.length > 0) {
var neo4jLinkItmArray = dataItem.graph.relationships;
neo4jLinkItmArray.forEach(function(linkItm) {
if (!(linkItm.id in linkItemMap)) {
linkItm.source = linkItm.startNode;
linkItm.target = linkItm.endNode;
linkItemMap[linkItm.id] = linkItm;
}
});
}
});
console.log('nodeItemMap.size:' + Object.keys(nodeItemMap).length);
console.log('linkItemMap.size:' + Object.keys(linkItemMap).length);
updateGraph();
return;
}
//also update graph when empty
updateGraph();
promptAlert($('#graphContainer'), 'No encontrado!', false);
}, 'json');
jqxhr.fail(function(data) {
promptAlert($('#graphContainer'), 'Error: se envió el texto de la consulta pero se recibió un mensaje de error (' + data + ')', true);
});
}
//Page Init
$(function() {
setupNeo4jLoginForAjax(neo4jLogin, neo4jPassword);
initGraph();
$('#queryText').keyup(function(e) {
if(e.which == 13) {
submitQuery();
}
});
$('#btnSend').click(function() {submitQuery()});
$('#chkboxCypherQry').change(function() {
if (this.checked)
$('#queryText').prop('placeholder', 'Cypher');
else
$('#queryText').prop('placeholder', 'Node Name');
});
});
</script>
</body>
</html>
I'm trying to wrap a heatmap with daily data on a spiral. Following this example I want to place days beneath eachother and move to the right when the week changes. If using the above example I get days laid next to eachother.
I tried to adapt the code from the example although I made good progress any help would be appreciated. The segments don't align nicely and there are some paths that shouldn't be there. Surely I made some mistakes while adapting the calculations.
Wanted result
Current progress
Code
const radians = 0.0174532925;
//CHART CONSTANTS
const chartRadius = 100;
const chartWidth = chartRadius * 3;
const chartHeight = chartRadius * 3;
const labelRadius = chartRadius + 5;
const margin = { "top": 180, "bottom": 40, "left": 180, "right": 40 };
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
//CHART OPTIONS
const holeRadiusProportion = 0.75; //fraction of chartRadius. 0 gives you some pointy arcs in the centre.
const holeRadius = holeRadiusProportion * chartRadius;
const segmentsPerCoil = 52; //number of coils. for this example, I have 12 months per year. But you change to whatever suits your data.
const segmentAngle = 360 / segmentsPerCoil;
let coils; //number of coils, based on data.length / segmentsPerCoil
let coilWidth; //remaining chartRadius (after holeRadius removed), divided by coils + 1. I add 1 as the end of the coil moves out by 1 each time
//SCALES
const colour = d3.scaleSequential(d3.interpolateViridis);
//CREATE SVG AND A G PLACED IN THE CENTRE OF THE SVG
const svg = d3.select("#chart")
.append("svg")
.attr("width", chartWidth + margin.left + margin.right)
.attr("height", chartHeight + margin.top + margin.bottom);
const g = svg.append("g")
.attr("transform", "translate("
+ (margin.left + chartRadius)
+ ","
+ (margin.top + chartRadius) + ")");
// count the weeks
let week = 0
//LOAD THE DATA
d3.csv("restrictions_daily.csv", convertTextToNumbers, function (error, data) {
if (error) { throw error; };
// get the week number
data.forEach(function (d) {
const dateParse = d3.timeParse("%d/%m/%Y")(d.date)
d.week = week
if (+d3.timeFormat("%d")(dateParse) % 7 === 0)
week = week + 1
})
//CALCULATE AND STORE THE REMAING
let dataLength = 52
coils = Math.ceil(dataLength / segmentsPerCoil);
coilWidth = (chartRadius * (1 - holeRadiusProportion)) / (coils + 1);
//console.log("coilWidth: " + coilWidth);
var dataExtent = d3.extent(data, function (d) { return d.value; });
colour.domain(dataExtent);
//ADD LABELS AND GRIDS FOR EACH MONTH FIRST
//SO THE GRID LINES APPEAR BEHIND THE SPIRAL
var monthLabels = g.selectAll(".month-label")
.data(months)
.enter()
.append("g")
.attr("class", "month-label");
monthLabels.append("text")
// .text(function (d) { return d; })
.attr("x", function (d, i) {
let labelAngle = (i * segmentAngle) + (segmentAngle / 2);
return x(labelAngle, labelRadius);
})
.attr("y", function (d, i) {
let labelAngle = (i * segmentAngle) + (segmentAngle / 2);
return y(labelAngle, labelRadius);
})
.style("text-anchor", function (d, i) {
return i < (months.length / 2) ? "start" : "end";
});
monthLabels.append("line")
.attr("x2", function (d, i) {
let lineAngle = (i * segmentAngle);
let lineRadius = chartRadius + 10;
// return x(lineAngle, lineRadius);
})
.attr("y2", function (d, i) {
let lineAngle = (i * segmentAngle);
let lineRadius = chartRadius + 10;
// return y(lineAngle, lineRadius);
});
// reset the days when new week starts
let firstDay = 0
//ASSUMING DATA IS SORTED, CALCULATE EACH DATA POINT'S SEGMENT VERTICES
data.forEach(function (d, i) {
let coil = Math.floor(i / segmentsPerCoil);
let position = +d.week - 1;
console.log(d)
// divide radius by 7 to get proportion for each day
const dayHeight = holeRadius / 7
// reset day of the week
if (i % 7 === 0) {
firstDay = 0
}
const newRadius = (dayHeight * firstDay) + 100
// increment the day
firstDay = firstDay + 1
//console.log("positions: " + i + " " + coil + " " + position);
let startAngle = position * segmentAngle;
let endAngle = (position + 1) * segmentAngle;
//console.log("angles: " + startAngle + " " + endAngle);
//console.log(holeRadius + " " + segmentsPerCoil + " " + coilWidth)
let startInnerRadius = newRadius + ((i / segmentsPerCoil) * coilWidth)
let startOuterRadius = newRadius + ((i / segmentsPerCoil) * coilWidth) + coilWidth;
let endInnerRadius = newRadius + (((i + 1) / segmentsPerCoil) * coilWidth)
let endOuterRadius = newRadius + (((i + 1) / segmentsPerCoil) * coilWidth) + coilWidth;
console.log(startInnerRadius, startOuterRadius, endInnerRadius, endInnerRadius, startAngle, endAngle)
//console.log("start radi: " + startInnerRadius + " " + startOuterRadius);
//console.log("end radi: " + endInnerRadius + " " + endOuterRadius);
//vertices of each segment
d.x1 = x(startAngle, startInnerRadius);
d.y1 = y(startAngle, startInnerRadius);
d.x2 = x(endAngle, endInnerRadius);
d.y2 = y(endAngle, endInnerRadius);
d.x3 = x(endAngle, endOuterRadius);
d.y3 = y(endAngle, endOuterRadius);
d.x4 = x(startAngle, startOuterRadius);
d.y4 = y(startAngle, startOuterRadius);
//CURVE CONTROL POINTS
let midAngle = startAngle + (segmentAngle / 2)
let midInnerRadius = newRadius + (((i + 0.5) / segmentsPerCoil) * coilWidth)
let midOuterRadius = newRadius + (((i + 0.5) / segmentsPerCoil) * coilWidth) + coilWidth;
//MID POINTS, WHERE THE CURVE WILL PASS THRU
d.mid1x = x(midAngle, midInnerRadius);
d.mid1y = y(midAngle, midInnerRadius);
d.mid2x = x(midAngle, midOuterRadius);
d.mid2y = y(midAngle, midOuterRadius);
//FROM https://stackoverflow.com/questions/5634460/quadratic-b%C3%A9zier-curve-calculate-points
d.controlPoint1x = (d.mid1x - (0.25 * d.x1) - (0.25 * d.x2)) / 0.5;
d.controlPoint1y = (d.mid1y - (0.25 * d.y1) - (0.25 * d.y2)) / 0.5;
d.controlPoint2x = (d.mid2x - (0.25 * d.x3) - (0.25 * d.x4)) / 0.5;
d.controlPoint2y = (d.mid2y - (0.25 * d.y3) - (0.25 * d.y4)) / 0.5;
//console.log(d);
});
var arcs = g.selectAll(".arc")
.data(data)
.enter()
.append("g")
.attr("class", "arc");
//STRAIGHT EDGES
/*
arcs.append("path")
.attr("d", function (d) {
let M = "M " + d.x1 + " " + d.y1;
let L1 = "L " + d.x2 + " " + d.y2;
let L2 = "L " + d.x3 + " " + d.y3;
let L3 = "L " + d.x4 + " " + d.y4;
return M + " " + L1 + " " + L2 + " " + L3 + " Z"
})
//.style("fill", function (d) { return colour(d.value); })
.style("fill", "white")
.style("stroke", "white")
*/
//CURVED EDGES
arcs.append("path")
.attr("d", function (d) {
//start at vertice 1
let start = "M " + d.x1 + " " + d.y1;
//inner curve to vertice 2
let side1 = " Q " + d.controlPoint1x + " " + d.controlPoint1y + " " + d.x2 + " " + d.y2;
//straight line to vertice 3
let side2 = "L " + d.x3 + " " + d.y3;
//outer curve vertice 4
let side3 = " Q " + d.controlPoint2x + " " + d.controlPoint2y + " " + d.x4 + " " + d.y4;
//combine into string, with closure (Z) to vertice 1
return start + " " + side1 + " " + side2 + " " + side3 + " Z"
})
.style("fill", function (d) { return colour(d.value); })
.style("stroke", "white")
//ADD LABELS FOR THE YEAR AT THE START OF EACH COIL (IE THE FIRST MONTH)
var yearLabels = arcs.filter(function (d) { return d.month == 1; }).raise();
yearLabels.append("path")
.attr("id", function (d) { return "path-" + d.year; })
.attr("d", function (d) {
//start at vertice 1
let start = "M " + d.x1 + " " + d.y1;
//inner curve to vertice 2
let side1 = " Q " + d.controlPoint1x + " " + d.controlPoint1y + " " + d.x2 + " " + d.y2;
return start + side1;
})
.style("fill", "none")
//.style("opacity", 0);
yearLabels.append("text")
.attr("class", "year-label")
.attr("x", 3)
.attr("dy", -5)
.append("textPath")
.attr("xlink:href", function (d) {
return "#path-" + d.year;
})
.text(function (d) { return d.year; })
// //DRAW LEGEND
//
// const legendWidth = chartRadius;
// const legendHeight = 20;
// const legendPadding = 40;
//
// var legendSVG = d3.select("#legend")
// .append("svg")
// .attr("width", legendWidth + legendPadding + legendPadding)
// .attr("height", legendHeight + legendPadding + legendPadding);
//
// var defs = legendSVG.append("defs");
//
// var legendGradient = defs.append("linearGradient")
// .attr("id", "linear-gradient")
// .attr("x1", "0%")
// .attr("y1", "0%")
// .attr("x2", "100%")
// .attr("y2", "0%");
//
// let noOfSamples = 20;
// let dataRange = dataExtent[1] - dataExtent[0];
// let stepSize = dataRange / noOfSamples;
//
// for (i = 0; i < noOfSamples; i++) {
// legendGradient.append("stop")
// .attr("offset", (i / (noOfSamples - 1)))
// .attr("stop-color", colour(dataExtent[0] + (i * stepSize)));
// }
//
// var legendG = legendSVG.append("g")
// .attr("class", "legendLinear")
// .attr("transform", "translate(" + legendPadding + "," + legendPadding + ")");
//
// legendG.append("rect")
// .attr("x", 0)
// .attr("y", 0)
// .attr("width", legendWidth)
// .attr("height", legendHeight)
// .style("fill", "url(#linear-gradient)");
//
// legendG.append("text")
// .text("Fewer nights")
// .attr("x", 0)
// .attr("y", legendHeight - 35)
// .style("font-size", "12px");
//
// legendG.append("text")
// .text("More nights")
// .attr("x", legendWidth)
// .attr("y", legendHeight - 35)
// .style("text-anchor", "end")
// .style("font-size", "12px");
//
});
function x(angle, radius) {
//change to clockwise
let a = 360 - angle;
//start from 12 o'clock
a = a + 180;
return radius * Math.sin(a * radians);
};
function y(angle, radius) {
//change to clockwise
let a = 360 - angle;
//start from 12 o'clock
a = a + 180;
return radius * Math.cos(a * radians);
};
function convertTextToNumbers(d) {
d.year = +d.year;
d.month = +d.month;
d.value = +d.value;
return d;
};
JSFiddle
Thanks for any help & suggestions!
Here is an example of spiralArc (kind of D3's arc with changing radius):
const spiralArc = (fromRadius, toRadius, width, fromAngle, toAngle) => {
const x1 = fromRadius * Math.sin(fromAngle);
const y1 = fromRadius * -Math.cos(fromAngle);
const x2 = (fromRadius + width) * Math.sin(fromAngle);
const y2 = (fromRadius + width) * -Math.cos(fromAngle);
const x3 = toRadius * Math.sin(toAngle);
const y3 = toRadius * -Math.cos(toAngle);
const x4 = (toRadius + width) * Math.sin(toAngle);
const y4 = (toRadius + width) * -Math.cos(toAngle);
return `
M ${x1},${y1}
L ${x2},${y2}
A ${fromRadius},${fromRadius} 1 0 1 ${x4},${y4}
L ${x3},${y3}
A ${fromRadius},${fromRadius} 0 0 0 ${x1},${y1}`;
}
const svg = d3.select('svg');
const g = svg.append('g')
.attr('transform', 'translate(300,300)')
const WIDTH = 10;
const BASE_RADIUS = 30;
const angle = Math.PI * 2 / 30;
for (let index = 0; index < 100; index++) {
const fromAngle = angle * index;
const toAngle = angle * (index + 1);
for (let level = 0; level < 5; level++) {
const fromRadius = BASE_RADIUS + index * 2 + WIDTH * level;
const toRadius = BASE_RADIUS + (index + 1) * 2 + WIDTH * level;
const path = spiralArc (fromRadius, toRadius, WIDTH, fromAngle, toAngle);
const color = `rgb(0,${192 + Math.random() * 64},255)`
g.append('path').attr('d', path).style('fill', color)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<svg width="600" height="600" />
I have a d3 chart that have both left and right paths/nodes. now what i'm trying to do is that on click of a node i want to append the same data ( same tree with left and right nodes ) and this new tree will be populated/centerd according to the clicked nodes x and y values, so i tried to add a new g with the x and y values i got from the object clicked.
like this
var g = svg.append("g")
.attr("transform", "translate(" + d.x * 2 + "," + d.y + ")");
drawTree2(left, "left", d);
drawTree2(right, "right", d);
but its not working, please help
var data = {
"name": "Root",
"children": [{
"name": "Branch 1"
}, {
"name": "Branch 2",
}, {
"name": "Branch 3"
}, {
"name": "Branch 4",
}, {
"name": "Branch 5"
},
{
"name": "Branch 6"
}, {
"name": "Branch 7",
}, {
"name": "Branch 8"
}, {
"name": "Branch 9",
}, {
"name": "Branch 10"
}
]
};
var split_index = Math.round(data.children.length / 2)
// Left data
var data1 = {
"name": data.name,
"children": JSON.parse(JSON.stringify(data.children.slice(0, split_index)))
};
// Right data
var data2 = {
"name": data.name,
"children": JSON.parse(JSON.stringify(data.children.slice(split_index)))
};
// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var g = svg.append("g")
.attr("transform", "translate(" + width / 2 + ",0)");
// Render both trees
drawTree(right, "right")
drawTree(left, "left")
function drawTree(root, pos) {
var SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
// Create new default tree layout
var tree = d3.tree()
// Set the size
// Remember the tree is rotated
// so the height is used as the width
// and the width as the height
.size([height, SWITCH_CONST * (width - 150) / 2]);
tree(root)
var nodes = root.descendants();
var links = root.links();
// Set both root nodes to be dead center vertically
nodes[0].x = height / 2
// Create links
var link = g.selectAll(".link." + pos)
.data(links)
.join(
enter => enter.append("path"),
update => update,
exit => exit.remove()
)
.attr("class", "link " + pos)
.attr("d", function(d) {
return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
});
// Create nodes
var node = g.selectAll(".node." + pos)
.data(nodes)
.join(
enter => {
const n = enter
.append("g")
.on("click", (e, d) => {
drawSecondTree(d);
});
n.append("circle").attr("r", 2.5);
n.append("text").attr("y", -10).style("text-anchor", "middle");
return n;
},
update => update,
exit => exit.remove()
)
.attr("class", function(d) {
return "node " + pos + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
})
.select("text")
.text(function(d) {
return d.data.name
});
}
function drawSecondTree(d) {
var g = svg.append("g")
.attr("transform", "translate(" + d.x * 2 + "," + d.y + ")");
drawTree2(left, "left", d);
drawTree2(right, "right", d);
function drawTree2(root, pos, d) {
console.log(d.x, d.y);
//return false;
var SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
// Create new default tree layout
var tree = d3.tree()
// Set the size
// Remember the tree is rotated
// so the height is used as the width
// and the width as the height
.size([height, SWITCH_CONST * (width - 150) / 2]);
tree(root)
var nodes = root.descendants();
var links = root.links();
// Set both root nodes to be dead center vertically
nodes[0].x = d.y;
// Create links
var link = g.selectAll(".link." + pos)
.data(links)
.join(
enter => enter.append("path"),
update => update,
exit => exit.remove()
)
.attr("class", "link " + pos)
.attr("d", function(d) {
return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
});
// Create nodes
var node = g.selectAll(".node." + pos)
.data(nodes)
.join(
enter => {
const n = enter
.append("g")
.on("click", (e, d) => toggle(d, pos, pos === "left" ? left : right));
n.append("circle").attr("r", 2.5);
n.append("text").attr("y", -10).style("text-anchor", "middle");
return n;
},
update => update,
exit => exit.remove()
)
.attr("class", function(d) {
return "node " + pos + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
})
.select("text")
.text(function(d) {
return d.data.name
});
}
}
.node circle {
fill: #999;
}
.node text {
font: 12px sans-serif;
}
.node--internal circle {
fill: #555;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg width="900" height="600"></svg>
I think I understand what you're trying to get at.
It's hard to explain because the way this is setup is a bit confusing. Basically, the main issue is the way you are creating the new g element's "center point" (the translate(x,y) part). The reason it's confusing is because you are switching X and Y coordinates in certain places. Maybe you can't get around that with how you want the map to look, which is fine, it's just hard to follow along.
I made the following changes in the drawSecondTree function (note the "added" and "updated" items):
function drawSecondTree(d) {
var gX = (width / 2) + d.y; // ********** added
var gY = d.x - gX; // ********** added
var g = svg.append("g")
.attr("transform", "translate(" + gX + "," + gY + ")"); // ********** updated
drawTree2(left, "left", d);
drawTree2(right, "right", d);
function drawTree2(root, pos, d) {
console.log(d.x, d.y);
//return false;
var SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
// Create new default tree layout
var tree = d3.tree()
// Set the size
// Remember the tree is rotated
// so the height is used as the width
// and the width as the height
.size([height, SWITCH_CONST * (width - 150) / 2]);
tree(root)
var nodes = root.descendants();
var links = root.links();
// Set both root nodes to be dead center vertically
// nodes[0].x = d.y;
nodes[0].x = (width / 2) + d.y; // ********** updated
// Create links
var link = g.selectAll(".link." + pos)
.data(links)
.join(
enter => enter.append("path"),
update => update,
exit => exit.remove()
)
.attr("class", "link " + pos)
.attr("d", function(d) {
return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
});
// Create nodes
var node = g.selectAll(".node." + pos)
.data(nodes)
.join(
enter => {
const n = enter
.append("g")
.on("click", (e, d) => toggle(d, pos, pos === "left" ? left : right));
n.append("circle").attr("r", 2.5);
n.append("text").attr("y", -10).style("text-anchor", "middle");
return n;
},
update => update,
exit => exit.remove()
)
.attr("class", function(d) {
return "node " + pos + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
})
.select("text")
.text(function(d) {
return d.data.name
});
}
}
And here's a codepen of the full code working (with overlap of the nodes).
I would also recommend looking into using the viewBox of the svg element and figuring out how to zoom out when you add more nodes by clicking on them.
I have been trying to merge a set of paths in d3, So that one color blends into the other so it appears as though forms a gradient i tried to use to create a gradient but its always in a single direction which does not work out.
Fiddle here https://jsfiddle.net/roug3/jnpe5v3p/
var mapGroup = d3.select("svg");
function renderARC() {
var txData = {x: 200 , y : 200 , angle : 30};
var etxD = {etxSN : "TX500"};
if(d3.select(".arc"+etxD.etxSN).node()){
return;
}
var arcLevel = 5;
var arcSpan = 20;
var arcAngle = 2.0944;
var txAngle = txData.angle + 0;
var startAngle = txAngle - (arcAngle / 2);
var endAngle = txAngle + (arcAngle / 2);
var x = txData.x;
var y = txData.y;
var cwidth = 20;
var dataset = {};
for(var i = 1;i<= arcLevel;i++){
dataset[i] = [i];
}
var color = ["#ee4035","#f37736","#fdf498","#7bc043","#0392cf"]
// var color = ["#009933" , "#33cc33" ,"#ff3300" , "#ffcc66" ,"#ff6699" ,"#4dffff"];
var pie = d3.layout.pie()
.sort(null)
.startAngle(startAngle)
.endAngle(endAngle);
var arc = d3.svg.arc();
var gs = mapGroup.append("g").classed("arc"+etxD.etxSN , true).classed("arcSegment" , true);
console.log(gs);
var ggs = gs.selectAll("g").data(d3.values(dataset)).enter().append("g");
var arcP = ggs.selectAll("path").data(function (d) {
return pie(d);
})
.enter();
arcP.append("path").
attr("class" , function (d, i) {
return "arcID"+etxD.etxSN+i;
})
.attr("fill", function (d, i, j) {
// var cspan = Math.floor(Math.random() * arcLevel);
return color[ j ];
})
.attr("d", function (d, i, j) {
return arc.innerRadius(cwidth * j + arcSpan).outerRadius(cwidth * (j + 1) + arcSpan)(d);
}).
attr("transform" , "translate("+x+","+y+")");
}
renderARC();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width=500 height=500></svg>
Any Suggestions
Thanks
This is as close as I could get it : https://jsfiddle.net/thatoneguy/jnpe5v3p/2/
With the help of these :
http://jsfiddle.net/Qh9X5/1110/
http://www.w3schools.com/svg/svg_grad_radial.asp
Basically you have to create a radial blur using the dataset :
var grads = mapGroup.append("defs").selectAll("radialGradient").data(pie(d3.values(dataset)))
.enter().append("radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d, i) {
return cwidth * (i + 1) + arcSpan
})
.attr("id", function(d, i) {
return "grad" + i;
}).attr("transform", "translate(" + x + "," + y + ")");;
grads.append("stop")
.attr("offset", "80%")
.style("stop-color", function(d, i) {return color[i];});
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d, i) {
if (color[i + 1]) {
console.log(color[i + 1])
return color[i + 1];
} else {
return color[i];
}
})
Then select this to fill your paths :
arcP.append("path").
attr("class", function(d, i) {
return "arcID" + etxD.etxSN + i;
})
.attr("fill", function(d, i) {
console.log(count)
count++;
return "url(#grad" + count + ")";
})
.attr("d", function(d, i, j) {
return arc.innerRadius(cwidth * j + arcSpan).outerRadius(cwidth * (j + 1) + arcSpan)(d);
}).
attr("transform", "translate(" + x + "," + y + ")");
var mapGroup = d3.select("svg");
function renderARC() {
var txData = {
x: 200,
y: 200,
angle: 30
};
var etxD = {
etxSN: "TX500"
};
if (d3.select(".arc" + etxD.etxSN).node()) {
return;
}
var arcLevel = 5;
var arcSpan = 20;
var arcAngle = 2.0944;
var txAngle = txData.angle + 0;
var startAngle = txAngle - (arcAngle / 2);
var endAngle = txAngle + (arcAngle / 2);
var x = txData.x;
var y = txData.y;
var cwidth = 20;
var dataset = {};
for (var i = 1; i <= arcLevel; i++) {
dataset[i] = [i];
}
var color = ["#ee4035", "#f37736", "#fdf498", "#7bc043", "#0392cf"]
// var color = ["#009933" , "#33cc33" ,"#ff3300" , "#ffcc66" ,"#ff6699" ,"#4dffff"];
var pie = d3.layout.pie()
.sort(null)
.startAngle(startAngle)
.endAngle(endAngle);
var arc = d3.svg.arc();
var gs = mapGroup.append("g").classed("arc" + etxD.etxSN, true).classed("arcSegment", true);
console.log(gs);
var ggs = gs.selectAll("g").data(d3.values(dataset)).enter().append("g");
var arcP = ggs.selectAll("path").data(function(d) {
return pie(d);
})
.enter();
var grads = mapGroup.append("defs").selectAll("radialGradient").data(pie(d3.values(dataset)))
.enter().append("radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d, i) {
return cwidth * (i + 1) + arcSpan
})
.attr("id", function(d, i) {
return "grad" + i;
}).attr("transform", "translate(" + x + "," + y + ")");;
grads.append("stop")
.attr("offset", "80%")
.style("stop-color", function(d, i) {return color[i];});
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d, i) {
if (color[i + 1]) {
console.log(color[i + 1])
return color[i + 1];
} else {
return color[i];
}
})
var count = -1;
arcP.append("path").
attr("class", function(d, i) {
return "arcID" + etxD.etxSN + i;
})
.attr("fill", function(d, i) {
console.log(count)
count++;
return "url(#grad" + count + ")";
})
.attr("d", function(d, i, j) {
return arc.innerRadius(cwidth * j + arcSpan).outerRadius(cwidth * (j + 1) + arcSpan)(d);
}).
attr("transform", "translate(" + x + "," + y + ")");
}
renderARC();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width=500 height=500></svg>
It isn't perfect but will put you on the right track :) Hope this helps
This is the know problem in chrome. I tried multiple solutions.
But my bad luck and understanding i am not able to fix.
Could someone help me. I spent lot of time. could not resolve this. Label this links not working in chrome
var nodes ;
var links;
var linkNodeMap = {};
var width = window.innerWidth-30,
height = window.innerHeight-140,
root = {
"nodes": [
{
"type": "S",
"id": "1",
"name": "1001"
},
{
"type": "S",
"id": "2",
"name": "3"
},
{
"id": "10.10.0.5",
"name": "h5"
},
{
"id": "10.10.0.3",
"name": "h3"
}
],
"links": [
{
"source": 1,
"p1":3,
"p2":4,
"index":0,
"target": 0
},
{
"source": 1,
"p1":5,
"p2":6,
"index":0,
"target": 0
},
{
"source": 1,
"p1":3,
"p2":4,
"index":0,
"target": 2
},
{
"source": 1,
"p1":3,
"p2":4,
"index":0,
"target": 3
}
]
};
document.getElementById("refresh").disabled = false;
var force = d3.layout.force()
.linkDistance(100)
.charge(-120)
.gravity(.03)
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.style("border", "1px solid black")
.attr("height", height);
var g = svg.append("g");
var link = g.selectAll("path");
var node = svg.selectAll(".node");
var sourcePortText = g.selectAll("text.label");
var destinationPortText = g.selectAll("text.label");
function updateLinkCount (link) {
var linkcount = getLinkCount(link);
if(linkcount == undefined)
linkcount = 0;
linkcount = linkcount + 1;
link.index = linkcount;
linkNodeMap[getKey(link)] = linkcount;
}
function getLinkCount(link) {
isAvaiable = linkNodeMap[link.source.id + '-' + link.target.id];
if(isAvaiable != undefined)
return isAvaiable
return linkNodeMap[link.target.id + '-' + link.source.id];
}
function getKey(link) {
isAvaiable = linkNodeMap[link.source.id + '-' + link.target.id];
if(isAvaiable != undefined)
return link.source.id + '-' + link.target.id
return link.target.id + '-' + link.source.id;
}
function update() {
nodes = root.nodes;
links = root.links;
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update links.
link = link.data(links, function(d) {
return d.source.id + '-' + d.p1 + '-' + d.target.id + '-' +d.p2; });
link.exit().remove();
link.enter().append("path")
.attr("class", "link")
.attr("id", function(d) {
return d.source.id + '-' + d.p1 + '-' + d.target.id + '-' +d.p2; });
links.forEach(function(link) {
updateLinkCount(link);
});
// Update nodes.
node = node.data(nodes, function(d) { return d.id; });
node.exit().remove();
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("circle")
.attr("r", 20)
.attr("id", function(d) { return d.id; });
nodeEnter.append("text")
.attr("dy", ".35em")
.text(function(d) { return d.name; });
node.select("circle")
.style("fill", color);
node.select("text")
.text(function(d) { return d.name; });
sourcePortText = sourcePortText.data(links, function(d) {
return d.source.id + '-' + d.p1 + '-' + d.target.id + '-' +d.p2;})
sourcePortText.enter().append("text")
.attr("dy", 4)
.attr("font-size", 10)
.attr("fill", "black")
.append("textPath")
.attr("startOffset","25%")
.attr("class", "textPath")
.attr("xlink:href", function(d) { return '#' + d.source.id + '-' + d.p1 + '-' + d.target.id + '-' +d.p2;})
.text(function(d) { return d.p1 });
sourcePortText.exit().remove();
destinationPortText = destinationPortText.data(links, function(d) {
return d.source.id + '-' + d.p1 + '-' + d.target.id + '-' +d.p2;})
destinationPortText.enter().append("text")
.attr("class", "label")
.attr("dy", 4)
.attr("font-size", 10)
.attr("fill", "black")
.append("textPath")
.attr("class", "textPath")
.attr("startOffset","75%")
.attr("xlink:href", function(d) { return '#' + d.source.id + '-' + d.p1 + '-' + d.target.id + '-' +d.p2;})
.text(function(d) { return d.p2;});
destinationPortText.exit().remove();
}
function tick() {
link.attr("d", linkArc);
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
sourcePortText.attr("x",function(d){ return xpos(d.source, d.target); });
sourcePortText.attr("y",function(d){ return ypos(d.source, d.target); });
svg.selectAll(".textPath").attr("xlink:href",
function(d) {
return "#"+d.source.id + '-' + d.p1 + '-' + d.target.id + '-' +d.p2;
})
destinationPortText.attr("x",function(d){ return xpos(d.target, d.source); });
destinationPortText.attr("y",function(d){ return ypos(d.target, d.source); });
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = 0;
var linkCount = getLinkCount(d);
if(linkCount > 1) {
dr = Math.sqrt(dx * dx + dy * dy);
// if there are multiple links between these two nodes, we need generate different dr for each path
dr = dr/(1 + (1/linkCount) * (linkCount - d.index));
}
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function xpos(s, t) {
var angle = Math.atan2(t.y - s.y, t.x - s.x);
return 35 * Math.cos(angle) + s.x;
};
function ypos(s, t) {
var angle = Math.atan2(t.y - s.y, t.x - s.x);
return 35 * Math.sin(angle) + s.y;
};
function color(d) {
return d.type == 'S' ? "#c6dbef": "#fd8d3c";
}
function refresh() {
linkNodeMap = {};
update();
}
update();
My fildle:
http://jsfiddle.net/pkolanda/Lmdag990/6/