For reference, I have taken sample code from Mike Bostock's Line Chart Example. Made few changes to handle touch and mouse move/drag
xxxxxxxxxx
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: blue;
stroke-width: 1.5px;
}
.xValue {
fill: #000000;
font-weight: bold;
}
.yValue {
fill: #999999;
}
.yValue.range {
fill: #000000;
}
.xValue.xPos {
fill: #44AA44;
}
.xValue.xNeg {
fill: #FF3333;
}
</style>
<body>
<script src="//d3js.org/d3.v3.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatDate = d3.time.format("%d-%b-%y");
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var touchScaleX = d3.scale.linear();
var touchScaleY = d3.scale.linear();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close);
});
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 + ")");
var rect = svg.append('rect')
.attr("width", width)
.attr("height", height)
.style("fill", "white");
var chartData = [];
var pointCoordinates = [];
d3.tsv("data.tsv", type, function(error, data) {
data.sort(function(a, b) {
if (a.date.getTime() > b.date.getTime()) {
return 1;
} else if (a.date.getTime() < b.date.getTime()) {
return -1;
} else {
return 0;
}
});
if (error) throw error;
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain(d3.extent(data, function(d) {
return d.close;
}));
chartData = data;
data.forEach(function(d) {
var localCoordinates = {};
localCoordinates.x = x(d.date);
localCoordinates.y = y(d.close);
pointCoordinates.push(localCoordinates);
});
touchScaleX.domain(pointCoordinates.map(function(d) {
return d.x;
}));
touchScaleX.range(pointCoordinates.map(function(d, i) {
return i;
})).clamp(true);
touchScaleY.domain(pointCoordinates.map(function(d) {
return d.y;
}));
touchScaleY.range(pointCoordinates.map(function(d, i) {
return i;
})).clamp(true);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.append("g").attr("id", "touchLayer");
});
function type(d) {
d.date = formatDate.parse(d.date);
d.close = +d.close;
return d;
}
var boxWidth = 0;
var boxHeight = 24;
function updatePositionsCallbackEvent(positions) {
positions.forEach(function(x, posIndex) {
var currX = positions[posIndex][0];
var currY = positions[posIndex][1];
var index = ~~touchScaleX(currX);
if (currX > 0 && index < pointCoordinates.length - 1) {
var perc = (currX - pointCoordinates[index].x) / (pointCoordinates[index + 1].x - pointCoordinates[index].x);
if (perc <= 0.5) {
positions[posIndex][0] = pointCoordinates[index].x == 0 ? 1 : pointCoordinates[index].x;
positions[posIndex][1] = pointCoordinates[index].y;
} else {
positions[posIndex][0] = pointCoordinates[index + 1].x;
positions[posIndex][1] = pointCoordinates[index + 1].y;
}
}
});
for (var i = 1; i < positions.length; i++) {
if (positions[i - 1][0] === positions[i][0]) {
positions.splice(i, 1);
i--;
}
}
var lines = svg.select("#touchLayer").selectAll("g.touchElements")
.data(positions);
var touchElements = lines.enter()
.append("g").attr("class", "touchElements");
// boxWidthboxHeight
touchElements.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 5)
.style("fill", "blue")
.style("stroke-width", 2)
.style("stroke", 'blue');
touchElements.append("rect")
.attr("height", boxHeight)
.style("fill", "#EEE")
;
touchElements.append("text")
.attr("x", boxWidth / 2)
.attr("y", boxHeight / 2)
.style("font-size", 14)
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.style("fill", "black")
.attr("transform", function(d) {
return "translate(-" + boxWidth / 2 + ",0)"
});
var inlineSpanData = [{
text: "Hello",
class: "xValue"
}, {
text: "There",
class: "yValue"
}];
var spans = lines.selectAll("text").selectAll("tspan").data(inlineSpanData);
spans.enter().append("tspan")
.text(function(d) {
return d.text;
})
.attr("class", function(d) {
return d.class;
});
touchElements.append("line")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", height)
.attr("y2", boxHeight)
.style("stroke-dasharray", "1, 2")
.style("stroke-width", 0.5)
.style("stroke", 'black')
.style("fill", 'black');
lines.attr("transform", function(d) {
return "translate(" + d[0] + ",0)";
});
var showSeperate = false;
if (showSeperate) {
lines.selectAll("text")
.each(function(d, i) {
var tspanVal1 = "Hello";
var tspanVal2 = "There";
var tspanClass1 = "xValue";
var tspanClass2 = "yValue";
var tspanDx2 = ".4em";
tspanVal1 = Math.round(y.invert(d3.select(this.parentNode).datum()[1]) * 100) / 100;
tspanVal2 = d3.time.format("%b %d, %Y")(x.invert(d3.select(this.parentNode).datum()[0]));
var tspans = d3.select(this).selectAll('tspan')[0];
d3.select(tspans[0]).text(tspanVal1).attr("class", tspanClass1);
d3.select(tspans[1]).text(tspanVal2).attr("class", tspanClass2).attr("dx", tspanDx2);
var bbox = this.getBBox();
if (boxWidth < bbox.width) {
boxWidth = bbox.width + 20;
}
});
lines.selectAll("rect")
.attr("width", boxWidth)
.attr("transform", function(d) {
d = d3.select(this.parentNode).datum();
if (d[0] < (boxWidth / 2)) {
return "translate(-" + (d[0]) + ",0)";
} else if (d[0] > width - (boxWidth / 2)) {
return "translate(-" + (boxWidth - (width - d[0])) + ",0)";
} else {
return "translate(-" + boxWidth / 2 + ",0)";
}
});
lines.selectAll("text")
.attr("x", boxWidth / 2)
.attr("y", boxHeight / 2)
.attr("transform", function(d) {
d = d3.select(this.parentNode).datum();
if (d[0] < (boxWidth / 2)) {
return "translate(-" + (d[0]) + ",0)";
} else if (d[0] > width - (boxWidth / 2)) {
return "translate(-" + (boxWidth - (width - d[0])) + ",0)";
} else {
return "translate(-" + boxWidth / 2 + ",0)";
}
});
} else {
var direction = 1;
if (positions[0][0] > positions[positions.length - 1][0])
direction = -1;
var padding = 0;
boxWidth = 0;
lines.selectAll("text")
.each(function(d) {
var tspanVal1 = "Hello";
var tspanVal2 = "There";
var tspanClass1 = "xValue";
var tspanClass2 = "yValue";
var tspanDx2 = ".4em";
if (positions.length === 1) {
tspanVal1 = Math.round(y.invert(d3.select(this.parentNode).datum()[1]) * 100) / 100;
tspanVal2 = d3.time.format("%b %d, %Y")(x.invert(d3.select(this.parentNode).datum()[0]));
} else {
var val1 = y.invert(positions[direction == 1 ? 0 : positions.length - 1][1]);
var val2 = y.invert(positions[direction == -1 ? 0 : positions.length - 1][1]);
var diff = Math.round((val2 - val1) * 100) / 100;
if (diff >= 0) {
tspanClass1 += " xPos";
} else {
tspanClass1 += " xNeg";
}
tspanClass2 += " range";
var perc = ((val2 - val1) * 100 / val1)
tspanVal1 = diff + "(" + Math.round(perc) + "%)";
tspanVal2 = d3.time.format("%b %d, %Y")(x.invert(positions[direction == 1 ? 0 : positions.length - 1][0])) + '-' + d3.time.format("%b %d, %Y")(x.invert(positions[direction == -1 ? 0 : positions.length - 1][0]));
tspanDx2 = "1em";
}
var tspans = d3.select(this).selectAll('tspan')[0];
d3.select(tspans[0]).text(tspanVal1).attr("class", tspanClass1);
d3.select(tspans[1]).text(tspanVal2).attr("class", tspanClass2).attr("dx", tspanDx2);
var bbox = this.getBBox();
if (boxWidth < bbox.width) {
//padding += (bbox.width - boxWidth)/2;
boxWidth = bbox.width + 20;
}
});
if (boxWidth < (positions[direction == -1 ? 0 : positions.length - 1][0] - positions[direction == 1 ? 0 : positions.length - 1][0])) {
boxWidth = (positions[direction == -1 ? 0 : positions.length - 1][0] - positions[direction == 1 ? 0 : positions.length - 1][0])
} else {
padding = (boxWidth - (positions[direction == -1 ? 0 : positions.length - 1][0] - positions[direction == 1 ? 0 : positions.length - 1][0])) / 2
}
console.log("*****");
lines.selectAll("rect")
.attr("width", boxWidth)
.attr("transform", function(d, i) {
d[0] = d3.select(this.parentNode).datum()[0];
var diff = 0;
if (positions.length === 1) {
if (d[0] < (boxWidth / 2)) {
diff += (boxWidth / 2) - d[0];
} else if (d[0] > width - (boxWidth / 2)) {
diff -= d[0] - (width - (boxWidth / 2));
}
} else {
diff = positions[direction == 1 ? 0 : positions.length - 1][0] - d3.select(this.parentNode).datum()[0];
if (d[0] - (-diff + padding) < 0) {
diff -= d[0] - (-diff + padding);
} else if (d[0] - (-diff + padding) + (boxWidth) > width) {
diff -= d[0] - (-diff + padding) + (boxWidth) - width;
}
}
return "translate(-" + (-diff + padding) + ",0)";
});
lines.selectAll("text")
.attr("x", boxWidth / 2)
.attr("y", boxHeight / 2)
.attr("transform", function(d) {
d[0] = d3.select(this.parentNode).datum()[0];
var diff = 0;
if (positions.length === 1) {
if (d[0] < (boxWidth / 2)) {
diff += (boxWidth / 2) - d[0];
} else if (d[0] > width - (boxWidth / 2)) {
diff -= d[0] - (width - (boxWidth / 2));
}
} else {
diff = positions[direction == 1 ? 0 : positions.length - 1][0] - d3.select(this.parentNode).datum()[0];
if (d[0] - (-diff + padding) < 0) {
diff -= d[0] - (-diff + padding);
} else if (d[0] - (-diff + padding) + (boxWidth) > width) {
diff -= d[0] - (-diff + padding) + (boxWidth) - width;
}
}
return "translate(-" + (-diff + padding) + ",0)";
});
}
lines.selectAll("circle").attr("transform", function(d) {
var currX = d3.select(this.parentNode).datum()[0];
var currY = d3.select(this.parentNode).datum()[1];
var index = ~~touchScaleX(d3.select(this.parentNode).datum()[0]);
if (currX > 0 && index < pointCoordinates.length - 1) {
//console.log(index, currX, pointCoordinates[index], pointCoordinates[index + 1]);
var perc = (currX - pointCoordinates[index].x) / (pointCoordinates[index + 1].x - pointCoordinates[index].x)
var newY = (perc * (pointCoordinates[index + 1].y - pointCoordinates[index].y)) + pointCoordinates[index].y;
return "translate(0," + newY + ")";
}
});
lines.exit().remove();
}
watchTouchMoveEvents(svg, updatePositionsCallbackEvent);
function watchTouchMoveEvents(element, updatePositionsCallback) {
var isDrag = false;
var selectionPoints = [];
var touchEventsSupport = false;
element.on('mousedown', function(e) {
isDrag = true;
d3.event.preventDefault();
}).on('mouseup', function(e) {
isDrag = false;
d3.event.preventDefault();
}).on('mousemove', function(e) {
var coordinates = d3.mouse(this);
if (coordinates[0] <= 0 || coordinates[0] >= width)
return;
if (touchEventsSupport === false) {
if (isDrag === false) {
selectionPoints = [];
selectionPoints[0] = coordinates;
} else {
selectionPoints[1] = coordinates;
}
updatePositions();
} else {
d3.event.preventDefault();
}
}).on("touchstart", function(e) {
touchEventsSupport = true;
var coordinates = d3.touches(this);
if (coordinates[0] <= 0 || coordinates[0] >= width)
return;
selectionPoints = coordinates;
updatePositions();
}).on("touchmove", function(e) {
var coordinates = d3.touches(this);
if (coordinates[0] <= 0 || coordinates[0] >= width)
return;
selectionPoints = coordinates;
updatePositions();
}).on("touchend", function(e) {
// do nothing
// var coordinates = d3.touches(this); // selectionPoints = coordinates; //updatePositions();
// d3.select("#events").append("div").text("touchend:" + coordinates);
});
var timer = 0;
function updatePositions() {
d3.event.preventDefault();
clearTimeout(timer);
timer = setTimeout(updatePositionsFinal, 0);
}
function updatePositionsFinal() {
updatePositionsCallback(selectionPoints);
}
}
</script>
<div id="events">
</div>
</body>
https://d3js.org/d3.v3.js