chart = {
const Label = 'Wavelength (nm)'
const marginTop = 20
const marginRight = 30
const marginBottom = 30
const marginLeft = 20
const strokeWidth = 1.5
const X = d3.map(data, d => d.X);
const Y = d3.map(data, d => d.Y);
const Z = d3.map(data, d => d.Sample);
const O = d3.map(data, d => d);
let selectedSample;
let D = d3.map(data, (d, i) => !isNaN(X[i]) && !isNaN(Y[i]));
const xDomain = d3.extent(X);
const yDomain = d3.extent(Y);
console.log(yDomain)
const zDomain = new d3.InternSet(Z);
console.log(zDomain)
const color = d3.scaleOrdinal(zDomain, d3.schemeTableau10);
const I = d3.range(X.length).filter(i => zDomain.has(Z[i]));
const xScale = d3.scaleLinear(xDomain, [marginLeft, width - marginRight]);
const yScale = d3.scaleLinear(yDomain, [height - marginBottom, marginTop]);
const xAxis = d3.axisBottom(xScale).ticks(width / 40).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(height / 30);
const T = Z;
const selmodel = SelectionModel(zDomain);
const groups = d3.group(data, d => d.X)
const line = d3.line()
.defined(i => D[i])
.curve(d3.curveLinear)
.x(i => xScale(X[i]))
.y(i => yScale(Y[i]));
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.style("-webkit-tap-highlight-color", "transparent")
.on("pointerenter", pointerentered)
.on("pointermove", pointermoved)
.on("pointerleave", pointerleft)
.on("click", pointclicked)
.on("touchstart", event => event.preventDefault());
const compareLine = svg.append("g");
compareLine.append("line")
.attr("y1", marginTop)
.attr("y2", height - marginBottom + 5)
.attr("stroke", "currentColor");
const ruleLabel = compareLine.append("text")
.attr("y", height - marginBottom - 5)
.attr("x", 18)
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "middle")
// .attr("dy", "1em");
const xg = svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis)
.call(g => g.append("text")
.attr("x", width/2 - 50)
.attr("y", 28)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(Label));
const yg = svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
//.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start"));
const path = svg.append("g")
.attr("fill", "none")
.attr("stroke", typeof color === "string" ? color : null)
.attr("stroke-width", strokeWidth)
.selectAll("path")
.data(d3.group(I, i => Z[i]))
.join("path")
.style("mix-blend-mode", "multiply")
.attr("stroke", typeof color === "function" ? ([z]) => color(z) : null)
.attr("d", ([, I]) => line(I))
.attr('stroke', d => selmodel.has(d[0]) ? color(d[0]) : 'transparent');
selmodel.on('change.chart', value => {
path.attr('stroke', d => selmodel.has(d[0]) ? color(d[0]) : 'transparent');
});
const dot = svg.append("g")
.attr("display", "none");
dot.append("circle")
.attr("r", 2.5);
dot.append("text")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "middle")
.attr("x", 75)
.attr("y", -8);
function pointclicked(event) {
if (!selected.locked) {
selected.locked = selectedSample
// svg.selectAll(".shapes").remove()
// selected.comments = commentsData.get(selectedSample)
// if (selected.comments) {
// svg.selectAll(".shapes")
// .data(selected.comments)
// .enter()
// .append('g').attr("class", "shapes")
// .attr("transform", (d, i) => `translate(${xScale(d.X)}, ${yScale(d.Y)})`)
// .call(g => g.append('path')
// .attr("d", function(d) {return d3.symbol().type(d3.symbolTriangle).size("30")()})
// .attr("fill", "red"))
// .call(g => g.append("text")
// .attr("y", -2)
// .attr("x", 3)
// .attr("font-family", "sans-serif")
// .attr("font-size", 11)
// .text(d => `(${d.X}:${d.Y.toFixed(2)})${d.Comment}`))
// }
} else {
selected.locked = ''
// svg.selectAll(".shapes").remove()
}
}
function pointermoved(event) {
const [xm, ym] = d3.pointer(event);
const I1 = selected.locked ? I.filter(i => Z[i] === selected.locked) : I.filter(i => selmodel.has(Z[i]));
const i = d3.least(I1, i => Math.hypot(xScale(X[i]) - xm, yScale(Y[i]) - ym)); // closest point
selectedSample = Z[i]
path.style("stroke", ([z]) => Z[i] === z ? null : (selmodel.has(z) ? "#ddd" : "transparent")).filter(([z]) => Z[i] === z).raise();
dot.attr("transform", `translate(${xScale(X[i])},${yScale(Y[i])})`);
compareLine.attr("transform", `translate(${xScale(X[i])},0)`).raise();
if (T) {
dot.select("text").text(`${T[i]}:(${Y[i]})`);
ruleLabel.text(`${X[i]}`);
}
const details = groups.get(X[i]).filter(d => selmodel.has(d.Sample))
d3.select('#chartwavelength').html(X[i])
d3.select('#chartdetail').html(
details.map(item => `<div class="de"><div style="flex: 1">${item.Sample}</div><div style="text-align: right;">${item.Y.toFixed(2)}</div></div>`).join('')
)
}
function pointerentered() {
path.style("mix-blend-mode", null).style("stroke", "#ddd");
dot.attr("display", null);
}
function pointerleft() {
selected.locked = ''
path.style("mix-blend-mode", "multiply").style("stroke", null);
dot.attr("display", "none");
compareLine.attr("transform", `translate(-40,0)`)
svg.selectAll(".shapes").remove()
}
return Object.assign(svg.node(), {scales: {color, selmodel},
update(focusX, focusY) {
const [minX, maxX] = focusX
const [minY, maxY] = focusY
// svg.selectAll(".tick line").remove()
xScale.domain(focusX)
yScale.domain([minY, maxY])
xg.call(xAxis)
yg.call(yAxis)
D = d3.map(D, (d, i) => X[i] >= minX && X[i] <= maxX);
path
.attr("d", ([, I]) => line(I))
.attr('stroke', d => selmodel.has(d[0]) ? color(d[0]) : 'transparent');
const maxIndex = d3.maxIndex(data, d => minX <= d.X && d.X <= maxX ? d.Y : NaN);
const minIndex = d3.minIndex(data, d => minX <= d.X && d.X <= maxX ? d.Y : NaN);
d3.select('#rangewavelength').html(`[${minX.toFixed(2)}, ${maxX.toFixed(2)}]`)
d3.select('#rangemax').html(`${Z[maxIndex]}: ${maxY.toFixed(2)}`)
d3.select('#rangemin').html(`${Z[minIndex]}: ${minY.toFixed(2)}`)
}
});
}