Hehe
Revising this one
Using Visvalingam line simplification algorithm, hackily adapted from
forked from tophtucker's block: Line chart scroller II
xxxxxxxxxx
<meta charset="utf-8">
<style>
* {
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
font-family: helvetica, sans-serif;
}
.table-wrapper {
position: relative;
height: 100%;
padding-top: 20px;
width: 8em;
margin: 0 0 0 auto;
}
.table-scroll {
height: 100%;
overflow: scroll;
}
table {
margin-right: 0;
margin-left: auto;
}
thead {
background: white;
}
th > div {
position: absolute;
width: 4em;
text-align: left;
background: white;
padding: 3px;
top: 0;
}
td {
padding: 3px;
width: 4em;
}
td:last-child,
th:last-child > div {
text-align: right;
}
svg {
position: absolute;
top: 0;
left: 0;
width: calc(100% - 8.5em);
height: 100%;
}
path {
fill: none;
stroke-width: 1;
stroke: black;
}
rect {
pointer-events: all;
fill: none;
}
</style>
<body>
<div class="table-wrapper">
<div class="table-scroll">
<table>
<thead>
<th><div>Name</div></th>
<th><div>Value</div></th>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<svg></svg>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="visvalingam.js"></script>
<script>
var format = d3.format("$.0f"),
marginLeft = 40,
indexThreshold = 4;
var data = d3.range(100).map(getRandomSeries)
.sort((a,b) => b.data[b.data.length-1][1] - a.data[a.data.length-1][1])
var x = d3.scaleTime()
.domain(d3.extent(d3.merge(data.map(d => d.data)).map(d => d[0])))
.range([marginLeft, d3.select("svg").node().getBoundingClientRect().width])
var y = d3.scaleLinear()
.domain(d3.extent(d3.merge(data.map(d => d.data)).map(d => d[1])))
.range([innerHeight, 0])
// add a third entry to each array element with triangle area for simplify algo
data.forEach(d => {
simplify(d.data)
})
var line = d3.line()
.x(d => x(d[0]))
.y(d => y(d[1]))
var row = d3.select("tbody")
.selectAll("tr")
.data(data)
.enter()
.append("tr")
.on("mouseenter", function(d,i) {
render(i);
})
row.append("td").text(d => d.name)
row.append("td").text(d => format(d.data[d.data.length-1][1] - 1))
var yAxis = d3.axisLeft(y).tickFormat(format)
var yAxisG = d3.select("svg").append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(" + marginLeft + ",0)")
.call(yAxis)
var path = d3.select("svg")
.selectAll("path.line")
.data(data)
.enter()
.append("path")
.classed("line", true)
.attr("d", d => line(d.data))
var scrollScale = d3.scaleLinear()
.domain([0, d3.select(".table-scroll").node().scrollHeight - (innerHeight - 20)])
.range([0, data.length-1])
var opacityScale = d3.scaleLinear()
.domain([0, indexThreshold])
.range([1, 0])
.clamp(true)
var zoom = d3.zoom()
.scaleExtent([1, data.length])
.on("zoom", zoomed);
d3.select("svg")
.append("rect")
.attr("width", innerWidth)
.attr("height", innerHeight)
.call(zoom)
function zoomed() {
indexThreshold = d3.event.transform.k
opacityScale.domain([0, indexThreshold])
render(lastIndex)
}
d3.select(".table-scroll").on("scroll", function() {
render(Math.round(scrollScale(this.scrollTop)));
})
var simplifyAreaScale = d3.scaleLinear()
.domain([3, data.length])
.range(d3.extent(d3.merge(data.map(d => d.data)).map(d => d[2])))
.clamp(true)
var lastIndex = 0;
function render(index) {
lastIndex = index
row
.style("opacity", (d,i) => opacityScale(Math.abs(index - i)))
.style("font-weight", (d,i) => i == index ? 'bold' : 'normal')
path
.style("opacity", (d,i) => opacityScale(Math.abs(index - i)))
.style("stroke-width", (d,i) => i == index ? 3 : 1)
.each(d => d.simplified = d.data.filter((d,i,arr) => i == 0 || i == arr.length-1 || d[2] >= simplifyAreaScale(indexThreshold)))
// .attr("d", d => line(d.simplified))
y.domain(
padExtent(
d3.extent(
d3.merge(
data
.filter((d,i) => Math.abs(index - i) < indexThreshold)
.map(d => d.data)
).map(d => d[1])
)
)
)
// var t = d3.transition()
// .duration(250)
// .ease(d3.easeLinear)
path
// .transition(t)
.attr("d", d => line(d.simplified))
yAxisG
// .transition(t)
.call(yAxis)
}
function getRandomSeries() {
return {
name: getRandomTicker(),
data: getRandomTimeSeries(100)
}
}
function getRandomTicker() {
var length = Math.ceil(Math.random()*4);
var chars = 'abcdefghijklmnopqrstuvwxyz';
return d3.range(length).map(() => chars[Math.floor(Math.random()*chars.length)].toUpperCase()).join('');
}
function getRandomTimeSeries(numPoints) {
var data = d3.range(numPoints).map(d => [
d3.interpolateDate(new Date("2000/01/01"), new Date("2016/10/01"))(d/numPoints),
undefined
])
data.forEach(function(d,i,arr) {
if(i==0) {
d[1] = d3.randomNormal(75, 30)()
} else {
d[1] = arr[i-1][1] * d3.randomNormal(1, .02)()
}
})
return data
}
function padExtent(extent) {
var d = extent[1] - extent[0]
return [
extent[0] - d * .25,
extent[1] + d * .25
]
}
</script>
https://d3js.org/d3.v4.min.js