|
<!DOCTYPE html> |
|
|
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<style> |
|
body { |
|
margin: 0; |
|
top: 0; |
|
right: 0; |
|
bottom: 0; |
|
left: 0; |
|
} |
|
|
|
path { |
|
stroke-width: 3px |
|
} |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<script> |
|
|
|
const height = 500 |
|
const width = 500 |
|
const margin = { "top": 20, "bottom": 20, "left": 20, "right": 20 } |
|
|
|
const data1 = [2, 5, 6, 7, 3, 8, 3, 4] |
|
const data2 = [6, 2, 2, 2, 2, 2, 4, 9] |
|
|
|
let combinedData = [] |
|
|
|
for (var i = 0; i < data1.length; i++) { |
|
let o = {} |
|
o.data1 = data1[i] |
|
o.data2 = data2[i] |
|
combinedData.push(o) |
|
} |
|
|
|
let xScale = d3.scaleLinear() |
|
.domain([0, combinedData.length - 1]) |
|
.range([0, width]) |
|
|
|
let yScale = d3.scaleLinear() |
|
.domain([0, 10]) |
|
.range([height, 0]) |
|
|
|
let curve = d3.curveCatmullRom.alpha(0.5) |
|
|
|
let area = d3.area() |
|
.x(function (d, i) { return xScale(i) }) |
|
.y0(function (d) { return yScale(d.data1) }) |
|
.y1(function (d) { return yScale(d.data2) }) |
|
.curve(curve); |
|
|
|
let line1 = d3.line() |
|
.x(function (d, i) { return xScale(i) }) |
|
.y(function (d) { return yScale(d.data1) }) |
|
.curve(curve); |
|
|
|
let line2 = d3.line() |
|
.x(function (d, i) { return xScale(i) }) |
|
.y(function (d) { return yScale(d.data2) }) |
|
.curve(curve); |
|
|
|
let svg = d3.select("body").append("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom) |
|
|
|
let g = svg.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")") |
|
|
|
var gradient = g.append("defs").append("linearGradient") |
|
.attr("id", "area-gradient") |
|
.attr("x1", "0%") |
|
.attr("y1", "0%") |
|
.attr("x2", "100%") |
|
.attr("y2", "0%"); |
|
|
|
let offset1 = 0.2 |
|
let offset2 = 0.4 |
|
|
|
let stopsData = [ |
|
{ "offset": 0, "stopColour": "#FFFFFF" }, |
|
{ "offset": offset1, "stopColour": "#FFFFFF" }, |
|
{ "offset": offset1, "stopColour": "#777777" }, |
|
{ "offset": offset2, "stopColour": "#777777" }, |
|
{ "offset": offset2, "stopColour": "#FFFFFF" }, |
|
{ "offset": 1, "stopColour": "#FFFFFF" } |
|
] |
|
|
|
gradient.selectAll("stop") |
|
.data(stopsData) |
|
.enter() |
|
.append("stop") |
|
.attr("offset", function (d) { return d.offset }) |
|
.attr("stop-color", function (d) { return d.stopColour }) |
|
|
|
let areaFill = g.append("path") |
|
.datum(combinedData) |
|
.style("fill", "url(#area-gradient)") |
|
.style("opacity", 0) |
|
.attr("d", area) |
|
|
|
let areaLine1 = g.append("line") |
|
.attr("x1", width*offset1) |
|
.attr("x2", width*offset1) |
|
.attr("y1", 0) |
|
.attr("y2", height) |
|
.style("stroke", "black") |
|
.style("stroke-width", "3px") |
|
.style("opacity", 0) |
|
|
|
let areaLine2 = g.append("line") |
|
.attr("x1", width*offset2) |
|
.attr("x2", width*offset2) |
|
.attr("y1", 0) |
|
.attr("y2", height) |
|
.style("stroke", "black") |
|
.style("stroke-width", "3px") |
|
.style("opacity", 0) |
|
|
|
let path1 = g.append("path") |
|
.datum(combinedData) |
|
.style("stroke", "red") |
|
.style("fill", "none") |
|
.attr("d", line1) |
|
|
|
let path2 = g.append("path") |
|
.datum(combinedData) |
|
.style("stroke", "blue") |
|
.style("fill", "none") |
|
.attr("d", line2) |
|
|
|
let path1Node = path1.node() |
|
let path2Node = path2.node() |
|
|
|
let rect = g.append("rect") |
|
.attr("width", width) |
|
.attr("height", height) |
|
.style("opacity", 0) |
|
.on("mouseover", function () { |
|
areaFill.style("opacity", 1) |
|
areaLine1.style("opacity", 1) |
|
areaLine2.style("opacity", 1) |
|
}) |
|
.on("mouseout", function () { |
|
areaFill.transition().style("opacity", 0) |
|
areaLine1.transition().style("opacity", 0) |
|
areaLine2.transition().style("opacity", 0) |
|
}) |
|
.on("mousemove", function (d) { |
|
let x = d3.mouse(this)[0] |
|
let middle = x / width |
|
|
|
offset1 = (middle - 0.1) < 0 ? 0 : (middle - 0.1) |
|
offset2 = (middle + 0.1) > 1 ? 1 : (middle + 0.1) |
|
|
|
o1 = width * offset1 |
|
o2 = width * offset2 |
|
|
|
line1Ys = findYs(path1Node, o1, o2) |
|
line2Ys = findYs(path2Node, o1, o2) |
|
|
|
areaLine1.attr("x1", width*offset1) |
|
.attr("x2", width*offset1) |
|
.attr("y1", line1Ys[0]) |
|
.attr("y2", line2Ys[0]) |
|
|
|
areaLine2.attr("x1", width*offset2) |
|
.attr("x2", width*offset2) |
|
.attr("y1", line1Ys[1]) |
|
.attr("y2", line2Ys[1]) |
|
|
|
stopsData = [ |
|
{ "offset": 0, "stopColour": "#FFFFFF" }, |
|
{ "offset": offset1, "stopColour": "#FFFFFF" }, |
|
{ "offset": offset1, "stopColour": "#777777" }, |
|
{ "offset": offset2, "stopColour": "#777777" }, |
|
{ "offset": offset2, "stopColour": "#FFFFFF" }, |
|
{ "offset": 1, "stopColour": "#FFFFFF" } |
|
] |
|
|
|
gradient.selectAll("stop") |
|
.data(stopsData) |
|
.attr("offset", function (d) { return d.offset }) |
|
.attr("stop-color", function (d) { return d.stopColour }) |
|
|
|
}) |
|
|
|
function findYs(p, x1, x2) { |
|
const accuracy = 1 //increase for quicker, but less accurate lines |
|
let ys = []; |
|
let i = x1; |
|
const l = p.getTotalLength() |
|
for (i; i < l; i+=accuracy) { |
|
let pos = p.getPointAtLength(i) |
|
if (pos.x > x1) { |
|
ys.push(pos.y) |
|
break |
|
} |
|
} |
|
for (i; i < l; i+=accuracy) { |
|
let pos = p.getPointAtLength(i) |
|
if (pos.x > x2) { |
|
ys.push(pos.y) |
|
break |
|
} |
|
} |
|
return ys; |
|
} |
|
|
|
|
|
|
|
</script> |
|
</body> |