D3.js Draw Circle between dash - svg

tried trying to draw circle and dash in one path
like this : ..--..--..--..--..--..--..
2 dot and 2 dashes and how can handle it with function
like this :
function Draw_Dash_And_Circle(x1,y1,x2,y2){
draw dash and circle on the single line
}
i type this code and work well
but need logical code for tracing dots location
<!-- Two dash Two Dot -->
var x1,x2,y1,y2,cx1,cy1,cx2,cy2;
x1 = 0;
x2 = 1;
y1 = 90;
y2 = 90;
for (let i = 0; i < 100; i++) {
svg.append('line')
.style("stroke", "black")
.style("stroke-width", 0.5)
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2);
x1 += 2;
x2 += 2;
svg.append('line')
.style("stroke", "black")
.style("stroke-width", 0.5)
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2);
cx1 = x2 + 3;
svg.append('circle')
.attr("cx",cx1)
.attr("r",0.5)
.attr("cy",y2);
cx2 = cx1 + 2;
svg.append('circle')
.attr("cx",cx2 )
.attr("r",0.5)
.attr("cy",y2);
x2 = cx2 + 2;
x1 = cx2 + 3;
}
please help me
thanks.

While you could append circles and lines or other elements along a path or line's route, it is probably easier to append two paths/lines and use stroke dash arrays and stroke line caps to style the lines into a collection of circles and/or squares/rectangles:
Eg for lines:
selection.append("line")
.attr("x1", d=>d.x1)
.attr("x2", d=>d.x2)
.attr("y1", d=>d.y1)
.attr("y2", d=>d.y2)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-linecap","round");
selection.append("line")
.attr("x1", d=>d.x1)
.attr("x2", d=>d.x2)
.attr("y1", d=>d.y1)
.attr("y2", d=>d.y2)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-dashoffset", "32" )
.attr("stroke-linecap","square")
Or for paths:
var line = d3.line()
.curve(d3.curveBasis);
selection.append("path")
.attr("d",line)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-linecap","round")
.style("fill","none")
selection.append("path")
.attr("d",line)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-dashoffset", "32" )
.attr("stroke-linecap","square")
.style("fill","none")
Which gives something like:
var svg = d3.select("svg");
var linedata = [
{x1: 100, y1: 50, x2: 300, y2: 50},
{x1: 100, y1: 250, x2: 300, y2: 250}
]
svg.selectAll("g")
.data(linedata)
.join("g")
.call(line);
function line(selection) {
selection.append("line")
.attr("x1", d=>d.x1)
.attr("x2", d=>d.x2)
.attr("y1", d=>d.y1)
.attr("y2", d=>d.y2)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-linecap","round");
selection.append("line")
.attr("x1", d=>d.x1)
.attr("x2", d=>d.x2)
.attr("y1", d=>d.y1)
.attr("y2", d=>d.y2)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-dashoffset", "32" )
.attr("stroke-linecap","square")
}
var pathdata = [
[50,100],
[150,200],
[250,100],
[350,200]
]
svg.append("g")
.datum(pathdata)
.call(path);
function path(selection) {
var line = d3.line()
.curve(d3.curveBasis);
selection.append("path")
.attr("d",line)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-linecap","round")
.style("fill","none")
selection.append("path")
.attr("d",line)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-dashoffset", "32" )
.attr("stroke-linecap","square")
.style("fill","none")
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<svg width= "500" height="300"></svg>
Also easy to animate:
var svg = d3.select("svg");
var linedata = [
{x1: 100, y1: 50, x2: 300, y2: 50},
{x1: 100, y1: 250, x2: 300, y2: 250}
]
svg.selectAll("g")
.data(linedata)
.join("g")
.call(line);
function line(selection) {
selection.append("line")
.attr("x1", d=>d.x1)
.attr("x2", d=>d.x2)
.attr("y1", d=>d.y1)
.attr("y2", d=>d.y2)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-linecap","round")
.transition()
.attr("stroke-dashoffset",1000)
.duration(20000)
selection.append("line")
.attr("x1", d=>d.x1)
.attr("x2", d=>d.x2)
.attr("y1", d=>d.y1)
.attr("y2", d=>d.y2)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-dashoffset", "32" )
.attr("stroke-linecap","square")
.transition()
.attr("stroke-dashoffset",1032)
.duration(20000)
}
var pathdata = [
[50,100],
[150,200],
[250,100],
[350,200]
]
svg.append("g")
.datum(pathdata)
.call(path);
function path(selection) {
var line = d3.line()
.curve(d3.curveBasis);
selection.append("path")
.attr("d",line)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-linecap","round")
.style("fill","none")
.transition()
.attr("stroke-dashoffset",1000)
.duration(20000)
selection.append("path")
.attr("d",line)
.attr("stroke","black")
.attr("stroke-width", 12)
.attr("stroke-dasharray", "0 16 0 48")
.attr("stroke-dashoffset", "32" )
.attr("stroke-linecap","square")
.style("fill","none")
.transition()
.attr("stroke-dashoffset",1032)
.duration(20000)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<svg width= "500" height="300"></svg>

Related

D3.js curved node label

I want to exchange the current node label with a curved label, placed above the node. I miss knowledge regarding, how to use paths proper and how to append a text to a path. Maybe you guys could enlight me.
The text should have the same curve as the node itself.
UPDATE
I implemented the solution but it seems either the path or textPath is to short. Its possible to adjust the innerRadius, outerRadius as well as the startAngle and endAngle. I guess the startAngle and endAngle define the length of the arc. No matter which value I test, the full label isn´t shown.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Curved Text</title>
<!-- Script Import -->
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<style>
body {
background: white;
overflow: hidden;
margin: 0px;
}
circle {
fill: whitesmoke;
stroke-width: 2;
stroke: black;
transform: scale(1);
transition: all 200ms ease-in-out;
}
circle:hover {
transform: scale(1.5)
}
</style>
<body>
<svg id="svg"></svg>
<script>
var width = window.innerWidth,
height = window.innerHeight,
radius = 40, // circle radius
offset = 35; // arrow offset
const svg = d3.select('svg')
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
var graph = {
"nodes": [
{ "id": "Stackoverflow" },
{ "id": "Reddit" },
{ "id": "Google" }
],
"links": [
{ "source": "Stackoverflow", "target": "Reddit"},
{ "source": "Reddit", "target": "Google"},
{ "source": "Google", "target": "Stackoverflow"},
]
}
const arc = d3.arc()
.innerRadius(radius + 5)
.outerRadius(radius + 5)
.startAngle(-Math.PI / 15)
.endAngle(Math.PI / 2 )
svg.append("defs")
.append("path")
.attr("id", "curvedLabelPath")
.attr("d", arc())
svg.append('defs').append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '-0 -5 10 10')
.attr('refX', 0)
.attr('refY', 0)
.attr('orient', 'auto')
.attr('markerWidth', 10)
.attr('markerHeight', 10)
.attr('xoverflow', 'visible')
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke', 'none');
var linksContainer = svg.append("g").attr("class", "linksContainer")
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
const simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id }).distance(250))
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(radius))
link = linksContainer.selectAll("g")
.data(graph.links)
.join("g")
.attr("curcor", "pointer")
link = linksContainer.selectAll("path")
.data(graph.links)
.join("path")
.attr("id", function (_, i) {
return "path" + i
})
.attr("stroke", "#000000")
.attr("opacity", 0.75)
.attr("stroke-width", 3)
.attr("fill", "transparent")
.attr("marker-end", "url(#arrowhead)")
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("cursor", "pointer")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("mouseenter", function (d) {
d3.select(this).select("text").attr("font-size", 15)
})
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", radius)
node.append("text")
.append("textPath")
.attr("href", "#curvedLabelPath")
.attr("text-anchor", "middle")
.attr("startoffset", "5%")
.text(function (d) {
return d.id
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links);
function ticked() {
link.attr("d", function (d) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
// Neuberechnung der Distanz
link.attr("d", function (d) {
// Länge des aktuellen Paths
var pl = this.getTotalLength(),
// Kreis Radius und Distanzwert
r = radius + offset,
// Umlaufposition wo der Path den Kreis berührt
m = this.getPointAtLength(pl - r);
var dx = m.x - d.source.x,
dy = m.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + m.x + "," + m.y;
});
node
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
dataFlow()
function dataFlow() {
var lines = linksContainer.selectAll("path")
dataflow = window.setInterval(function () {
lines.style("stroke-dashoffset", offset)
.style("stroke", "black")
.style("stroke-dasharray", 5)
.style("opacity", 0.5)
offset -= 1
}, 40)
var offset = 1;
}
</script>
</body>
</html>
Here's an example for one circle and label that uses a <textPath>.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
// set up
const width = 200;
const height = 200;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height);
// radius of the circle that the label is above
const radius = 50;
// arc generator for the curved path
const arc = d3.arc()
// add a bit of space so that the label
// won't be right on the circle
.innerRadius(radius + 5)
.outerRadius(radius + 5)
.startAngle(-Math.PI / 2)
.endAngle(Math.PI / 2);
// add the path that the label will follow to <defs>
svg.append('defs')
.append('path')
.attr('id', 'curvedLabelPath')
.attr('d', arc());
// create a group for the circle and label
const g = svg.append('g')
.attr('transform', `translate(${width / 2},${height / 2})`);
// draw the circle
g.append('circle')
.attr('stroke', 'black')
.attr('fill', '#d3d3d3')
.attr('r', radius);
// draw the label
g.append('text')
.append('textPath')
.attr('href', '#curvedLabelPath')
// these two lines center along the arc.
// the offset is 25% instead of 50% because d3.arc() creates
// an arc that has an outer and inner part. in this case,
// each parth is ~50% of the path, so the middle of the
// outer arc is 25%
.attr('text-anchor', 'middle')
.attr('startOffset', '25%')
.text('stackoverflow');
</script>
</body>
</html>
Here are the changes to your updated code to make the labels not get cutoff, as explained in my comment below:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Curved Text</title>
<!-- Script Import -->
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<style>
body {
background: white;
overflow: hidden;
margin: 0px;
}
circle {
fill: whitesmoke;
stroke-width: 2;
stroke: black;
transform: scale(1);
transition: all 200ms ease-in-out;
}
circle:hover {
transform: scale(1.5)
}
</style>
<body>
<svg id="svg"></svg>
<script>
var width = window.innerWidth,
height = window.innerHeight,
radius = 40, // circle radius
offset = 35; // arrow offset
const svg = d3.select('svg')
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
var graph = {
"nodes": [
{ "id": "Stackoverflow" },
{ "id": "Reddit" },
{ "id": "Google" }
],
"links": [
{ "source": "Stackoverflow", "target": "Reddit"},
{ "source": "Reddit", "target": "Google"},
{ "source": "Google", "target": "Stackoverflow"},
]
}
const arc = d3.arc()
.innerRadius(radius + 5)
.outerRadius(radius + 5)
.startAngle(-Math.PI / 2)
.endAngle(Math.PI / 2 )
svg.append("defs")
.append("path")
.attr("id", "curvedLabelPath")
.attr("d", arc())
svg.append('defs').append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '-0 -5 10 10')
.attr('refX', 0)
.attr('refY', 0)
.attr('orient', 'auto')
.attr('markerWidth', 10)
.attr('markerHeight', 10)
.attr('xoverflow', 'visible')
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke', 'none');
var linksContainer = svg.append("g").attr("class", "linksContainer")
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
const simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id }).distance(250))
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(radius))
link = linksContainer.selectAll("g")
.data(graph.links)
.join("g")
.attr("curcor", "pointer")
link = linksContainer.selectAll("path")
.data(graph.links)
.join("path")
.attr("id", function (_, i) {
return "path" + i
})
.attr("stroke", "#000000")
.attr("opacity", 0.75)
.attr("stroke-width", 3)
.attr("fill", "transparent")
.attr("marker-end", "url(#arrowhead)")
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("cursor", "pointer")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("mouseenter", function (d) {
d3.select(this).select("text").attr("font-size", 15)
})
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", radius)
node.append("text")
.append("textPath")
.attr("href", "#curvedLabelPath")
.attr("text-anchor", "middle")
.attr("startOffset", "25%")
.text(function (d) {
return d.id
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links);
function ticked() {
link.attr("d", function (d) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
// Neuberechnung der Distanz
link.attr("d", function (d) {
// Länge des aktuellen Paths
var pl = this.getTotalLength(),
// Kreis Radius und Distanzwert
r = radius + offset,
// Umlaufposition wo der Path den Kreis berührt
m = this.getPointAtLength(pl - r);
var dx = m.x - d.source.x,
dy = m.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + m.x + "," + m.y;
});
node
.attr("transform", d => `translate(${d.x}, ${d.y})`);
}
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
dataFlow()
function dataFlow() {
var lines = linksContainer.selectAll("path")
dataflow = window.setInterval(function () {
lines.style("stroke-dashoffset", offset)
.style("stroke", "black")
.style("stroke-dasharray", 5)
.style("opacity", 0.5)
offset -= 1
}, 40)
var offset = 1;
}
</script>
</body>
</html>

Why are some of the grid lines randomly disappearing on my responsive D3 chart?

I have created a stripped down JSFiddle of my D3 chart. I have made it responsive (with viewbox and preserveaspectratio) using the solution at:
responsive D3 chart
When I resize the window and make it smaller, some of the grid lines seem to be disappearing and reappearing. I presume this will look bad at small resolutions (eg. 320x480 mobile phone). Is there a way to preserve my gridlines when the window gets resized smaller?
HTML code:
<!--//d3 chart//-->
<div class="centre-div"></div>
CSS Code:
.centre-div {
margin: 0 auto;
max-width: 550px;
}
/* D3 chart css */
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
}
JS code:
//function createScatterplot() {
//Width and height
var margin = {
top: 15,
right: 2,
bottom: 2,
left: 2
};
//define width and height as the inner dimensions of the chart area.
var width = 550 - margin.left - margin.right;
var height = 550 - margin.top - margin.bottom;
var padding = 10;
//define svg as a G element that translates the origin to the top-left corner of the chart area.
//add <svg> to the last <div class="centre-div"> tag on the html page
//this allows me to reuse the createScatterplot() function to draw multiple charts
var svg = d3.select(d3.selectAll(".centre-div")[0].pop()).append("svg")
//.attr("width", width + margin.left + margin.right)
//.attr("height", height + margin.top + margin.bottom)
//make svg responsive
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", "0 0 550 550")
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//With this convention, all subsequent code can ignore margins.
//http://bl.ocks.org/mbostock/3019563
//Static dataset
var dataset = [
[5, -2, "A"],
[-4, -9, "B"],
[2, 5, "C"],
[1, -3, "D"],
[-3, 5, "E"],
[4, 1, "F"],
[4, 4, "G"],
[5, 7, "H"],
[-5, -2, "I"],
[0, 8, "J"],
[-6, -5, "K"]
];
//Create scale functions
var xScale = d3.scale.linear()
.domain([-10, 11])
.range([padding, width - padding * 2]);
var yScale = d3.scale.linear()
.domain([-10, 11])
.range([height - padding, padding]);
//different scale for gridlines, so last tick has no line
var xScale2 = d3.scale.linear()
.domain([-10, 10])
.range([padding, width - padding * 2]);
var yScale2 = d3.scale.linear()
.domain([-10, 10])
.range([height - padding, padding]);
//add arrowheads
defs = svg.append("defs")
defs.append("marker")
.attr({
"id": "arrow",
"viewBox": "-5 -5 10 10",
"refX": 0,
"refY": 0,
"markerWidth": 7, //marker size
"markerHeight": 7, //marker size
"orient": "auto"
})
.append("path")
.attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
.attr("fill", "#000");
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(22)
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(22)
//create scatterplot crosses
svg.selectAll("line.diag1")
.data(dataset)
.enter()
.append("line")
.attr({
"class": "diag1",
"x1": function(d) {
return xScale(d[0]) - 4;
},
"y1": function(d) {
return yScale(d[1]) - 4;
},
"x2": function(d) {
return xScale(d[0]) + 4;
},
"y2": function(d) {
return yScale(d[1]) + 4;
},
"stroke": "#006CCA",
"opacity": "1",
"stroke-width": "2px"
});
svg.selectAll("line.diag2")
.data(dataset)
.enter()
.append("line")
.attr({
"class": "diag2",
"x1": function(d) {
return xScale(d[0]) + 4;
},
"y1": function(d) {
return yScale(d[1]) - 4;
},
"x2": function(d) {
return xScale(d[0]) - 4;
},
"y2": function(d) {
return yScale(d[1]) + 4;
},
"stroke": "#006CCA",
"opacity": "1",
"stroke-width": "2px"
});
//Create X axis
svg.append("g")
.attr("class", "axis")
.style("stroke-width", 2)
.attr("transform", "translate(0," + 11 * (height) / 21 + ")")
.call(xAxis)
//add x label
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", 15)
.attr("font-style", "italic")
.attr("font-weight", "bold")
.style("text-anchor", "end")
.text("x");
//Create Y axis
svg.append("g")
.attr("class", "axis")
.style("stroke-width", 2)
.attr("transform", "translate(" + 10 * (width - padding) / 21 + ",0)")
.call(yAxis)
//add y label
.append("text")
.attr("class", "label")
.attr("x", -10)
.attr("y", -5)
.attr("font-style", "italic")
.attr("font-weight", "bold")
.style("text-anchor", "end")
.text("y");
//add arrowheads to axis ends
//add line on top of x-axis and arrowhead
svg.append("line")
.attr({
"x1": 0,
"y1": 11 * height / 21,
"x2": width - padding * 1.5,
"y2": 11 * height / 21,
"stroke": "black",
"stroke-width": "2px",
"marker-end": "url(#arrow)"
});
//add line on top of y-axis and arrowhead
svg.append("line")
.attr({
"x1": 10 * (width - padding) / 21,
"y1": height,
"x2": 10 * (width - padding) / 21,
"y2": 0.4 * padding,
"stroke": "black",
"stroke-width": "2px",
"marker-end": "url(#arrow)"
});
//Assuming that you have Mike Bostock's standard margins defined and you have defined a linear scale for the y-axis the following code will create horizontal gridlines without using tickSize().
//https://stackoverflow.com/questions/15580300/proper-way-to-draw-gridlines
//create horizontal grid lines
var gridwidth = 19 * width / 20;
var gridheight = 19 * height / 20;
svg.selectAll("line.horizontalGrid").data(yScale2.ticks(20)).enter()
.append("line")
.attr({
"class": "horizontalGrid",
"x1": 0,
"x2": gridwidth,
"y1": function(d) {
return yScale(d);
},
"y2": function(d) {
return yScale(d);
},
"fill": "none",
"shape-rendering": "crispEdges",
"stroke": "black",
"stroke-width": "1px",
"opacity": "0.3"
});
//create vertical gridlines
svg.selectAll("line.verticalGrid").data(xScale2.ticks(20)).enter()
.append("line")
.attr({
"class": "verticalGrid",
"y1": height - gridheight,
"y2": height,
"x1": function(d) {
return xScale(d);
},
"x2": function(d) {
return xScale(d);
},
"fill": "none",
"shape-rendering": "crispEdges",
"stroke": "black",
"stroke-width": "1px",
"opacity": "0.3"
});
//remove last ticks and zero ticks
svg.selectAll(".tick")
.filter(function(d) {
return d === 11;
})
.remove();
svg.selectAll(".tick")
.filter(function(d) {
return d === 0;
})
.remove();
//add a custom origin identifier
svg.append("text")
.attr({
"class": "origintext",
"x": 455 * width / 1000,
"y": 552 * height / 1000,
"text-anchor": "end",
"font-size": "65%"
})
.text("0");
//add labels to points plotted
svg.selectAll("textlabels")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d[2];
})
.attr("x", function(d) {
return xScale(d[0]) + 5;
})
.attr("y", function(d) {
return yScale(d[1]) - 5;
})
.attr("font-weight", "bold")
.attr("font-size", "12px")
.attr("fill", "black");
//}
That is an aliasing effect which will occur because the way the lines will get rendered is influenced by various factors. The main three of them being stroke width, position and rendering mode. For using shape-rendering: crispEdges the SVG spec states:
To achieve crisp edges, the user agent might turn off anti-aliasing for all lines...
Depending on the scaling and the translation of the line it may be calculated to appear between two screen pixels while the scaled stroke width is not broad enough to color any of those adjacent screen pixels. That way the lines seem to randomly disappear and appear again.
Further explanations can be found in "Why is SVG stroke-width : 1 making lines transparent?" or in my answer to "Drew a straight line, but it is crooked d3".
For your code you can change the rendering behaviour by using shape-rendering: geometricPrecision instead of crispEdges when drawing the grid lines. Have a look at the updated JSFiddle for a working example.
In my case I simply solved this issue by setting "showMaxMin" to false.
lineChart.xAxis.showMaxMin(false).tickValues(xAxisTickValues).tickFormat(function (d) {
if (typeof d === 'string') {
d = parseFloat(d);
}
return d3.time.format("%d %b")(new Date(d));
});

