Add arrow to both sides of line in D3 - d3.js

I am new for D3. How can I add an arrow to the beginning and to the end of this line?
var x = 100;
var y = 100;
var canvas = d3.select("#canvasContainer")
.append("svg")
.attr("width", 600)
.attr("height", 500);
var line1 = canvas.append("line")
.attr("x1", x)
.attr("y1", y)
.attr("x2", x)
.attr("y2", y + 50)
.attr("stroke", "red")
.attr("stroke-width", "3");

All you need is:
svg
.append('svg:defs')
.append('svg:marker')
.attr('id', 'triangle')
.attr('refX', 6)
.attr('refY', 6)
.attr('markerWidth', 30)
.attr('markerHeight', 30)
.attr('markerUnits', 'userSpaceOnUse')
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 0 12 6 0 12 3 6')
.style('fill', 'black');
Next:
const pathNodes = svg
.append('path')
.datum(path)
.attr('d', line)
.attr('fill', 'none')
.attr('stroke-width', '3')
.attr('stroke', (d) => d.color)
.attr('marker-end', 'url(#triangle)');
Or with animation in my case:
const arrow = svg
.append('svg:path')
.attr('d', 'M 0 0 12 6 0 12 3 6')
.attr('fill', 'black');
arrow
.transition()
.duration(2000)
.delay(1500)
.ease(d3.easeLinear)
.attrTween('transform', this.arrowAnimation(pathNodes.node()));
arrowAnimation(path) => {
const l = path.getTotalLength();
let prevX = 0;
let prevY = 0;
return (d, i, a) => {
return (t) => {
const p = path.getPointAtLength(t * l);
const deltaY = prevY - p.y;
const deltaX = prevX - p.x;
prevY = p.y;
prevX = p.x;
let rotTran;
if (deltaY === 0) {
if (deltaX > 0) {
rotTran = 'rotate(-180)';
} else {
rotTran = 'rotate(180)';
}
} else if (deltaX === 0) {
if (deltaY > 0) {
rotTran = 'rotate(-90)';
} else {
rotTran = 'rotate(90)';
}
} else {
rotTran = 'rotate(0)';
}
return 'translate(' + p.x + ',' + p.y + ') ' + rotTran;
};
};
}

Related

drag grouped elements move not continuous

