{
const root = d3.create('div')
root.append('div')
.style('height', '25px')
.style('font-weight', 'bold')
.text('Using line dictionary:');
const segmentsDiv = root.append('div')
.style('height', '25px');
const initializationDiv = root.append('div')
.style('height', '25px');
const foundSegmentsDiv = root.append('div')
.style('height', '25px')
.text('Found segments: ---');
const lookupPerformanceDiv = root.append('div')
.style('height', '25px')
.text('Look-up performance: ---');
const getClosestDiv = root.append('div')
.style('height', '25px')
.text('Get closest performance: ---');
const fullLines = [];
const lineSegments = [];
let prevSelection = null;
const init0 = performance.now();
const dict = new LineDictionary(-2);
const firstLine = lines[0];
for (let n = 0; n < lines.length; n++) {
const d = lines[n];
const segmentIDs = [];
for (let i = 1; i < d.length; i++) {
const p1 = d[i - 1];
const p2 = d[i];
// Append new line segments
const segmentID = lineSegments.length;
segmentIDs.push(segmentID);
lineSegments.push({
segmentID,
lineID: n,
geometry: {x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y},
line: null, // Set later
});
dict.add(p1.x, p1.y, p2.x, p2.y, segmentID);
}
fullLines.push(segmentIDs);
};
const init1 = performance.now(); // Init timer end
segmentsDiv.text(`Total segments: ${lineSegments.length}`);
initializationDiv.text(`Initialization: ${(init1 - init0).toFixed(2)} ms`);
const svg = root.append('svg')
.style('width', `${width}px`)
.style('height', `${height}px`)
.style('border', '2px dotted DimGrey')
.on('mouseout', () => {
if (prevSelection) {
prevSelection.forEach(d => {
lineSegments[d].line.attr('stroke', 'SteelBlue');
});
}
prevSelection = null;
})
.on('mousemove', () => {
// Undo previous selection
if (prevSelection) {
prevSelection.forEach(d => {
lineSegments[d].line.attr('stroke', 'SteelBlue');
});
}
const mousePos = d3.mouse(d3.event.target);
const t0 = performance.now();
const linesOnTile = dict.getLinesOn3Grid(mousePos[0], mousePos[1]);
const t1 = performance.now();
foundSegmentsDiv.text(`Found segments: ${linesOnTile.length}`);
lookupPerformanceDiv.text(`Look-up performance: ${(t1 - t0).toFixed(2)} ms`);
if(!linesOnTile) return;
// Position as point
const point = new Vector2(mousePos[0], mousePos[1]);
const t2 = performance.now();
let minDist = Infinity;
let minLineID = null;
linesOnTile.forEach(d => {
const seg = lineSegments[d];
const dist = DistanceToLine(
point,
new Vector2(seg.geometry.x1, seg.geometry.y1),
new Vector2(seg.geometry.x2, seg.geometry.y2),
);
if (dist < minDist) {
minDist = dist;
minLineID = seg.lineID;
}
});
const t3 = performance.now();
getClosestDiv.text(`Get closest performance: ${(t3 - t2).toFixed(2)} ms`);
// Color all segments
fullLines[minLineID].forEach(d => {
lineSegments[d].line.attr('stroke', 'Tomato');
});
prevSelection = fullLines[minLineID];
});
// Line function
function appendLine(x1, y1, x2, y2, color) {
svg.append('line')
.attr('x1', x1).attr('y1', y1)
.attr('x2', x2).attr('y2', y2)
.attr('stroke', color)
.attr('pointer-events', 'none');
}
// Append grid
for (let x = 100; x < width; x+=100) appendLine(x, 0, x, height, 'LightGrey');
for (let y = 100; y < height; y+=100) appendLine(0, y, width, y, 'LightGrey');
lineSegments.forEach(d => {
d.line = svg.append('line')
.attr('x1', d.geometry.x1).attr('y1', d.geometry.y1)
.attr('x2', d.geometry.x2).attr('y2', d.geometry.y2)
.attr('stroke', 'SteelBlue')
.attr('pointer-events', 'none');
});
return root.node();
}