d3 multiple svg with 1 row of data each

I'm stuck with a d3.js issue:
I'm trying to draw several SVG canvas with a LINE (it's a more complex custom shape, but as an example the line is sufficient), where the length (called "d.uni") of the line is determined by data. So, each row of the data is mapped to a line within its own SVG.
I looked at the examples that are out there for multiple SVG, like http://bl.ocks.org/mbostock/1305111 and http://bl.ocks.org/mbostock/3888852 and adapted the idea, which is mainly – as far as I get it – to bind data to the SVG drawing function.
Here is my code:
var w = 300;
var h = 250;
var stemL = 100;
var length1 = 80;
var angle1 = -1.2;
d3.csv("data_test.csv", function(data) {
//Create SVG element
var svg = d3.select("body")
.selectAll("svg")
.data(data)
.enter()
.append("svg")
.attr("width", w)
.attr("height", h)
// Cat 1
svg.selectAll("uni")
.data(data)
.enter()
.append("line")
.attr("x1", w/2)
.attr("y1", h - stemL)
.attr("x2", function(d) {return w/2 + length1 * d.uni * Math.sin(angle1);})
.attr("y2", function(d) {return (h - stemL) - length1 * d.uni * Math.cos(angle1); })
.style("stroke", "steelblue")
.style("stroke-width", 5);
});
As a result I get the expected 3 SVG (see data_test.csv below), but d3 draws each line of the data in every SVG on top of each other, instead of line 1 in SVG 1, line 2 in SVG 2, etc.
What am I missing??? :-/
Many many thanks for your help in advance!
Ewa
data_test.csv:
country,uni
Sweden,1.6
Germany,1
Poland,0.7
You have ".data(data)" two times. One in "Create SVG element" ano one in "Cat 1" and that seems to be reason that d3 draws each line of the data in every SVG. ".data(data)" in "Create SVG element" is enough.
When "Cat 1" is changed to this
svg.append("line")
.attr("x1", w/2)
.attr("y1", h - stemL)
.attr("x2", function(d) {return w/2 + length1 * d.uni * Math.sin(angle1);})
.attr("y2", function(d) {return (h - stemL) - length1 * d.uni * Math.cos(angle1); })
.style("stroke", "steelblue")
.style("stroke-width", 5);
I get three different lines.
[edit]
You can also write everything like this:
var svg = d3.select("body")
.selectAll("svg")
.data(data)
.enter()
.append("svg")
.attr("width", w)
.attr("height", h)
.append("line")
.attr("x1", w/2)
.attr("y1", h - stemL)
.attr("x2", function(d) {return w/2 + length1 * d.uni * Math.sin(angle1);})
.attr("y2", function(d) {return (h - stemL) - length1 * d.uni * Math.cos(angle1); })
.style("stroke", "steelblue")
.style("stroke-width", 5);

