I am new to D3 and data visualizing and I'm having some troubles loading my real data.
You'll find my code in the below sections.
Right now I have some data stored in an array, and now what I want to do is, store my actual data from my database into pie charts.
Also if I do this :
var mydata=d3.json("mydatafile");
console.log(mydata);
It shows me all the data I have retrieved from database in a promise array.
Is there any way possible I can get these data and put them in my charts?
The code for my pie chart written in D3js is below:
var aColor = [
'rgb(127, 212, 123)', //green
'rgb(240, 149, 164)', // red
'rgb(181, 174, 175)' //gray
]
var data = [{
"platform": "Yes",
"percentage": 87.00
}, //skyblue
{
"platform": "No",
"percentage": 1.00
}, //darkblue
{
"platform": "N/A",
"percentage": 17.00
}]; //orange
var svgWidth = 200,
svgHeight = 200,
radius = Math.min(svgWidth, svgHeight) / 2;
var svg = d3.select('#graph1').append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
//Create group element to hold pie chart
var g = svg.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var pie = d3.layout.pie().value(function (d) {
return d.percentage;
});
var path = d3.svg.arc()
.outerRadius(80)
.innerRadius(40);
var arc = g.selectAll("arc")
.data(pie(data))
.enter()
.append("g")
.sort((a, b) => b.data.percentage - a.data.percentage);
arc.append("path")
.attr("d", path)
.attr("fill", function (d, i) { return aColor[i]; });
var label = d3.svg.arc()
.outerRadius(20)
.innerRadius(100);
arc.append("text")
.attr("transform", function (d) {
return "translate(" + label.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.text(function (d) {
return +d.data.percentage;
});
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="graph1"></div>
On the other hand as per my backend, I have this project written on asp.net .net framework and I have this function which retrieves all the data on JSON format, which is cool.
public JsonResult BarChart()
{
string query = "select e.ProjectName,cyn.Name From Events e left join ConstYesNoes cyn on cyn.ID = e.ApproveId";
IEnumerable<BarChartsViewModel> ListResults = db.Database.SqlQuery<BarChartsViewModel>(query).ToList();
return Json(ListResults.Select(x => new { Name = x.Name, ApprovedId = x.ApprovedId, ID = x.ID, ProjectName = x.ProjectName }).ToList(), JsonRequestBehavior.AllowGet);
}
Related
I would like to add a background image for each circle of my bubble chart. I already read some solutions on different subject but it doesn't solve my problem. All the other style are correctly added but not the background-image style.
scope.chart = function(rootData){
var diameter = 900,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.value(function(d) { return (d.size); })
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("id","svg")
.attr("class", "bubble");
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(rootData))
.filter(function (d) {
return !d.children;
}))
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function (d) {
return d.r;
})
.style("opacity", "0")
.style("fill", function (d) {
return "black"
})
.style("background-image", function (d){
return "myImage2.png"
});
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); })
.style("opacity", "0");
node.on("click", click);
d3.selectAll("circle").style("fill", function (d) {
return "black"
});
d3.select(this).select("circle").style('fill', '#f2f40d');
d3.selectAll("circle").style("filter", null);
d3.select(this).select("circle").style("filter", "url(#f1)");
}
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function (child) {
recurse(node.name, child);
});
else classes.push({
parameter1: name,
className: node.name,
size: node.size,
});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
}
Edit : This is the code I tried but the image not appears.
/**
* Create the bubble chart graph
* #constructor
*/
d3DemoApp.directive('chart', [function($scope) {
return {
restrict: 'EA',
transclude: true,
scope: {
parameter1: '=parameter1',
paramete2: '=paramete2',
paramete3: '=paramete3',
paramete4: '=paramete4',
paramete5: '=paramete5',
chartData : '=',
myFunction : '=myFunction'
},
link: function(scope) {
scope.$watch('chartData', function(newValue) {
if (newValue){
scope.chart(newValue);
}
});
scope.chart = function(rootData){
d3.select('svg').remove(); // This delete the graph not wanted
d3.select('svg').remove(); // This delete the graph not wanted
var diameter = 900,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.value(function(d) { return (d.paramete5+1); })
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("id","svg")
.attr("class", "bubble");
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(rootData))
.filter(function (d) {
return !d.children;
}))
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
svg.append("defs")
.append("pattern")
.attr("id", "image")
.attr("height","100%")
.attr("width","100%")
.attr("x", "37%")
.attr("y","40%")
.append("image")
.attr("x", "0%")
.attr("y","0%")
.attr("viewBox", "0 0 200 200")
.attr("height","100")
.attr("width","100")
.attr("xlink:href", "linkImage");
node.append("circle")
.attr("r", function (d) {
return d.r;
})
.style("opacity", "0")
.style("fill", function (d) {
return "black"
})
.attr("fill", 'url("#image")');
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); })
.style("opacity", "0");
node.on("click", click);
function click(d) {
scope.$apply(function () {
console.log("Call on apply");
scope.myFunction(); // Call is just correct once scope.parameter1 = d.parameter1;
scope.paramete3 = d.paramete3;
scope.paramete2 = d.title;
scope.paramete4 = d.paramete4;
scope.paramete5 = d.paramete5;
});
d3.selectAll("circle").style("fill", function (d) {
return "black"
});
d3.select(this).select("circle").style('fill', 'circle');
d3.selectAll("circle").style("filter", null);
d3.select(this).select("circle").style("filter", "url(#f1)");
var test = document.getElementById("svg");
function whatClicked(evt) {
if (evt.target.id == "svg"){
d3.selectAll("circle").style("fill", function (d) {
return "black"
});
}
scope.myFunction();
}
test.addEventListener("click", whatClicked, true);
}
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function (child) {
recurse(node.name, child);
});
else classes.push({
parameter1: name,
className: node.name,
title: node.title,
value: node.paramete5,
paramete4: node.projectId,
parameter6: node.parameter7,
paramete5: node.paramete5,
paramete3: node.paramete3,
isLocked:node.isLocked
});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
}
if(typeof scope.chartData != "undefined"){
scope.drawChart(scope.chartData);
}
}
};
}]);
Here's the plunker: https://plnkr.co/edit/Kzr2a4HFG2yJxWfu90ri?p=preview
If you want to show an image in the background of a svg element use defs:
svg.append("defs")
.append("pattern")
.attr("id", "image")
.attr("height","100%")
.attr("width","100%")
.attr("x", "37%")
.attr("y","40%")
.append("image")
.attr("x", "0%")
.attr("y","0%")
.attr("viewBox", "0 0 200 200")
.attr("height","100")
.attr("width","100")
.attr("xlink:href", "ImageLink");
And give this reference to your element by url:
.attr("fill", 'url("#image")');
Just started learning meteor and d3 / crossfilter charting libraries.
Picked up some example code off the Web, and have it working in my local app.
I do have an empty this.autorun() function in my meteor client code, but have no idea what part of the lengthy d3 initialization and composition routine should be put into autorun, in order for these charts to react to the data changes.
I have tried to just put the Flights.find().fetch() inside the autorun, but in that case, the page never seem to finish loading.
Here is my entire meteor code:
if (Meteor.isClient) {
Template.dashboard.helpers({
});
Template.dashboard.events({
});
Template.dashboard.rendered = function(){
var flights = Flights.find().fetch();
if (!flights.length) return;
var crossData = crossfilter(flights);
// d3.csv(data, function(error, flights) {
// Various formatters.
var formatNumber = d3.format(",d"),
formatChange = d3.format("+,d"),
formatDate = d3.time.format("%B %d, %Y"),
formatTime = d3.time.format("%I:%M %p");
// A nest operator, for grouping the flight list.
var nestByDate = d3.nest()
.key(function(d) { return d3.time.day(d.date); });
// A little coercion, since the CSV is untyped.
flights.forEach(function(d, i) {
d.index = i;
d.date = parseDate(d.date);
d.delay = +d.delay;
d.distance = +d.distance;
});
// Create the crossfilter for the relevant dimensions and groups.
var flight = crossfilter(flights),
all = flight.groupAll(),
date = flight.dimension(function(d) { return d.date; }),
dates = date.group(d3.time.day),
hour = flight.dimension(function(d) { return d.date.getHours() + d.date.getMinutes() / 60; }),
hours = hour.group(Math.floor),
delay = flight.dimension(function(d) { return Math.max(-60, Math.min(149, d.delay)); }),
delays = delay.group(function(d) { return Math.floor(d / 10) * 10; }),
distance = flight.dimension(function(d) { return Math.min(1999, d.distance); }),
distances = distance.group(function(d) { return Math.floor(d / 50) * 50; });
var charts = [
barChart()
.dimension(hour)
.group(hours)
.x(d3.scale.linear()
.domain([0, 24])
.rangeRound([0, 10 * 24])),
barChart()
.dimension(delay)
.group(delays)
.x(d3.scale.linear()
.domain([-60, 150])
.rangeRound([0, 10 * 21])),
barChart()
.dimension(distance)
.group(distances)
.x(d3.scale.linear()
.domain([0, 2000])
.rangeRound([0, 10 * 40])),
barChart()
.dimension(date)
.group(dates)
.round(d3.time.day.round)
.x(d3.time.scale()
.domain([new Date(2001, 0, 1), new Date(2001, 3, 1)])
.rangeRound([0, 10 * 90]))
.filter([new Date(2001, 1, 1), new Date(2001, 2, 1)])
];
// Given our array of charts, which we assume are in the same order as the
// .chart elements in the DOM, bind the charts to the DOM and render them.
// We also listen to the chart's brush events to update the display.
var chart = d3.selectAll(".chart")
.data(charts)
.each(function(chart) { chart.on("brush", renderAll).on("brushend", renderAll); });
// Render the initial lists.
var list = d3.selectAll(".list")
.data([flightList]);
// Render the total.
d3.selectAll("#total")
.text(formatNumber(flight.size()));
renderAll();
// Renders the specified chart or list.
function render(method) {
d3.select(this).call(method);
}
// Whenever the brush moves, re-rendering everything.
function renderAll() {
chart.each(render);
list.each(render);
d3.select("#active").text(formatNumber(all.value()));
}
// Like d3.time.format, but faster.
function parseDate(d) {
return new Date(2001,
d.substring(0, 2) - 1,
d.substring(2, 4),
d.substring(4, 6),
d.substring(6, 8));
}
window.filter = function(filters) {
filters.forEach(function(d, i) { charts[i].filter(d); });
renderAll();
};
window.reset = function(i) {
charts[i].filter(null);
renderAll();
};
function flightList(div) {
var flightsByDate = nestByDate.entries(date.top(40));
div.each(function() {
var date = d3.select(this).selectAll(".date")
.data(flightsByDate, function(d) { return d.key; });
date.enter().append("div")
.attr("class", "date")
.append("div")
.attr("class", "day")
.text(function(d) { return formatDate(d.values[0].date); });
date.exit().remove();
var flight = date.order().selectAll(".flight")
.data(function(d) { return d.values; }, function(d) { return d.index; });
var flightEnter = flight.enter().append("div")
.attr("class", "flight");
flightEnter.append("div")
.attr("class", "time")
.text(function(d) { return formatTime(d.date); });
flightEnter.append("div")
.attr("class", "origin")
.text(function(d) { return d.origin; });
flightEnter.append("div")
.attr("class", "destination")
.text(function(d) { return d.destination; });
flightEnter.append("div")
.attr("class", "distance")
.text(function(d) { return formatNumber(d.distance) + " mi."; });
flightEnter.append("div")
.attr("class", "delay")
.classed("early", function(d) { return d.delay < 0; })
.text(function(d) { return formatChange(d.delay) + " min."; });
flight.exit().remove();
flight.order();
});
}
function barChart() {
if (!barChart.id) barChart.id = 0;
var margin = {top: 10, right: 10, bottom: 20, left: 10},
x,
y = d3.scale.linear().range([100, 0]),
id = barChart.id++,
axis = d3.svg.axis().orient("bottom"),
brush = d3.svg.brush(),
brushDirty,
dimension,
group,
round;
function chart(div) {
var width = x.range()[1],
height = y.range()[0];
y.domain([0, group.top(1)[0].value]);
div.each(function() {
var div = d3.select(this),
g = div.select("g");
// Create the skeletal chart.
if (g.empty()) {
div.select(".title").append("a")
.attr("href", "javascript:reset(" + id + ")")
.attr("class", "reset")
.text("reset")
.style("display", "none");
g = div.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
g.append("clipPath")
.attr("id", "clip-" + id)
.append("rect")
.attr("width", width)
.attr("height", height);
g.selectAll(".bar")
.data(["background", "foreground"])
.enter().append("path")
.attr("class", function(d) { return d + " bar"; })
.datum(group.all());
g.selectAll(".foreground.bar")
.attr("clip-path", "url(#clip-" + id + ")");
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(axis);
// Initialize the brush component with pretty resize handles.
var gBrush = g.append("g").attr("class", "brush").call(brush);
gBrush.selectAll("rect").attr("height", height);
gBrush.selectAll(".resize").append("path").attr("d", resizePath);
}
// Only redraw the brush if set externally.
if (brushDirty) {
brushDirty = false;
g.selectAll(".brush").call(brush);
div.select(".title a").style("display", brush.empty() ? "none" : null);
if (brush.empty()) {
g.selectAll("#clip-" + id + " rect")
.attr("x", 0)
.attr("width", width);
} else {
var extent = brush.extent();
g.selectAll("#clip-" + id + " rect")
.attr("x", x(extent[0]))
.attr("width", x(extent[1]) - x(extent[0]));
}
}
g.selectAll(".bar").attr("d", barPath);
});
function barPath(groups) {
var path = [],
i = -1,
n = groups.length,
d;
while (++i < n) {
d = groups[i];
path.push("M", x(d.key), ",", height, "V", y(d.value), "h9V", height);
}
return path.join("");
}
function resizePath(d) {
var e = +(d == "e"),
x = e ? 1 : -1,
y = height / 3;
return "M" + (.5 * x) + "," + y
+ "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6)
+ "V" + (2 * y - 6)
+ "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y)
+ "Z"
+ "M" + (2.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8)
+ "M" + (4.5 * x) + "," + (y + 8)
+ "V" + (2 * y - 8);
}
}
brush.on("brushstart.chart", function() {
var div = d3.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", null);
});
brush.on("brush.chart", function() {
var g = d3.select(this.parentNode),
extent = brush.extent();
if (round) g.select(".brush")
.call(brush.extent(extent = extent.map(round)))
.selectAll(".resize")
.style("display", null);
g.select("#clip-" + id + " rect")
.attr("x", x(extent[0]))
.attr("width", x(extent[1]) - x(extent[0]));
dimension.filterRange(extent);
});
brush.on("brushend.chart", function() {
if (brush.empty()) {
var div = d3.select(this.parentNode.parentNode.parentNode);
div.select(".title a").style("display", "none");
div.select("#clip-" + id + " rect").attr("x", null).attr("width", "100%");
dimension.filterAll();
}
});
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return x;
x = _;
axis.scale(x);
brush.x(x);
return chart;
};
chart.y = function(_) {
if (!arguments.length) return y;
y = _;
return chart;
};
chart.dimension = function(_) {
if (!arguments.length) return dimension;
dimension = _;
return chart;
};
chart.filter = function(_) {
if (_) {
brush.extent(_);
dimension.filterRange(_);
} else {
brush.clear();
dimension.filterAll();
}
brushDirty = true;
return chart;
};
chart.group = function(_) {
if (!arguments.length) return group;
group = _;
return chart;
};
chart.round = function(_) {
if (!arguments.length) return round;
round = _;
return chart;
};
return d3.rebind(chart, brush, "on");
}
// });
this.autorun(function(){
})
}
}
if (Meteor.isServer) {
Meteor.startup(function () {
});
}
If this helps, here is my attempt at reproducing one of the d3 force layout examples with collision detection / custom gravity functions https://gist.github.com/gmlnchv/80dd206440cca39800b8. I'm using observe() to react to changes.
I have an openlayers 3 map where I can draw Polygon.
I would have returned the WKT String that rapresent the drawed polygon.
How Can I do it?
JSFiddle Code http://jsfiddle.net/michelejs/3zawt33b/7/
Here my map:
map = new ol.Map({
target: 'map',
layers: [raster,vector],
view: new ol.View({
center: ol.proj.fromLonLat([11.249367, 43.774298]),
zoom: 15
})
});
Here the intaractions that help me to draw the polygon:
function addInteraction() {
var ct = 0;
draw = new ol.interaction.Draw({
source: source,
type: 'Polygon',
geometryFunction: function (c, g) {
if (goog.isDef(g)) {
g.setCoordinates(c);
} else {
g = new ol.geom.Polygon(c);
}
if (c[0].length > ct) {
console.log('click coord : ' + c[0][c[0].length - 1]);
var coord = c[0][c[0].length - 1];
$('div#coordinate').html( $('div#coordinate').html() + "<p>" + ( Number(coord[0]).toFixed(2) ) + " - " + ( Number(coord[1]).toFixed(2) ) + "</p>" );
coordinates.push(coord);
ct = c[0].length;
} else {
console.log('move coord : ' + c[0][c[0].length - 1]);
}
return g;
}
});
draw.on('drawend', function(e) {
isin = e;
checkIfIn();
lastFeature = e.feature;
//write WKT Polygon Code in div#getAsWK
})
draw.on('drawstart', function (e) {
source.clear();
});
map.addInteraction(draw);
}
map.addInteraction(draw);
ol3 contains the ol.format.WKT class for this purpose.
Use writeGeometry() method like this:
var format = new ol.format.WKT(),
wkt = format.writeGeometry(yourFeature.getGeometry());
See API-docs: http://openlayers.org/en/v3.0.0/apidoc/ol.format.WKT.html#writeGeometry
Fiddle: http://jsfiddle.net/igor23/3zawt33b/9/
I want to put multi pie chart on d3 map but I'm confused how to do it.
I'm noob at d3 so I searched about the issue and made below code.
Someone suggested that I should make two seperate svgs so I did it.
There are two svgs. one has a map, and the other has multiple pie chart.
Now I have a problem with mapping these pies on map.
I don't know how to map these pies with geo coordinates...
It's just browser(?) coordinates so I want to change it.
and I want to zoom and pan map correctly with pies...
hope your help...
summary
I have two problems
putting multiple pie charts on right map place
zoom and pan correctly
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script>
var width = 960, height = 500;
var projection = d3.geo.mercator().center([ 127.46, 36.00 ])
.scale(4000).translate([ width / 2, height / 2 ]);
var color = d3.scale.category20();
//first svg
var svg = d3.select("body").append("svg").attr("width", width).attr(
"height", height);
var path = d3.geo.path().projection(projection);
var radius = 30;
var arc = d3.svg.arc().innerRadius(radius - 100).outerRadius(
radius - 20);
var g = svg.append("g");
var rawFile = new XMLHttpRequest();
rawFile.open("GET", "/tourdata.txt", false);
var allText;
rawFile.onreadystatechange = function() {
if (rawFile.readyState === 4) {
if (rawFile.status === 200 || rawFile.status == 0) {
allText = rawFile.responseText;
}
}
}
rawFile.send(null);
var lines = allText.split('\n');
var timeTable = new Array();
var res = lines[0].split(", ");
var currentTime = res[1];
var currentDate = res[0];
var timeInformation = new Array();
var corInformation = new Array();
var timeCorTable = new Array();
for ( var i = 0; i < lines.length; i++) {
var res = lines[i].split(", ");
if (currentDate != res[0] || currentTime != res[1]) {
currentDate = res[0];
currentTime = res[1];
timeTable[currentDate + ":" + currentTime] = timeInformation;
timeCorTable[currentDate + ":" + currentTime] = timeCorTable;
timeInformation = new Array();
corInformation = new Array();
}
if (timeInformation[res[2] + "," + res[3]] == undefined) {
corInformation.push(res[2] + "," + res[3]);
timeInformation.push([ res[4], res[5], res[6], res[7], res[8],
res[9], res[10], res[11], res[12], res[13] ]);
}
}
timeTable.push(timeInformation);
timeCorTable.push(corInformation);
var data = timeTable;
console.log(timeCorTable[0]);
// load and display the World
d3.json("kor.json",
function(error, topology) {
// load and display the cities
var geo = topojson.object(topology,
topology.objects['kor.geo']).geometries;
g.selectAll('path').data(geo).enter().append('path').attr(
'd', path)
});
console.log(data[0]);
//second svg that has multiple pies
var svgSvg = d3.select("body").select("svg").selectAll("g").data(timeTable[0]).enter().append("svg:svg").attr(
"width", width).attr("height", height).append("svg:g").style(//svg:g make pie group
"opacity", 0.8).attr("transform", function(d, i) {
var split = timeCorTable[0][i].split(",");
//console.log(split[0]);
var point = [split[0], split[1]];
console.log(projection(point[0], point[1])[0]);
return ("translate(" + projection(point[0], point[1])[0] + "," + projection(point[0], point[1])[1] + ")");
});
svgSvg.selectAll("path").data(d3.layout.pie()).enter().append(
"svg:path").attr("d",
d3.svg.arc().innerRadius(10).outerRadius(30)).style("fill", function(d, i) { return color(i); });
// zoom and pan
var zoom = d3.behavior.zoom().on(
"zoom",
function() {
g.attr("transform", "translate("
+ d3.event.translate.join(",") + ")scale("
+ d3.event.scale + ")");
g.selectAll("path").attr("d", path.projection(projection));
svgSvg.attr("transform", "translate("
+ d3.event.translate.join(",") + ")scale("
+ d3.event.scale + ")");
});
svg.call(zoom)
</script>
http://i.imgur.com/F2Nqz4b.png?1
Hello Community! That in the picture is what I'd like to achieve...
This is my working code so far (associated to the left half of the picture):
var diagramElement = this.getElement();
var links = eval(this.getState().string);
// string = e.g. "[{source: \"Germany\", target: \"Europe\", property: \"is type\", type: \"connection\"}]"
var width = 800, height = 300;
var svg = d3.select(diagramElement).append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.size([width, height])
.linkDistance(100)
.charge(-400)
.on("tick", tick);
var link;
var circle;
var text;
var nodes = {};
var linktext;
svg.append("defs")
.selectAll("marker").data(["connection", "new"]).enter()
.append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", 0)
.attr("markerWidth", 8)
.attr("markerHeight", 8)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.onStateChange = function() {
svg.selectAll("g").remove();
nodes = {};
links = [];
links = eval(this.getState().string);
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name : link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name : link.target});
});
force
.nodes(d3.values(nodes))
.links(links)
.on("tick", tick)
.start();
link = svg.append("g").selectAll("line.link")
.data(force.links())
.enter()
.append("svg:line")
.attr("class", function(d) {return "link " + d.type;})
.attr("marker-end", function(d) {return "url(#" + d.type + ")";});
circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter()
.append("circle")
.attr("r", 8)
.call(force.drag);
text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter()
.append("text")
.style("font-size","15px")
.attr("x", 10)
.attr("y", ".42em")
.text(function(d) {return d.name;});
// this is where the linktext is aligned relatively to the links
// must change something here
linktext = svg.append("g").selectAll("text")
.data(force.links())
.enter()
.append("text")
.style("font-size","15px")
.attr("dx", 1)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {return d.type;});
};
function tick() {
circle.attr("cx", function(d) { return d.x = Math.max(12, Math.min(798 - 12, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(12, Math.min(279 - 12, d.y)); });
text.attr("transform", transform);
// linktext position update
linktext.attr("transform", function(d) {return "translate(" + (d.source.x + d.target.x) / 2 + "," + (d.source.y + d.target.y) / 2 + ")";});
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
So I've appended the text successfully to the links, but now how would I position in slightly above the link, in link direction, as shown in my attached picture?
I appreciate any help
Inside on('tick') after determining the position of your links you can also apply some transformations like so:
linkLabels
.attr('transform', d => {
// Adding to the transform. Multiple transforms would just get overwritten.
let transformation = ``
// The text position should be at the middle of the line
const x = (d.source.x + d.target.x) / 2
const y = (d.source.y + d.target.y) / 2
transformation += `translate(${x}, ${y}) `
// Make sure the text is always upright
if(d.source.x > d.target.x) {
transformation += `rotate(180) `
}
// Aligning text to the slope.
// The formula for the tangent of a slope is (y2 - y1) / (x2 - x1)
// where (xi, yi) are the coordinates of the line endings
const angle = Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x)
transformation += `rotate(${angle * 180/Math.PI}) `
return transformation
})