Why don't these adjacent SVG paths join cleanly? - svg

There's a line between these paths; why?
(On my machine it looks like this: )
path.myshape {
stroke: gray;
fill: gray;
stroke-opacity:0.5;
fill-opacity:0.5;
}
<svg width="120px" height="120px" viewBox="0 0 120 120">
<path class="myshape" d="M0 0 L100 100 L100 0" />
<path class="myshape" d="M0 0 L100 100 L0 100" />
</svg>
A similar issue happens even without the stroke (it's harder to see but it's still there). I am confused why this is happening; if I have two triangles that are halves of a square, why don't I just see a square?
Is there a way to prevent this?
(On my machine it looks like this: )
path.myshape {
stroke: none;
fill: gray;
fill-opacity:0.5;
}
<svg width="120px" height="120px" viewBox="0 0 120 120">
<path class="myshape" d="M0 0 L100 100 L100 0" />
<path class="myshape" d="M0 0 L100 100 L0 100" />
</svg>
More realistic example (where I'm trying to get rid of the lines between triangles that have nearly the same fill/stroke attributes):
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// add the graph canvas to the body of the webpage
var svg = d3.select("div#plot1").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var axis = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xsc = d3.scaleLinear()
.domain([-2, 2]) // the range of the values to plot
.range([ 0, width ]); // the pixel range of the x-axis
var ysc = d3.scaleLinear()
.domain([-2, 2])
.range([ height, 0 ]);
var closedLine = d3.line()
.x(function(d){ return xsc(d[0]); })
.y(function(d){ return ysc(d[1]); })
.curve(d3.curveLinearClosed);
function attrfunc(f,attr) {
return function(d) {
return f(d[attr]);
};
}
function doit(data)
{
var items = axis.selectAll("path.item")
.data(data);
items.enter()
.append("path")
.attr("class", "item")
.merge(items)
.attr("d", attrfunc(closedLine, "xy"))
.attr("stroke", "gray")
.attr("stroke-width", 1)
.attr("stroke-opacity", function(d) { return 1-d.age;})
.attr("fill", "gray")
.attr("fill-opacity", function(d) {return 1-d.age;});
items.exit().remove();
}
var state = {
t: 0,
theta: 0,
omega: 0.5,
A: 1.0,
N: 60,
history: []
}
d3.timer(function(elapsed)
{
var S = state;
if (S.history.length > S.N)
S.history.shift();
var dt = Math.min(0.1, elapsed*1e-3);
S.t += dt;
S.theta += S.omega * dt;
var sample = {
t: S.t,
x: S.A*(Math.cos(S.theta)+0.1*Math.cos(6*S.theta)),
y: S.A*(Math.sin(S.theta)+0.1*Math.sin(6*S.theta))
}
S.history.push(sample);
// Create triangular regions
var data = [];
for (var k = 0; k < S.history.length-1; ++k)
{
var pt1 = S.history[k];
var pt2 = S.history[k+1];
data.push({age: (S.history.length-1-k)/S.N,
xy:
[[0,0],
[pt1.x,pt1.y],
[pt2.x,pt2.y]]
});
}
doit(data);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.min.js"></script>
<div id="plot1">
</div>

Related

Donut Chart connect all arcs

Drawing svg donut chart using circle. I need to connect all arcs, instead gap between them. is it possible?
I set stroke-dasharray="${item.percent} 100" but it doesn't work. also I set pathLength="${100+0*arr.length}" but it doesn't work either. See the code below
var DonutSlice = [{
id: 1,
percent: 60,
color: 'DarkSeaGreen',
label: 'Slice 1'
},
{
id: 2,
percent: 30,
color: 'DarkOrchid',
label: 'Slice 2'
},
{
id: 3,
percent: 10,
color: 'Tomato',
label: 'Slice 3'
}
];
var circlecontainer = document.getElementById('circlecontainer');
var output = document.getElementById('output');
circlecontainer.innerHTML = DonutSlice.map((item, i, arr) => {
let offset = 1 * i + arr.filter((item, j) => j < i)
.reduce((total, item) => total + item.percent, 0);
return `<circle data-id="${item.id}" stroke="${item.color}"
cx="80" cy="80" r="79" class="slice"
pathLength="${100+0*arr.length}"
stroke-dasharray="${item.percent} 100"
stroke-dashoffset="-${offset}" />`;
}).join('');
circlecontainer.addEventListener('mouseover', e => {
let slice = e.target.closest('.slice');
if (slice) {
item = DonutSlice.find(item => item.id == parseInt(slice.dataset.id));
output.innerHTML = `${item.label} was clicked`;
}
});
.slice {
/* stroke-linecap: round; */
stroke-width: 15;
fill: none;
cursor: pointer;
}
p#output {
text-align: center;
}
<div style="width: 200px">
<svg class="svg2" width="174" height="174" preserveAspectRatio="xMinYMin meet"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 174 174" version="1.1">
<circle fill="#000000" cx="87" cy="87" r="87" />
<g id="circlecontainer" transform="rotate(0 0 0)"></g>
</svg>
<p id="output"></p>
</div>
Instead of using stroke-dashoffset I prefer to use transform rotate. So the offset value needs to be a number of degrees. I added / 100 * 360 to the calculation.
If you use the circlecontainer group to position the chart you can skip the cx and cy attributes on the circle elements.
Update
There was a gap between the slices. I changed the pathLength to 360 so that is matches the rotation. Then I add 1 degree to the length of the slice and rotate the slice 1 degree. Now, the end of each slice will overlap the slice next to it.
var DonutSlice = [{
id: 1,
percent: 60,
color: 'DarkSeaGreen',
label: 'Slice 1'
},
{
id: 2,
percent: 30,
color: 'DarkOrchid',
label: 'Slice 2'
},
{
id: 3,
percent: 10,
color: 'Tomato',
label: 'Slice 3'
}
];
var circlecontainer = document.getElementById('circlecontainer');
var output = document.getElementById('output');
circlecontainer.innerHTML = DonutSlice.map((item, i, arr) => {
let offset = arr.filter((item, j) => j < i)
.reduce((total, item) => total + item.percent, 0) / 100 * 360;
return `<circle data-id="${item.id}" stroke="${item.color}"
r="72" class="slice"
pathLength="360"
stroke-dasharray="${item.percent / 100 * 360 + 1} 360"
transform="rotate(${offset - 1})" />`;
}).join('');
circlecontainer.addEventListener('mouseover', e => {
let slice = e.target.closest('.slice');
if (slice) {
item = DonutSlice.find(item => item.id == parseInt(slice.dataset.id));
output.innerHTML = `${item.label} was clicked`;
}
});
.slice {
/* stroke-linecap: round; */
stroke-width: 16;
fill: none;
cursor: pointer;
}
p#output {
text-align: center;
}
<div style="width: 200px">
<svg class="svg2" preserveAspectRatio="xMinYMin meet"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 164 164">
<circle fill="#000000" cx="84" cy="84" r="80" />
<g id="circlecontainer" transform="translate(80 80) rotate(-90)"></g>
</svg>
<p id="output"></p>
</div>
Your offset calculation adds a gap.
Change it to (delete the additional 1 * i +):
let offset = arr.filter((item, j) => j < i)
.reduce((total, item) => total + item.percent, 0);
var DonutSlice = [{
id: 1,
percent: 60,
color: 'DarkSeaGreen',
label: 'Slice 1'
},
{
id: 2,
percent: 30,
color: 'DarkOrchid',
label: 'Slice 2'
},
{
id: 3,
percent: 10,
color: 'Tomato',
label: 'Slice 3'
}
];
var circlecontainer = document.getElementById('circlecontainer');
var output = document.getElementById('output');
circlecontainer.innerHTML = DonutSlice.map((item, i, arr) => {
let offset = arr.filter((item, j) => j < i)
.reduce((total, item) => total + item.percent, 0);
return `<circle data-id="${item.id}" stroke="${item.color}"
cx="80" cy="80" r="79" class="slice"
pathLength="${100+0*arr.length}"
stroke-dasharray="${item.percent} 100"
stroke-dashoffset="-${offset}" />`;
}).join('');
circlecontainer.addEventListener('mouseover', e => {
let slice = e.target.closest('.slice');
if (slice) {
item = DonutSlice.find(item => item.id == parseInt(slice.dataset.id));
output.innerHTML = `${item.label} was clicked`;
}
});
.slice {
/* stroke-linecap: round; */
stroke-width: 15;
fill: none;
cursor: pointer;
}
p#output {
text-align: center;
}
<div style="width: 200px">
<svg class="svg2" width="174" height="174" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 174 174" version="1.1">
<circle fill="#000000" cx="87" cy="87" r="87" />
<g id="circlecontainer" transform="rotate(0 0 0)"></g>
</svg>
<p id="output"></p>
</div>