I try move a grouped element but after click and drag, the elements jumped away.
demo()
function demo() {
var tooltip = d3.select('body')
.append('div')
.attr('id','tooltip')
.style('position','absolute')
.style('opacity',0)
.style('background','lightsteelblue')
var svg = d3.select("body")
.append("svg")
.attr("width", 300)
.attr("height", 200)
.style("background", "#ececec")
add_grid(svg);
var data = [
{
text: "O",
x: 50,
y: 50
},
];
var g = svg.append('g')
var fontsize = 20;
var box = g.selectAll(".box")
.data(data)
.join('g')
.attr('class','box')
.attr("pointer-events", "all")
box.call(
d3.drag()
.on("start",function(event,d) {
d3.select(this).raise().classed("active", true);
d3.select('#tooltip')
.transition().duration(100)
.style('opacity', 1)
})
.on("drag",function(event,d) {
d.x = event.x
d.y = event.y
d3.select(this).attr('transform',`translate(${d.x},${d.y})`)
var desc = "(" + d.x.toFixed(1) +"," + d.y.toFixed(1) + ")"
d3.select('#tooltip')
.style('left', (event.x+2) + 'px')
.style('top', (event.y-2) + 'px')
.text(desc)
})
.on("end", function dragEnd(event,d) {
d3.select(this).classed("active", false);
d3.select('#tooltip').style('opacity', 0)}
))
.on('mouseover', function(event,d) {
})
.on('mouseout', function(event,d) {
})
.on('mousemove', function(event,d) {
})
.on("mousedown", function(){
})
.on("mouseup", function(){
});
var txt = box.append("text")
.attr("text-anchor", "middle")
.attr("dominant-baseline",'text-before-edge')//'central')//text-bottom
.attr("font-size", fontsize)
.attr("x", (d) => d.x)
.attr("y", (d) => d.y)
var tspan = txt.selectAll(".tspan")
.data((d) => d.text.split("\n"))
.join("tspan")
.attr("class", "tspan")
.attr("x", function (d) {
let x = +d3.select(this.parentNode).attr("x");
return x;
})
.attr("y", function (d,i) {
let y = +d3.select(this.parentNode).attr("y");
return y + i*fontsize * .9;
})
.text((d) => d);
box.each((d,i,n) => {
var bbox = d3.select(n[i]).node().getBBox()
var padding = 2
bbox.x -= padding
bbox.y -= padding
bbox.width += 2*padding
bbox.height += 2*padding
d.bbox = bbox
})
.attr('transform',d => `translate(${0},${-d.bbox.height/2})`)
.append('rect')
.attr('x',d => d.bbox.x)
.attr('y',d => d.bbox.y)
.attr('width', d => d.bbox.width)
.attr('height',d => d.bbox.height)
.attr('stroke','red')
.attr('fill','none')
add_dots(svg,data)
function add_dots(svg,data) {
svg.selectAll('.dots')
.data(data)
.join('circle')
.attr('class','dots')
.attr('r',2)
.attr('cx',d => d.x)
.attr('cy',d => d.y)
.attr('fill','red')
}
function add_grid(svg) {
var w = +svg.attr("width");
var step = 10;
var mygrid = function (d) {
return `M 0,${d} l ${w},0 M ${d},0 l 0,${w}`;
};
var grid = [];
for (var i = 0; i < w; i += step) {
grid.push(i);
}
svg
.append("g")
.selectAll(null)
.data(grid)
.enter()
.append("path")
.attr("d", (d) => mygrid(d))
.attr("fill", "none")
.attr("stroke", "green")
.attr("stroke-width", 0.5);
}
}
<script src="https://unpkg.com/d3#7.0.4/dist/d3.min.js"></script>

drag group elements with dx/dy

I try to use dx/dy to add offset to group elems after drag, current code not group elements not following mouse move.
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 300);
var nodes = [
{
id:"A",
x:50,
y:50,
text:"hello"
}
]
add_box(svg,50,50,nodes)
var tooltip = d3.select('body')
.append('div')
.attr('id','tooltip')
.style('position','absolute')
.style('opacity',0)
.style('background','lightsteelblue')
function add_box(svg,x,y,nodes) {
var g = svg.selectAll('.node')
.data(nodes)
.join('g')
.attr('class','node')
g.call(d3.drag()
.on('start', dragStart)
.on('drag', dragging)
.on('end', dragEnd)
)
var txt = g.append('text')
.text(d => d.text)
.attr('x',d => d.x)
.attr('y',d => d.y)
var bbox = txt.node().getBBox()//getBoundingClientRect()
var m = 2
bbox.x -= m
bbox.y -= m
bbox.width += 2*m
bbox.height += 2*m
var rect = g.append('rect')
.attr('x',bbox.x)
.attr('y',bbox.y)
.attr('width',bbox.width)
.attr('height',bbox.height)
.attr('fill','none')
.attr('stroke','black')
}
function dragStart(event,d){
d3.select(this).raise()
.style("stroke", "")
d3.select('#tooltip')
.transition().duration(100)
.style('opacity', 1)
}
function dragging(event,d){
var x = event.x;
var y = event.y;
var dx = event.dx
var dy = event.dy
d3.select(this).select("text")
.attr("dx", dx)
.attr("dy", dy);
d3.select(this).select("rect")
.attr("dx", dx)
.attr("dy", dy);
var desc = "(" + x.toFixed(1) +"," + y.toFixed(1) + ")"
d3.select('#tooltip')
.style('left', (x+2) + 'px')
.style('top', (y-2) + 'px')
.text(desc)
}
function dragEnd(event,d){
d3.select(this)
.style("stroke", "black")
d3.select('#tooltip').style('opacity', 0)
}
<script src="https://d3js.org/d3.v7.min.js"></script>

