diff options
| author | s-ol <s+removethis@s-ol.nu> | 2024-03-13 21:38:46 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2024-03-13 21:38:46 +0000 |
| commit | 49cc2223a419eb86610287e0ed9ff73deb95cd84 (patch) | |
| tree | 960d876a2f6b8740d28b1ae6970ee0f7899d430a | |
| parent | web: reset plot when entering manual mode (diff) | |
| download | t937-serial-49cc2223a419eb86610287e0ed9ff73deb95cd84.tar.gz t937-serial-49cc2223a419eb86610287e0ed9ff73deb95cd84.zip | |
web: editable setpoint curve
| -rw-r--r-- | web/main.js | 10 | ||||
| -rw-r--r-- | web/plot.js | 84 | ||||
| -rw-r--r-- | web/style.css | 1 |
3 files changed, 81 insertions, 14 deletions
diff --git a/web/main.js b/web/main.js index c682567..d164f70 100644 --- a/web/main.js +++ b/web/main.js @@ -33,10 +33,12 @@ let END_TIME = null; let TIMEOUT; const PLOTS = { - temp1: plot.addPlot('temp1'), - temp2: plot.addPlot('temp2'), - temp3: plot.addPlot('temp3'), - tempavg: plot.addPlot('tempavg'), + paste: plot.addPlot('paste', 'slategray', [[0, 24]], true), + setpoint: plot.addPlot('setpoint', 'teal', [[0, 24]], true), + temp1: plot.addPlot('temp1', 'plum'), + temp2: plot.addPlot('temp2', 'salmon'), + temp3: plot.addPlot('temp3', 'sienna'), + tempavg: plot.addPlot('tempavg', 'rebeccapurple'), }; const elements = [ diff --git a/web/plot.js b/web/plot.js index ae826d5..4c2320f 100644 --- a/web/plot.js +++ b/web/plot.js @@ -22,31 +22,77 @@ const sec = d3.format('02'); const min = d3.format(' '); const minsec = v => `${min(Math.floor(v / 60))}:${sec(v % 60)}`; -export const addPlot = (name) => { +export const addPlot = (name, color, data=[], interactive=false) => { const path = svg.append('path') + // .attr('cursor', 'copy') .attr('fill', 'none') - .attr('stroke', 'black') + .attr('stroke', color) .attr('stroke-width', '2'); + const points = interactive && ( + svg.append('g') + .attr('cursor', 'grab') + ); + PLOTS[name] = { - data: [], + color, + data, + points, path, }; return PLOTS[name]; }; - -export const update = () => { +export const update = (transition=true) => { const times = d3.merge(Object.values(PLOTS).map(p => p.data.map(d => d[0]))); const temps = d3.merge(Object.values(PLOTS).map(p => p.data.map(d => d[1]))); x.domain([0, d3.max([...times, 120])]).nice(30); y.domain([0, d3.max([...temps, 200])]).nice(25); - xa.transition().call(d3.axisBottom(x).tickFormat(minsec)) - ya.transition().call(d3.axisLeft(y)) - for (const { path, data } of Object.values(PLOTS)) { - path.transition().attr('d', line(data)); + const dur = transition ? null : 0; + + xa.transition().duration(dur).call(d3.axisBottom(x).tickFormat(minsec)) + ya.transition().duration(dur).call(d3.axisLeft(y)) + for (const { path, points, data, color } of Object.values(PLOTS)) { + path.transition().duration(dur).attr('d', line(data)); + points && points.selectAll('circle') + .data(data) + .join('circle') + .attr('fill', color) + .attr('r', 5) + .attr('cx', (d) => x(d[0])) + .attr('cy', (d) => y(d[1])) + .on('dblclick', function (e, d) { + e.stopPropagation(); + + const i = data.indexOf(d); + data.splice(i, 1); + update(false); + }) + .call( + d3.drag() + .on('start', () => points.attr('cursor', 'grabbing')) + .on('drag', function (e, d) { + const [time, temp] = [x.invert(e.x), y.invert(e.y)]; + + const ci = data.indexOf(d); + if (data[ci-1] && data[ci-1][0] > time) { + data.splice(ci-1, 0, ...data.splice(ci, 1)); + } else if (data[ci+1] && data[ci+1][0] < time) { + data.splice(ci+1, 0, ...data.splice(ci, 1)); + } + + d[0] = time; + d[1] = temp; + + d3.select(this) + .attr('cx', (d) => x(d[0])) + .attr('cy', (d) => y(d[1])); + update(false); + }) + .on('end', () => points.attr('cursor', 'grab')) + ); } }; @@ -61,8 +107,11 @@ const onresize = () => { y.range([height - margin, margin]); xa.transition().call(d3.axisBottom(x).tickFormat(minsec)) ya.transition().call(d3.axisLeft(y)) - for (const { path, data } of Object.values(PLOTS)) { + for (const { path, data, points } of Object.values(PLOTS)) { path.transition().attr('d', line(data)); + points && points.transition().selectAll('circle') + .attr('cx', (d) => x(d[0])) + .attr('cy', (d) => y(d[1])); } xa.attr('transform', `translate(0, ${height - margin})`); @@ -70,5 +119,20 @@ const onresize = () => { }; window.addEventListener('resize', onresize); +svg.on('dblclick', (e) => { + const [px, py] = d3.pointer(e); + const [time, temp] = [x.invert(px), y.invert(py)]; + + const current = PLOTS.setpoint; + + const ni = current.data.findLastIndex(([tt, tp]) => tt <= time); + if (current.data[ni][0] === time) { + current.data[ni][1] = temp; + } else { + current.data.splice(ni + 1, 0, [time, temp]); + } + update(false); +}); + onresize(); update(); diff --git a/web/style.css b/web/style.css index 8bd7380..a397fce 100644 --- a/web/style.css +++ b/web/style.css @@ -50,4 +50,5 @@ aside div label + label { #plot { flex: 1 0 400px; + user-select: none; } |