Fix linear gradient of SVG circle

I have the following circle progress bar. Everything is fine except for the gradient. The circle is actually 2 arcs. And when the script draws the first arc the gradient is red->blue. So the start of the circle is red. And the tail is blue. But when the second arc is being drawn the start of the gradient switches to blue and I don't know how to fix it. I don't want it to switch colors. I want the gradient to always be the same
function update(percentage) {
var width = 160,
height = 160,
cx = width / 2,
cy = height / 2,
start_angle = 0,
barsize = 10;
var r = Math.min(cx, cy) - barsize / 2;
if (percentage === 100) {
percentage -= 0.0001;
}
var end_angle = start_angle + percentage * Math.PI * 2 / 100;
var x1 = cx + r * Math.sin(start_angle),
y1 = cy - r * Math.cos(start_angle),
x2 = cx + r * Math.sin(end_angle),
y2 = cy - r * Math.cos(end_angle);
// This is a flag for angles larger than than a half circle
// It is required by the SVG arc drawing component
var big = 0;
if (end_angle - start_angle > Math.PI) big = 1;
// This string holds the path details
var d = "M" + x1 + "," + y1 + // Start at (x1,y1)
" A" + r + "," + r + // Draw an arc of radius r
" 0 " + big + " 1 " + // Arc details...
x2 + "," + y2;
document.getElementById('path').setAttribute('d', d);
}
function animate(start, finish) {
setTimeout(function() {
update(start);
console.log(document.getElementsByClassName('progress__content'))
let element = document.getElementsByClassName('progress__content')[0];
element.textContent = start + '%';
start += 1;
if (start <= finish) {
animate(start, finish);
} else {
return;
}
}, 10);
}
function go() {
animate(0, 100);
}
.progress {
position: absolute;
top: 50%;
left: 50%;
margin-top: -100px;
margin-left: 100px;
width: 200px;
height: 200px;
}
.progress__content {
position: absolute;
left: 50%;
top: 50%;
margin-left: -50px;
margin-top: -23px;
font-family: Helvetica;
font-size: 40px;
width: 103px;
height: 47px;
text-align: center;
}
body {
background: #f1f1f1;
}
<button onclick="go()">Click me</button>
<div class="progress clip-svg">
<div class="progress__content">0%</div>
<svg width="160" height="160">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop stop-color="#EE3028" offset="0" />
<stop stop-color="#067BC2" offset="1" />
</linearGradient>
</defs>
<ellipse rx="75" ry="75" cx="80" cy="80" stroke="#f2f2f2" fill="none" stroke-width="10"></ellipse>
<g>
<path id="path" stroke-width="10" stroke="url(#gradient)" fill="none" d="">
</path>
</g>
</svg>
</div>
The gradient changes because you define the gradient on the entire path, and as the path object grows down and left, the gradient stop positions are continuously redefined as well. The solution is to change your gradientUnits to userSpaceOnUse - so the gradient is defined vs. the drawing surface/viewBox vs. relative to the object. Possible implementation below (I'm not entirely sure what color scheme you're aiming for - but tweak the stop locations & colors until you have what you want).
function update(percentage) {
var width = 160,
height = 160,
cx = width / 2,
cy = height / 2,
start_angle = 0,
barsize = 10;
var r = Math.min(cx, cy) - barsize / 2;
if (percentage === 100) {
percentage -= 0.0001;
}
var end_angle = start_angle + percentage * Math.PI * 2 / 100;
var x1 = cx + r * Math.sin(start_angle),
y1 = cy - r * Math.cos(start_angle),
x2 = cx + r * Math.sin(end_angle),
y2 = cy - r * Math.cos(end_angle);
// This is a flag for angles larger than than a half circle
// It is required by the SVG arc drawing component
var big = 0;
if (end_angle - start_angle > Math.PI) big = 1;
// This string holds the path details
var d = "M" + x1 + "," + y1 + // Start at (x1,y1)
" A" + r + "," + r + // Draw an arc of radius r
" 0 " + big + " 1 " + // Arc details...
x2 + "," + y2;
document.getElementById('path').setAttribute('d', d);
}
function animate(start, finish) {
setTimeout(function() {
update(start);
console.log(document.getElementsByClassName('progress__content'))
let element = document.getElementsByClassName('progress__content')[0];
element.textContent = start + '%';
start += 1;
if (start <= finish) {
animate(start, finish);
} else {
return;
}
}, 10);
}
function go() {
animate(0, 100);
}
.progress {
position: absolute;
top: 50%;
left: 50%;
margin-top: -100px;
margin-left: 100px;
width: 200px;
height: 200px;
}
.progress__content {
position: absolute;
left: 50%;
top: 50%;
margin-left: -50px;
margin-top: -23px;
font-family: Helvetica;
font-size: 40px;
width: 103px;
height: 47px;
text-align: center;
}
body {
background: #f1f1f1;
}
<button onclick="go()">Click me</button>
<div class="progress clip-svg">
<div class="progress__content">0%</div>
<svg width="160" height="160">
<defs>
<linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="160" y2="0">
<stop stop-color="#EE3028" offset="0" />
<stop stop-color="#067BC2" offset="160" />
</linearGradient>
</defs>
<ellipse rx="75" ry="75" cx="80" cy="80" stroke="#f2f2f2" fill="none" stroke-width="10"></ellipse>
<g>
<path id="path" stroke-width="10" stroke="url(#gradient)" fill="none" d="">
</path>
</g>
</svg>
</div>