d3.js - add rectangle background to filtered elements

I try to add a background rectangle for all filtered elements. the idea is add all filtered element into g element then use BBox of g element to draw the rectangle.
Two issues need help:
The top and bottom looks like cutted.
rectangle position has shift back half the width.(before add the rectangle, it's good)
tree_demo()
function tree_demo() {
tree(
{name:'AAAA',children:[
{name: "BBBB", children: [{name:'CCCC'}]},
{name: "DDDD", children: [{name: "EEEE"}]},
{name: "FFFF", children: [{name: "GGGG"}]},
{name: "HHHH", children: [{name: "IIII"}]},
{name: "JJJJ", children: [{name: "KKKK"}]},
{name: "LLLL", children: [{name: "MMMM"}]},
]}
);
function add_defs(svg) {
var col = 'black'
var path = ['M',6,4,'L',1,2,1,6,'z'].join(' ')
svg
.append('defs')
.append('marker')
.attr('id', 'arr1')
.attr('viewBox', [0, 0, 8, 8])
.attr('refX', 7)
.attr('refY', 4)
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('orient', 'auto-start-reverse')
.append('path')
.attr('d',path)
.attr('stroke', col)
.attr('fill','black')
path = ['M',10,0,10,6,2,2,2,10,10,6,10,12].join(' ')
svg
.append('defs')
.append('marker')
.attr('id', 'arr2')
.attr('viewBox', [0, 0, 12, 12])
.attr('refX', 10)
.attr('refY', 6)
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto-start-reverse')
.append('path')
.attr('d',path)
.attr('stroke', col)
.attr('fill','black')
path = ['M',10,6,2,2,3,6,2,10,'z'].join(' ')
svg
.append('defs')
.append('marker')
.attr('id', 'arr3')
.attr('viewBox', [0, 0, 12, 12])
.attr('refX', 10)
.attr('refY', 6)
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto-start-reverse')
.append('path')
.attr('d',path)
.attr('stroke', col)
.attr('fill','black')
path = ['M',10,0,10,12,'M',9,6,2,2,4,6,2,10,'z'].join(' ')
svg
.append('defs')
.append('marker')
.attr('id', 'arr4')
.attr('viewBox', [0, 0, 12, 12])
.attr('refX', 10)
.attr('refY', 6)
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto-start-reverse')
.append('path')
.attr('d',path)
.attr('stroke', col)
.attr('fill','black')
}
function tree(root) {
var width = 400
var height = 160
var step = 50
var margin = {top: 0, right: 40, bottom: 0, left: 60}
var treemap = d3.tree().size([height,200])
var node = treemap(d3.hierarchy(root))
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
add_defs(svg)
var color = d3.scaleOrdinal(d3.schemeCategory10);
var nodes = treemap(d3.hierarchy(root))
var links = treemap(nodes).links()
var g = svg.selectAll("g")
.data([nodes])
.enter().append("g")
.attr("class", "nodes")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var link = g.append("g")
.attr("class", "link")
.selectAll("path")
.data(function(d) { return links; })
.enter().append("path")
.attr("class", "links")
.attr('fill','none')
.attr('stroke','black')
.attr('marker-end', 'url(#arr1)')
var node = g.append("g")
.attr("class", "node")
.selectAll("g")
.data(function(d) { return nodes; })
.enter().append("g")
.attr("class", "nodes")
var rects = node.append("rect")
.attr('fill-opacity',0.3)
.attr('fill',(d,i) => {
var c = 'none'
if (i == 1 || i == 2 || i == 7 || i == 8) {
c = color(1)
}else if (i == 3||i==4||i==9||i==10) {
c = color(2)
}else if(i==5||i==6||i==11||i==12) {
c = color(3)
}
return c
})
var mw = 100
var x = 40
var dist = [0,mw+2*x,mw+x,mw+x,mw+x,mw+x]
var loc = []
dist.map((d,i) => {
if (i>0) {
const [e] = loc.slice(-1)
d += e
}
loc.push(d)
})
var txt = node.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr('font-size',12)
.text(function(d) {
return d.data.type == 'hidden'?'':d.data.name;
})
.each(function(d,i,n) {
d.width = Math.max(mw, this.getComputedTextLength() + 10);
d.height=20
if (d.data.type == 'hidden') {
d.width = 0
d.height = 0
}
d.y = loc[d.depth]
})
.attr('x',d => {
return d.y
})
.attr('y', d => d.x)
node.filter(function(d) { return "join" in d.data; }).insert("path", "text")
.attr("class", "join");
var node = svg.selectAll(".node g")
.attr("class", function(d) { return d.data.type; })
.attr('x',d => d.y)
.attr('y',d => d.x)
var x = node.select("rect")
.attr('stroke','black')
.attr('x', d => d.y - d.width/2)
.attr('y', d => d.x - d.height/2)
.attr("ry", 0)
.attr("rx", 0)
.attr("height", 20)
.attr("width", function(d) { return d.width; })
const zagzag = (d) => {
let from = d.source
let to = d.target
let dx = to.y - from.y;
let dy = to.x - from.x;
if (Math.abs(dx) > Math.abs(dy)) {
let mx,my,nx,ny,sx,sy
sx = (dx>0)?1:-1
sy = (dy>0)?1:-1
mx = from.y + from.width/2*sx
my = from.x
nx = to.y - to.width/2*sx
ny = to.x
dx = nx - mx;
dy = ny - my;
return `M ${mx},${my} l ${dx/2},0 0,${dy} ${dx/2},0 `;
}else{//vertical
let mx,my,nx,ny,sx,sy
sx = (dx>0)?1:-1
sy = (dy>0)?1:-1
mx = from.y
my = from.x + from.height/2*sy
nx = to.y
ny = to.x - to.height/2*sy
dx = nx - mx;
dy = ny - my;
return `M ${mx},${my} l 0,${dy/2} ${dx},0 0,${dy/2}`;
}
};
node.select(".join")
.attr("d",d => zagzag(d) );
svg.selectAll(".link path")
.attr("d", d => zagzag(d));
// debug start
var g1 = svg.append('g')
x.filter((d,i) => i == 1||i==2||i==7||i==8)
.each((d,i,n) => {
g1.append(() => n[i])
})
var e = 5
var box = g1.node().getBBox()
svg.insert('rect','.nodes')
.attr('stroke','black')
.attr('fill','green')
.attr('fill-opacity',.4)
.attr('x',box.x-e)
.attr('y',box.y-e)
.attr('width',box.width+2*e)
.attr('height',box.height+2*e)
//debug end
return svg;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

D3 - Create a chart with 3 fixed y-axis ticks

I have a D3 chart but only want to show 3 ticks for the y-axis.
Depending on the data, I sometimes get 3, 4 or 5 ticks which makes it difficult for me to style with CSS.
Here is the full code:
// Create a new d3
var chart = d3.select('#analytics-chart').append('div').attr('class', 'chart');
chart.append('div').attr('class', 'y-axis');
chart.append('div').attr('class', 'bars-and-x-axis');
var barMargin = '0 2px',
min = 0,
max = d3.max(data, function(d) {
return parseInt(d.value, 10);
});
var bars = d3.selectAll('.bars-and-x-axis').append('div').attr('class', 'bars'),
xaxis = d3.selectAll('.bars-and-x-axis').append('div').attr('class', 'x-axis'),
yaxis = d3.selectAll('.y-axis'),
xScale = d3.scale.linear().domain([1, data.length]),
yScale = d3.scale.linear().range(0, 100).domain([min, max]),
barWrapper = bars.selectAll()
.data(data.map(function(d) {
return d.value;
}))
.enter()
.append('div')
.attr('class', function(d, i) {
if (d == 0) {
return 'chart-data-wrapper empty';
} else {
return 'chart-data-wrapper';
}
}).style('margin', barMargin);
var bar = barWrapper.append('div').attr('class', 'chart-data-bar')
.style('height', function(d) {
return Math.ceil((d - min) / (max - min) * 100) + 'px';
})
.append('div')
.attr('class', 'tooltip')
.attr('style', function(d, i) {
return 'left: ' + Math.ceil(i / data.length * 100) + '%; transform: translateX(-' + Math.ceil(i / data.length * 100) + '%); ';
})
.append('p')
.text(function(d, i) {
return data[i].date;
})
.append('p')
.attr('class', 'data')
.text(function(d, i) {
return data[i].content;
});
xaxis.selectAll()
.data(xScale.ticks(12))
.enter()
.append('div')
.attr('class', 'x-axis-mark');
yaxis.selectAll()
.data(yScale.ticks(3))
.enter()
.insert('small', ':first-child')
.attr('class', 'label')
.text(function(d, i) {
if (d > 999) {
d = d / 1000 + 'k';
}
return d;
});
var tick = d3.selectAll('.x-axis-mark')
.append('div')
.attr('class', function(d, i) {
if (i % 3 == 1) {
return 'x-axis-tick-with-label';
} else {
return 'x-axis-tick';
}
});
var label = d3.selectAll('.x-axis-mark')
.append('small')
.attr('class', 'label')
.text(function(d, i) {
var format = d3.time.format('%b');
return data[i].xlabel;
})
.attr('class', function(d, i) {
if (i % 3 != 1) {
d3.select(this).remove();
}
});
Edit - I've added the full code and attached an image of the working example below:
I was able to replace yScale.ticks(3) in .data(yScale.ticks(3)) with my own math function that returns an array of values to create the tick values.

Delete a line when ticks are clicked - d3js

I have two y-axis with time as data.
I am trying to add and delete a line when ticks are clicked on the respective axis.
Lines are getting generated but not sure how to remove the lines. i tried using
svg.data([thisData]).remove('line')
but that removes the chart completely.
MORE DETAILS
there is 1-1 relationship between ticks of respective axis.
var data = [{
"inTime": "2013-04-24T00:00:00-05:00",
"outTime": "2013-04-24T00:00:00-05:00"
}, {
"inTime": "2013-04-24T00:00:00-05:00",
"outTime": "2013-04-24T00:00:00-05:00"
}, {
"inTime": "2013-04-24T00:00:00-05:00",
"outTime": "2013-04-24T00:00:00-05:00"
}, {
"inTime": "2013-04-26T00:00:00-05:00",
"outTime": "2013-04-26T00:00:00-05:00"
},
];
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
},
width = 600,
height = 700;
//Define Left Y axis
var y = d3.time.scale()
.domain([new Date(data[0].inTime), d3.time.day.offset(new Date(data[data.length - 1].inTime), 1)])
.rangeRound([0, width - margin.left - margin.right]);
//Define Right Y axis
var y1 = d3.time.scale()
.domain([new Date(data[1].inTime), d3.time.day.offset(new Date(data[data.length - 1].outTime), 1)])
.rangeRound([0, width - margin.left - margin.right]);
//Left Yaxis attributes
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.tickFormat(d3.time.format('%m/%d %H:%M'))
.tickSize(8)
.tickPadding(8);
//Right Yaxis attributes
var yAxisRight = d3.svg.axis()
.scale(y1)
.orient('right')
.tickFormat(d3.time.format('%m/%d %H:%M'))
.tickSize(8)
.tickPadding(8);
//Create chart
var svg = d3.select('body').append('svg')
.attr('class', 'chart')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
//Add left Yaxis to group
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(100, 5)')
.call(yAxis);
//Add right Yaxis to group
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(400, 1)')
.call(yAxisRight);
var parse = d3.time.format('%m/%d %H:%M');
//Function to add a line between two ticks
function addLine(t1, t2) {
var ticks = {};
d3.selectAll('.tick text').each(function(d) {
ticks[d3.select(this).text()] = this;
});
var pos1 = ticks[t1].getBoundingClientRect();
var pos2 = ticks[t2].getBoundingClientRect();
svg.append('line')
.attr('x1', pos1.top - pos1.width)
.attr('y1', pos1.top + 5)
.attr('x2', pos2.left - 5)
.attr('y2', pos2.top + 5)
.style('stroke', 'black')
}
var ticks = svg.selectAll(".tick");
ticks.attr('class', function(d, i) {
return 'ticks' + i;
}).each(function(d, i) {
d3.select(this).append("circle")
.attr('id', function(d) {
return 'tickCircle' + i;
})
.attr('class', function(d) {
return 'tickCircles' + this.id
})
.attr("r", 5)
.on('click', function(d) {
console.log('clicked')
return addLineNew(this);
})
.on('mouseover', function(d){
d3.select(this).style('fill','red'); })
.on('mouseout', function(d){
d3.select(this).style('fill','black'); })
});
ticks.selectAll("line").remove();
var firstTick;
var secondTick;
var secondTickMap={};
var firstTickMap={};
var allLines=[];
//add Line
function addLineNew(element) {
if (firstTick && secondTick) {
firstTick = '';
secondTick = '';
}
if (!firstTick || firstTick === '') {
firstTick = element.id
}
else if ((secondTick != 'undefined' || secondTick === '') && !(secondTick in firstTickMap)) {
secondTick = element.id
}
if (firstTick && secondTick) {
if(firstTick == secondTick){
if(firstTick in firstTickMap){delete firstTickMap.firstTick;}
else if(firstTick in secondTickMap){delete secondTickMap.firstTick;}
if(secondTick in firstTickMap){delete firstTickMap.secondTick;}
else if(secondTick in secondTickMap ){delete secondTickMap.secondTick;}
}
if(!(firstTick in firstTickMap) && !(secondTick in secondTickMap) && !(firstTick in secondTickMap) && !(secondTick in firstTickMap))
{
var firstTickBBox = getBBox(firstTick)
var secondTickBBox = getBBox(secondTick);
var firstTickPos = getCenterPoint(firstTickBBox);
var secondTickPos = getCenterPoint(secondTickBBox);
firstTickMap[firstTick] = firstTick;
secondTickMap[secondTick] = secondTick;
createLine(firstTickPos, secondTickPos)
}
}
}
//get Center Point
function getCenterPoint(element) {
var thisX = element.left + element.width / 2;
var thisY = element.top + element.height / 2;
return [thisX, thisY]
}
function getBBox(element) {
var thisEl = document.getElementById(element).getBoundingClientRect();
return thisEl;
}
//create a line between pointA and pointB
function createLine(pointA, pointB) {
var thisData = {
x1: pointA[0],
y1: pointA[1],
x2: pointB[0],
y2: pointB[1]
};
allLines.push(svg.data([thisData]).append('line')
.attr('x1', function(d) {
console.log(d)
return d.x1;
})
.attr('y1', function(d) {
return d.y1;
})
.attr('x2', function(d) {
return d.x2;
})
.attr('y2', function(d) {
return d.y2;
}).style('stroke', 'black')
.style('stroke-width','1')
.attr('transform', 'translate(' + (-margin.left - 5) + ', ' + (- margin.top - 5) + ')'));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
First off lets clear a couple of things up. When you create the line you push your array of lines to an array like so :
allLines.push(svg.data([thisData]).append('line')...
This is not the correct way to do it. The best way to do it is, when you create a line, push that lines data, e.g x1,y1,x2,y2 etc to an array and use this array to create all the lines at once. This is how D3 works.
So I changed your functions around.
function createLine(pointA, pointB) {
var thisData = {
x1: pointA[0],
y1: pointA[1],
x2: pointB[0],
y2: pointB[1]
};
allLinesData.push(thisData) //push points into array
drawLines(allLinesData); //draw all lines at once from 'allLinesData'
}
Function to draw lines :
function drawLines(data) { //pass the data you want
var line = svg.selectAll('.line').data(data);
line.enter().append('line')
.attr('id', function(d, i) {return 'genLine' + i; })
.attr('x1', function(d) { return d.x1;})
.attr('y1', function(d) { return d.y1; })
.attr('x2', function(d) { return d.x2; })
.attr('y2', function(d) { return d.y2; })
.style('stroke', 'black')
.style('stroke-width', '3')
.on('mouseover', function(d) { d3.select(this).style('stroke', 'red') })
.on('mouseout', function(d) { d3.select(this).style('stroke', 'black') })
.attr('transform', 'translate(' + (-margin.left - 5) + ', ' + (-margin.top - 5) + ')')
line.on('dblclick', function(d) { //delete line
var thisLine = this;
line.each(function(e, i) {
var thisLine2 = this;
if (thisLine.id === thisLine2.id) {
console.log('splice')
allLinesData.splice(i--, 1); //remove from array you use to feed the line drawer
d3.select(this).remove(); //remove it from DOM
}
})
})
line.exit().remove(); //remove unwanted lines
}
Also added on 'mouseover' so you know what line youre on.
Here is a working fiddle : https://jsfiddle.net/reko91/vr09w905/1/
Also if you just want it here :
var data = [{
"inTime": "2013-04-24T00:00:00-05:00",
"outTime": "2013-04-24T00:00:00-05:00"
}, {
"inTime": "2013-04-24T00:00:00-05:00",
"outTime": "2013-04-24T00:00:00-05:00"
}, {
"inTime": "2013-04-24T00:00:00-05:00",
"outTime": "2013-04-24T00:00:00-05:00"
}, {
"inTime": "2013-04-26T00:00:00-05:00",
"outTime": "2013-04-26T00:00:00-05:00"
}, ];
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
},
width = 600,
height = 700;
//Define Left Y axis
var y = d3.time.scale()
.domain([new Date(data[0].inTime), d3.time.day.offset(new Date(data[data.length - 1].inTime), 1)])
.rangeRound([0, width - margin.left - margin.right]);
//Define Right Y axis
var y1 = d3.time.scale()
.domain([new Date(data[1].inTime), d3.time.day.offset(new Date(data[data.length - 1].outTime), 1)])
.rangeRound([0, width - margin.left - margin.right]);
//Left Yaxis attributes
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.tickFormat(d3.time.format('%m/%d %H:%M'))
.tickSize(8)
.tickPadding(8);
//Right Yaxis attributes
var yAxisRight = d3.svg.axis()
.scale(y1)
.orient('right')
.tickFormat(d3.time.format('%m/%d %H:%M'))
.tickSize(8)
.tickPadding(8);
//Create chart
var svg = d3.select('body').append('svg')
.attr('class', 'chart')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
//Add left Yaxis to group
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(100, 5)')
.call(yAxis);
//Add right Yaxis to group
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(400, 1)')
.call(yAxisRight);
var parse = d3.time.format('%m/%d %H:%M');
//Function to add a line between two ticks
function addLine(t1, t2) {
var ticks = {};
d3.selectAll('.tick text').each(function(d) {
ticks[d3.select(this).text()] = this;
});
var pos1 = ticks[t1].getBoundingClientRect();
var pos2 = ticks[t2].getBoundingClientRect();
svg.append('line')
.attr('x1', pos1.top - pos1.width)
.attr('y1', pos1.top + 5)
.attr('x2', pos2.left - 5)
.attr('y2', pos2.top + 5)
.style('stroke', 'black')
}
var ticks = svg.selectAll(".tick");
ticks.attr('class', function(d, i) {
return 'ticks' + i;
}).each(function(d, i) {
d3.select(this).append("circle")
.attr('id', function(d) {
return 'tickCircle' + i;
})
.attr('class', function(d) {
return 'tickCircles' + this.id
})
.attr("r", 5)
.on('click', function(d) {
console.log('clicked')
return addLineNew(this);
})
.on('mouseover', function(d) {
d3.select(this).style('fill', 'red');
})
.on('mouseout', function(d) {
d3.select(this).style('fill', 'black');
})
});
ticks.selectAll("line").remove();
var firstTick;
var secondTick;
var secondTickMap = {};
var firstTickMap = {};
//var allLines = [];
var allLinesData = [];
//add Line
function addLineNew(element) {
if (firstTick && secondTick) {
firstTick = '';
secondTick = '';
}
if (!firstTick || firstTick === '') {
firstTick = element.id
} else if ((secondTick != 'undefined' || secondTick === '') && !(secondTick in firstTickMap)) {
secondTick = element.id
}
if (firstTick && secondTick) {
if (firstTick == secondTick) {
if (firstTick in firstTickMap) {
delete firstTickMap.firstTick;
} else if (firstTick in secondTickMap) {
delete secondTickMap.firstTick;
}
if (secondTick in firstTickMap) {
delete firstTickMap.secondTick;
} else if (secondTick in secondTickMap) {
delete secondTickMap.secondTick;
}
}
if (!(firstTick in firstTickMap) && !(secondTick in secondTickMap) && !(firstTick in secondTickMap) && !(secondTick in firstTickMap)) {
var firstTickBBox = getBBox(firstTick)
var secondTickBBox = getBBox(secondTick);
var firstTickPos = getCenterPoint(firstTickBBox);
var secondTickPos = getCenterPoint(secondTickBBox);
firstTickMap[firstTick] = firstTick;
secondTickMap[secondTick] = secondTick;
createLine(firstTickPos, secondTickPos)
}
}
}
//get Center Point
function getCenterPoint(element) {
var thisX = element.left + element.width / 2;
var thisY = element.top + element.height / 2;
return [thisX, thisY]
}
function getBBox(element) {
var thisEl = document.getElementById(element).getBoundingClientRect();
return thisEl;
}
//create a line between pointA and pointB
function createLine(pointA, pointB) {
var thisData = {
x1: pointA[0],
y1: pointA[1],
x2: pointB[0],
y2: pointB[1]
};
allLinesData.push(thisData) //push points into array
drawLines(allLinesData); //draw all lines at once from 'allLinesData'
}
function drawLines(data) { //pass the data you want
var line = svg.selectAll('.line').data(data);
line.enter().append('line')
.attr('id', function(d, i) {
return 'genLine' + i;
})
.attr('x1', function(d) {
return d.x1;
})
.attr('y1', function(d) {
return d.y1;
})
.attr('x2', function(d) {
return d.x2;
})
.attr('y2', function(d) {
return d.y2;
})
.style('stroke', 'black')
.style('stroke-width', '3')
.on('mouseover', function(d) {
d3.select(this).style('stroke', 'red')
})
.on('mouseout', function(d) {
d3.select(this).style('stroke', 'black')
})
.attr('transform', 'translate(' + (-margin.left - 5) + ', ' + (-margin.top - 5) + ')')
line.on('dblclick', function(d) { //delete line
var thisLine = this;
line.each(function(e, i) {
var thisLine2 = this;
if (thisLine.id === thisLine2.id) {
console.log('splice')
allLinesData.splice(i--, 1); //remove from array you use to feed the line drawer
d3.select(this).remove(); //remove it from DOM
}
})
})
line.exit().remove(); //remove unwanted lines
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>

Resources