Gradient colors from d3.scale.category10() with opacity change on a svg circle?

I am trying to apply the colors from the color = d3.scale.category10(); var to the gradient for the circle svg, what am I doing wrong? All I am seeing is the first color of the color = d3.scale.category10();(which is blue) to 0% opacity gradient but that is all. If I take the gradient out then I see the range I want which is from 1-4? Thanks in advance!
var nodes = d3.range(300).map(function() { return {radius: Math.random() * 12 + 4}; }),
root = nodes[0],
color = d3.scale.category10();
root.radius = 0;
root.fixed = true;
var force = d3.layout.force()
.gravity(0.05)
.charge(function(d, i) { return i ? 0 : -4000; })
.nodes(nodes)
.size([width, height]);
force.start();
var svg = d3.select("body").append("svg:svg")
.attr("width", width)
.attr("height", height);
var gradient = svg.append("defs").append("radialGradient")
.attr("id", "gradient")
.attr("cx", "50%")
.attr("cy", "50%");
gradient.append("stop")
.attr("offset", "75%")
.style("stop-color", function(d, i) { return color(i % 4); })
.attr("stop-opacity", "1");
gradient.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d, i) { return color(i % 4); })
.attr("stop-opacity", ".1");
svg.selectAll("circle")
.data(nodes.slice(1))
.enter().append("circle")
.attr("r", function(d) { return d.radius; })
.style("fill", "url(#gradient)");
Your stop elements don't have any data joined with them, so in your function (d, i), i will always be 0. If you just want the two stops, you could do something like this:
gradient.append("stop")
.attr("offset", "75%")
.style("stop-color", color(0))
.attr("stop-opacity", "1");
gradient.append("stop")
.attr("offset", "100%")
.style("stop-color", color(1))
.attr("stop-opacity", ".1");
If instead you're just trying to fade the edges of your circles, a gradient isn't what you want at all. Instead, you'll need to apply a solid color to each circle, then create a single opacity-only gradient inside a mask, and apply that mask to each circle. Something like this:
var defs = svg.append('defs');
var gradient = defs.append('radialGradient')
.attr('id', 'fadient');
gradient.append('stop')
.attr('offset', '75%')
.attr('stop-color', 'white')
.attr('stop-opacity', 1)
gradient.append('stop')
.attr('offset', '100%')
.attr('stop-color', 'white')
.attr('stop-opacity', .1)
var mask = defs.append('mask')
.attr('id', 'mask')
.attr('maskContentUnits', 'objectBoundingBox')
.append('circle')
.attr('fill', 'url(#fadient)')
.attr('cx', .5)
.attr('cy', .5)
.attr('r', .5)
svg.selectAll("circle")
.data(nodes.slice(1))
.enter().append("circle")
.attr('cx', function (d, i) { return 20 * i })
.attr('cy', 50)
.attr("r", function(d) { return d.radius; })
.attr('mask', 'url(#mask)')
.attr("fill", function (d, i) { return color(i); });