YES/NO - is there a way to improve mouse dragging with pure SVG tools?

So I was spending some time playing around with pure (no external libraries) SVG elements dragging.
In general all works, but there is this nasty issue for fast moving mouse:
- when user mousedowns a draggable SVG element close to its edge
- then drags (mousemove) such draggable too fast
- the mouse "loses" the draggable
Here the issue is described in more details:
http://www.svgopen.org/2005/papers/AdvancedMouseEventModelForSVG-1/index.html#S3.2
Also here the author tried to fix UX by leveraging mouseout event:
http://nuclearprojects.com/blog/svg-click-and-drag-object-with-mouse-code/
I copied the above code snippet here: http://codepen.io/cmer41k/pen/zNGwpa
The question I have is:
Is there no other way (provided by pure SVG) to prevent such "loss" of SVG element while mouse moves too fast?
My attempt to solve this was:
- detect (somehow) that mouseout event happened without finishing the dragging.
- and if so (we sort of detected "disconnect") - reconnect the SVG element with current mouse position.
Is there a reason why this wouldn't work?
Code:
var click=false; // flag to indicate when shape has been clicked
var clickX, clickY; // stores cursor location upon first click
var moveX=0, moveY=0; // keeps track of overall transformation
var lastMoveX=0, lastMoveY=0; // stores previous transformation (move)
function mouseDown(evt){
evt.preventDefault(); // Needed for Firefox to allow dragging correctly
click=true;
clickX = evt.clientX;
clickY = evt.clientY;
evt.target.setAttribute("fill","green");
}
function move(evt){
evt.preventDefault();
if(click){
moveX = lastMoveX + ( evt.clientX – clickX );
moveY = lastMoveY + ( evt.clientY – clickY );
evt.target.setAttribute("transform", "translate(" + moveX + "," + moveY + ")");
}
}
function endMove(evt){
click=false;
lastMoveX = moveX;
lastMoveY = moveY;
evt.target.setAttribute("fill","gray");
}
The most important part of your code is missing, namely how or more specifically on which element you register the events.
What you basically do to prevent this problem is to register the mousemove and mouseup events on the outermost svg element, and not on the element you want to drag.
svg.addEventListener("mousemove", move)
svg.addEventListener("mouseup", endMove)
When starting the drag, register the events on the svg element, and when done unregister them.
svg.removeEventListener("mousemove", move)
svg.removeListener("mouseup", endMove)
you have to store the element you are currently dragging, so it is available in the other event handlers.
what i additionally do is to set pointer-events to "none" on the dragged
element so that you can react to mouse events underneath the dragged element (f.e. finding the drop target...)
evt.target.setAttribute("pointer-events", "none")
but don't forget to set it back to something sensible when dragging is done
evt.target.setAttribute("pointer-events", "all")
var click = false; // flag to indicate when shape has been clicked
var clickX, clickY; // stores cursor location upon first click
var moveX = 0,
moveY = 0; // keeps track of overall transformation
var lastMoveX = 0,
lastMoveY = 0; // stores previous transformation (move)
var currentTarget = null
function mouseDown(evt) {
evt.preventDefault(); // Needed for Firefox to allow dragging correctly
click = true;
clickX = evt.clientX;
clickY = evt.clientY;
evt.target.setAttribute("fill", "green");
// register move events on outermost SVG Element
currentTarget = evt.target
svg.addEventListener("mousemove", move)
svg.addEventListener("mouseup", endMove)
evt.target.setAttribute("pointer-events", "none")
}
function move(evt) {
evt.preventDefault();
if (click) {
moveX = lastMoveX + (evt.clientX - clickX);
moveY = lastMoveY + (evt.clientY - clickY);
currentTarget.setAttribute("transform", "translate(" + moveX + "," + moveY + ")");
}
}
function endMove(evt) {
click = false;
lastMoveX = moveX;
lastMoveY = moveY;
currentTarget.setAttribute("fill", "gray");
svg.removeEventListener("mousemove", move)
svg.removeEventListener("mouseup", endMove)
currentTarget.setAttribute("pointer-events", "all")
}
<svg id="svg" width="800" height="600" style="border: 1px solid black; background: #E0FFFF;">
<rect x="0" y="0" width="800" height="600" fill="none" pointer-events="all" />
<circle id="mycirc" cx="60" cy="60" r="22" onmousedown="mouseDown(evt)" />
</svg>
more advanced
there are still two things not so well with this code.
it does not work for viewBoxed SVGs nor for elements inside
transformed parents.
all the globals are bad coding practice.
here is how to fix those:
Nr. 1 is solved by converting mouse coordinates into local coordinates using the inverse of getScreenCTM (CTM = Current Transformation Matrix).
function globalToLocalCoords(x, y) {
var p = elem.ownerSVGElement.createSVGPoint()
var m = elem.parentNode.getScreenCTM()
p.x = x
p.y = y
return p.matrixTransform(m.inverse())
}
For nr. 2 see this implementation:
var dre = document.querySelectorAll(".draggable")
for (var i = 0; i < dre.length; i++) {
var o = new Draggable(dre[i])
}
function Draggable(elem) {
this.target = elem
this.clickPoint = this.target.ownerSVGElement.createSVGPoint()
this.lastMove = this.target.ownerSVGElement.createSVGPoint()
this.currentMove = this.target.ownerSVGElement.createSVGPoint()
this.target.addEventListener("mousedown", this)
this.handleEvent = function(evt) {
evt.preventDefault()
this.clickPoint = globalToLocalCoords(evt.clientX, evt.clientY)
this.target.classList.add("dragged")
this.target.setAttribute("pointer-events", "none")
this.target.ownerSVGElement.addEventListener("mousemove", this.move)
this.target.ownerSVGElement.addEventListener("mouseup", this.endMove)
}
this.move = function(evt) {
var p = globalToLocalCoords(evt.clientX, evt.clientY)
this.currentMove.x = this.lastMove.x + (p.x - this.clickPoint.x)
this.currentMove.y = this.lastMove.y + (p.y - this.clickPoint.y)
this.target.setAttribute("transform", "translate(" + this.currentMove.x + "," + this.currentMove.y + ")")
}.bind(this)
this.endMove = function(evt) {
this.lastMove.x = this.currentMove.x
this.lastMove.y = this.currentMove.y
this.target.classList.remove("dragged")
this.target.setAttribute("pointer-events", "all")
this.target.ownerSVGElement.removeEventListener("mousemove", this.move)
this.target.ownerSVGElement.removeEventListener("mouseup", this.endMove)
}.bind(this)
function globalToLocalCoords(x, y) {
var p = elem.ownerSVGElement.createSVGPoint()
var m = elem.parentNode.getScreenCTM()
p.x = x
p.y = y
return p.matrixTransform(m.inverse())
}
}
.dragged {
fill-opacity: 0.5;
stroke-width: 0.5px;
stroke: black;
stroke-dasharray: 1 1;
}
.draggable{cursor:move}
<svg id="svg" viewBox="0 0 800 600" style="border: 1px solid black; background: #E0FFFF;">
<rect x="0" y="0" width="800" height="600" fill="none" pointer-events="all" />
<circle class="draggable" id="mycirc" cx="60" cy="60" r="22" fill="blue" />
<g transform="rotate(45,175,75)">
<rect class="draggable" id="mycirc" x="160" y="60" width="30" height="30" fill="green" />
</g>
<g transform="translate(200 200) scale(2 2)">
<g class="draggable">
<circle cx="0" cy="0" r="30" fill="yellow"/>
<text text-anchor="middle" x="0" y="0" fill="red">I'm draggable</text>
</g>
</g>
</svg>
<div id="out"></div>

