I am still trying to get a graph that I have produced using rCharts into a slidify slide deck. I have tried copying the script that rCharts produces into slidify's .Rmd file but all I get is an empty slide.
Here is the .Rmd file:
---
title : Test Sankey
subtitle :
author : Matthew
job :
framework : io2012 # {io2012, html5slides, shower, dzslides, ...}
highlighter : highlight.js # {highlight.js, prettify, highlight}
hitheme : tomorrow #
widgets : [d3_sankey] # {mathjax, quiz, bootstrap}
mode : selfcontained # {standalone, draft}
---
Slide 1
---
The graph should be on the next slide.
---
<div id='sankey1' class='rChart d3_sankey'></div>
<script>
(function(){
var params = {
"dom": "sankey1",
"width": 960,
"height": 500,
"data": {
"source": [ "a", "b", "c", "d", "e", "a", "d", "f", "e", "g", "h", "j", "j", "k", "l", "q" ],
"target": [ "j", "d", "a", "q", "f", "k", "e", "g", "j", "e", "l", "c", "h", "b", "d", "a" ],
"value": [ 8, 1, 4, 6, 2, 1, 1, 1, 6, 9, 2, 2, 4, 6, 9, 1 ]
},
"nodeWidth": 15,
"nodePadding": 10,
"layout": 32,
"id": "sankey1"
};
params.units ? units = " " + params.units : units = "";
//hard code these now but eventually make available
var formatNumber = d3.format("0,.0f"), // zero decimal places
format = function(d) { return formatNumber(d) + units; },
color = d3.scale.category20();
if(params.labelFormat){
formatNumber = d3.format(".2%");
}
var svg = d3.select('#' + params.id).append("svg")
.attr("width", params.width)
.attr("height", params.height);
var sankey = d3.sankey()
.nodeWidth(params.nodeWidth)
.nodePadding(params.nodePadding)
.layout(params.layout)
.size([params.width,params.height]);
var path = sankey.link();
var data = params.data,
links = [],
nodes = [];
//get all source and target into nodes
//will reduce to unique in the next step
//also get links in object form
data.source.forEach(function (d, i) {
nodes.push({ "name": data.source[i] });
nodes.push({ "name": data.target[i] });
links.push({ "source": data.source[i], "target": data.target[i], "value": +data.value[i] });
});
//now get nodes based on links data
//thanks Mike Bostock https://groups.google.com/d/msg/d3-js/pl297cFtIQk/Eso4q_eBu1IJ
//this handy little function returns only the distinct / unique nodes
nodes = d3.keys(d3.nest()
.key(function (d) { return d.name; })
.map(nodes));
//it appears d3 with force layout wants a numeric source and target
//so loop through each link replacing the text with its index from node
links.forEach(function (d, i) {
links[i].source = nodes.indexOf(links[i].source);
links[i].target = nodes.indexOf(links[i].target);
});
//now loop through each nodes to make nodes an array of objects rather than an array of strings
nodes.forEach(function (d, i) {
nodes[i] = { "name": d };
});
sankey
.nodes(nodes)
.links(links)
.layout(params.layout);
var link = svg.append("g").selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function (d) { return Math.max(1, d.dy); })
.sort(function (a, b) { return b.dy - a.dy; });
link.append("title")
.text(function (d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var node = svg.append("g").selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function (d) { return d; })
.on("dragstart", function () { this.parentNode.appendChild(this); })
.on("drag", dragmove));
node.append("rect")
.attr("height", function (d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function (d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function (d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
node.append("text")
.attr("x", -6)
.attr("y", function (d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function (d) { return d.name; })
.filter(function (d) { return d.x < params.width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
// the function for moving the nodes
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + (
d.x = Math.max(0, Math.min(params.width - d.dx, d3.event.x))
) + "," + (
d.y = Math.max(0, Math.min(params.height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", path);
}
})();
</script>
When I compile the .Rmd file in R I get the following output:
slidify("index.Rmd")
processing file: index.Rmd
|.................................................................| 100%
ordinary text without R code
output file: index.md
Copying files to libraries/frameworks/io2012...
Copying files to libraries/highlighters/highlight.js...
Copying files to libraries/widgets/d3_sankey...
[1] "index.html"
Any idea on what I am doing wrong here? Thank you very much!
Related
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);
}
Using the forceNetwork function of the networkD3 package, it is possible to create an interactive network graph that can show the node names when hovering over them.
I am trying to create a graph that not only shows the node where the mouse is hovering over, but also all neighboring nodes, i.e. all nodes that are directly connected to the selected node. However, it should not show any nodes that are not directly connected to the node.
Although I found the argument opacityNoHover, it will affect all the nodes that the mouse is not covering and not just the nodes with a direct connection.
library(networkD3)
# example data
data(MisLinks)
data(MisNodes)
# creating the plot
forceNetwork(Links = MisLinks, Nodes = MisNodes,
Source = "source", Target = "target",
Value = "value", NodeID = "name",
Group = "group", opacity = 1, fontSize = 15,
opacityNoHover = 0)
You could re-write the mouseover and mouseout functions and override them with htmlwidgets::onRender...
library(networkD3)
library(htmlwidgets)
data(MisLinks)
data(MisNodes)
fn <- forceNetwork(Links = MisLinks, Nodes = MisNodes, Source = "source",
Target = "target", Value = "value", NodeID = "name",
Group = "group", opacity = 1, fontSize = 15,
opacityNoHover = 0)
customJS <- '
function(el,x) {
var link = d3.selectAll(".link")
var node = d3.selectAll(".node")
var options = { opacity: 1,
clickTextSize: 10,
opacityNoHover: 0.1,
radiusCalculation: "Math.sqrt(d.nodesize)+6"
}
var unfocusDivisor = 4;
var links = HTMLWidgets.dataframeToD3(x.links);
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source + "," + d.target] = 1;
linkedByIndex[d.target + "," + d.source] = 1;
});
function neighboring(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function nodeSize(d) {
if(options.nodesize){
return eval(options.radiusCalculation);
}else{
return 6}
}
function mouseover(d) {
var unfocusDivisor = 4;
link.transition().duration(200)
.style("opacity", function(l) { return d != l.source && d != l.target ? +options.opacity / unfocusDivisor : +options.opacity });
node.transition().duration(200)
.style("opacity", function(o) { return d.index == o.index || neighboring(d, o) ? +options.opacity : +options.opacity / unfocusDivisor; });
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", function(d){return nodeSize(d)+5;});
node.select("text").transition()
.duration(750)
.attr("x", 13)
.style("stroke-width", ".5px")
.style("font", 24 + "px ")
.style("opacity", function(o) { return d.index == o.index || neighboring(d, o) ? 1 : 0; });
}
function mouseout() {
node.style("opacity", +options.opacity);
link.style("opacity", +options.opacity);
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", function(d){return nodeSize(d);});
node.select("text").transition()
.duration(1250)
.attr("x", 0)
.style("font", options.fontSize + "px ")
.style("opacity", 0);
}
d3.selectAll(".node").on("mouseover", mouseover).on("mouseout", mouseout);
}
'
onRender(fn, customJS)
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.
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
})