Svg clip-path within rectangle does not work

I have a simple graph with x and y axes. I don't want the drawing area I draw within to overlap the axes.
I'm using d3 to create my chart but the clip-path does not work:
http://jsfiddle.net/EqLBJ/
var margin = {top: 19.5, right: 19.5, bottom: 19.5, left: 39.5},
width = 960 - margin.right,
height = 500 - margin.top - margin.bottom;
var xScale = d3.scale.linear().
domain([xMin, xMax]). // your data minimum and maximum
range([0, width]); // the pixels to map to, e.g., the width of the diagram.
var yScale = d3.scale.linear().
domain([yMax, yMin]).
range([0, height]);
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).ticks(10, d3.format(",d")),
yAxis = d3.svg.axis().orient("left").scale(yScale);
var chart = d3.select("#chart").append("svg")
.attr("class", "chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("pointer-events", "all")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(d3.behavior.zoom().scaleExtent([0.2, 5]).on("zoom", redraw));
var rect = chart.append('svg:rect')
.attr('width', width)
.attr('height', height)
.attr('fill', 'white');
var line = d3.svg.line()
.interpolate("basis")
.x(function(d, i) { return xScale(d.time); })
.y(function(d) { return yScale(d.value); });
var clip = chart.append("svg:clipPath")
.attr("id", "clip");
clip.append("svg:rect")
.attr("id", "clip-rect")
.attr("width", width)
.attr("height", height);
// .attr("fill", "white");
var path = chart.append("svg:path")
.attr("clip-path", "url(#clip-rect)")
.data([data])
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", "maroon")
.attr("stroke-width", 2)
.attr("d", line);
// x-axis label
chart.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height - 6)
.text("time");
// y-axis label
chart.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("value");
// x-axis
var xaxis = chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// y-axis
var yaxis = chart.append("g")
.attr("class", "y axis")
.call(yAxis);
function redraw()
{
console.log("here", d3.event.translate, d3.event.scale);
path.transition()
.ease("linear")
.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
You want something like this:
http://jsfiddle.net/dsummersl/EqLBJ/1/
Specifically:
use 'clip' instead of 'clip-rect'
put the content you wish to clip inside a 'g' element, and specify the 'clip-path' attribute and the transforms for the 'g' element.

Resources