Live graphing of data with d3 using real data

I have seen lots of great demos for live graphing of data using D3.
http://bl.ocks.org/simenbrekken/6634070 is one I like. However, all of the examples I have seen use random generated values. I want to graph live data, and display the most recent values as an updating numeric display. I use a python script which writes data from sensor readings to csv files. The csv is 3 values on each line: unixtime,sensor1_value,sensor2_value. Every 5 seconds there is a new line of data added to a ring buffer file which has 720 lines of data. When the web page is displayed I want to read the 720 lines in the buffer file then update the graph with each new value which is written onto the end of the file. I could also create a file with just the new line of csv every 5 seconds so that the update was performed by always reading a file with just 1 line of csv rather than manipulating the entire buffer.
Does anyone know of an example, or the right code to achieve this?
The code for the above cited example is:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.graph .axis {
stroke-width: 1;
}
.graph .axis .tick line {
stroke: black;
}
.graph .axis .tick text {
fill: black;
font-size: 0.7em;
}
.graph .axis .domain {
fill: none;
stroke: black;
}
.graph .group {
fill: none;
stroke: black;
stroke-width: 1.5;
}
</style>
</head>
<body>
<div class="graph"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var limit = 60 * 1,
duration = 750,
now = new Date(Date.now() - duration)
var width = 500,
height = 200
var groups = {
current: {
value: 0,
color: 'orange',
data: d3.range(limit).map(function() {
return 0
})
},
target: {
value: 0,
color: 'green',
data: d3.range(limit).map(function() {
return 0
})
},
output: {
value: 0,
color: 'grey',
data: d3.range(limit).map(function() {
return 0
})
}
}
var x = d3.time.scale()
.domain([now - (limit - 2), now - duration])
.range([0, width])
var y = d3.scale.linear()
.domain([0, 100])
.range([height, 0])
var line = d3.svg.line()
.interpolate('basis')
.x(function(d, i) {
return x(now - (limit - 1 - i) * duration)
})
.y(function(d) {
return y(d)
})
var svg = d3.select('.graph').append('svg')
.attr('class', 'chart')
.attr('width', width)
.attr('height', height + 50)
var axis = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(x.axis = d3.svg.axis().scale(x).orient('bottom'))
var paths = svg.append('g')
for (var name in groups) {
var group = groups[name]
group.path = paths.append('path')
.data([group.data])
.attr('class', name + ' group')
.style('stroke', group.color)
}
function tick() {
now = new Date()
// Add new values
for (var name in groups) {
var group = groups[name]
//group.data.push(group.value) // Real values arrive at irregular intervals
group.data.push(20 + Math.random() * 100)
group.path.attr('d', line)
}
// Shift domain
x.domain([now - (limit - 2) * duration, now - duration])
// Slide x-axis left
axis.transition()
.duration(duration)
.ease('linear')
.call(x.axis)
// Slide paths left
paths.attr('transform', null)
.transition()
.duration(duration)
.ease('linear')
.attr('transform', 'translate(' + x(now - (limit - 1) * duration) + ')')
.each('end', tick)
// Remove oldest data point from each group
for (var name in groups) {
var group = groups[name]
group.data.shift()
}
}
tick()
</script>
</body>
</html>
Whereas the code I use for creating a static graph of one of the values (o2) from my csv versus a line:
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 900 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Set the ranges
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(10);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(10);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.o2); });
// Adds the svg canvas
var svg = d3.select("body")
.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 + ")");
// Get the data
d3.csv("./data/buffer.txt", function(error, data) {
data.forEach(function(d) {
d.time = +d.time;
d.o2 = +d.o2;
console.log(d.time);
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain([0, d3.max(data, function(d) { return d.o2; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
</script>
</body>

SVG Path : How to marker-end arrow head to circle border?

I making circle (about Radian to 20). and have a few text on inside.
and I want to draw arrow-path to it.
So I use "marker-end" . but circle override arrow.
How to (Can I ) move marker-end location to circle border?
<svg width="600px" height="400px" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs><!-- ready for endpoint arrow -->
<marker id="endpoint" viewBox="-50 0 50 50" refX="0" refY="25" markerUnits="strokeWidth" markerWidth="20" markerHeight="20" orient="auto">
<path d="M -50 0 L 0 25 L -50 50 z"></path>
</marker>
</defs>
<g>
<text x="10" y="50">Trouble : Arrow Hide(override) by Circle</text>
<path d="M 0,100 C 50,100 80,131 130,131 C 130,131 190,130 200,100"
stroke="black" fill="none" marker-end="url(#endpoint)"></path>
<path d="M 440,100 C 350,100 350,131 300,131 C 260,131 230,60 200,100"
stroke="black" fill="none" marker-end="url(#endpoint)"></path>
<circle cx="200" cy="100" r="20" stroke-width="2px" stroke="#aaa" fill="#fff" opacity="0.95"></circle>
<text x="165" y="105">Some Text</text>
</g>
<g>
<text x="10" y="240">What I want?</text>
<text x="10" y="260">Auto detect arrow head to border</text>
<path d="M 0,300 C 50,300 80,331 130,331 C 130,331 180,330 200,300" stroke="black" fill="none"></path>
<path d="M 185 315 L 174 312 L 185 326 z"></path>
<path d="M 440,300 C 350,300 350,331 300,331 C 260,331 230,260 200,300" stroke="black" fill="none"></path>
<path d="M 216 289 L 223 295 L 220 283 z"></path>
<circle cx="200" cy="300" r="20" stroke-width="2px" stroke="#aaa" fill="#fff" opacity="0.75"></circle>
<text x="165" y="305">Some Text</text>
</g>
<!-- /////////////////////////////////////////////// -->
</svg>
There is no way to have markers be drawn back along the line away from the end.
You wither need to draw the arrow heads yourself. Or have the lines end at the circle border.
Thankyou everyone.I try another way.
Example.
var draw = function(type, opts) {
var svg = document.createElementNS("http://www.w3.org/2000/svg", type);
for (var k in opts) {
svg.setAttribute(k, opts[k]);
}
return svg;
};
var getCircleDestination = function(from, to) {
var tx = parseFloat(to.getAttribute("cx"));
var ty = parseFloat(to.getAttribute("cy"));
var fx = from.center.x;
var fy = from.center.y;
var w = fx - tx;
var h = fy - ty;
var z = Math.sqrt(w * w + h * h);
var r = parseFloat(to.getAttribute("r"));
var dz = z / r;
var dx = tx + w / dz;
var dy = ty + h / dz;
/* dx2,dy2 ...dz * 1.2 is no good idea. */
var dx2 = tx + w / (dz * 1.2);
var dy2 = ty + h / (dz * 1.2);
var circle = draw("circle", {
"cx": dx,
"cy": dy,
"r": 3,
"fill": "red"
});
return {
"s": {
"x": dx2,
"y": dy2
},
"svg": circle
};
};
var canvas = draw("svg", {
"width": "400",
"height": "150",
"style": "background-color:#f0f0f0;border:1px solid black;"
});
var def = draw("defs", {});
var marker = draw("marker", {
"id": "endpoint",
"viewBox": "-50 0 50 50",
"refX": 0,
"refY": 25,
"markerUnits": "strokeWidth",
"markerWidth": 10,
"markerHeight": 10,
"orient": "auto"
});
var marker_path = draw("path", {
"d": "M -50 0 L 0 25 L -50 50 z"
});
var btn = document.createElement("button");
btn.style.display = "block";
btn.innerHTML = "Add Line";
canvas.appendChild(def);
canvas.appendChild(marker);
marker.appendChild(marker_path);
document.getElementById("canvas").appendChild(canvas);
document.getElementById("canvas").appendChild(btn);
var circle = draw("circle", {
"cx": 200,
"cy": 75,
"r": 10,
"fill": "white",
"stroke": "black",
"stroke-width": 1,
"opacity": 0.5
});
canvas.appendChild(circle);
var paths = new Array();
btn.addEventListener("click", function() {
var x = Math.random() * canvas.clientWidth;
var y = Math.random() * canvas.clientHeight;
var from = {
"center": {
"x": x,
"y": y
}
};
var svg = getCircleDestination(from, circle);
var c = svg.svg;
var stx = svg.s.x;
var sty = svg.s.y;
var ttx = svg.s.x + (Math.random() * 10 + 15) * (svg.s.x < x ? 1 : -1);
var tty = svg.s.y + (Math.random() * 10 + 15) * (svg.s.y < y ? 1 : -1);
var p = draw("path", {
"d": "M" + x + "," + y + " Q " + ttx + "," + tty + " " + stx + "," + sty + " " + c.getAttribute("cx") + "," + c.getAttribute("cy"),
"stroke": "black",
"fill": "none",
"marker-end": "url(#endpoint)"
});
canvas.appendChild(c);
canvas.appendChild(p);
paths.push({
"c": c,
"p": p
});
if (paths.length > 5) {
var t = paths.shift();
t.c.remove();
t.p.remove();
}
});
<div id="canvas"></div>

Resources