Skip to content

Commit b5c4a47

Browse files
committed
Add linestyle, alpha, and marker options to Plot1D: enhance line customization
1 parent 691c3a6 commit b5c4a47

10 files changed

Lines changed: 759 additions & 63 deletions

anyplotlib/figure_esm.js

Lines changed: 124 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,22 +1338,115 @@ function render({ model, el }) {
13381338
// Clip
13391339
ctx.save(); ctx.beginPath(); ctx.rect(r.x,r.y,r.w,r.h); ctx.clip();
13401340

1341-
function _drawLine(yData,lineXArr,color,lw){
1342-
if(!yData||!yData.length) return;
1343-
const n=yData.length;
1344-
ctx.beginPath();ctx.strokeStyle=color;ctx.lineWidth=lw;ctx.lineJoin='round';
1345-
let first=true;
1346-
for(let i=0;i<n;i++){
1347-
const xFrac=lineXArr.length>=2?(lineXArr[i]-lineXArr[0])/((lineXArr[lineXArr.length-1]-lineXArr[0])||1):i/((n-1)||1);
1348-
const px=_fracToPx1d(xFrac,x0,x1,r), py=_valToPy1d(yData[i],dMin,dMax,r);
1349-
if(first){ctx.moveTo(px,py);first=false;}else{ctx.lineTo(px,py);}
1341+
// Linestyle → canvas dash pattern
1342+
const _LINESTYLE_DASH = {
1343+
'solid': [],
1344+
'dashed': [6, 3],
1345+
'dotted': [2, 3],
1346+
'dashdot': [6, 3, 2, 3],
1347+
};
1348+
1349+
// Draw a single marker symbol centred at (px, py) with half-size ms.
1350+
// The caller is responsible for beginPath() before and fill()/stroke() after.
1351+
function _drawMarkerSymbol(mctx, marker, px, py, ms) {
1352+
switch (marker) {
1353+
case 'o':
1354+
mctx.arc(px, py, ms, 0, Math.PI * 2);
1355+
break;
1356+
case 's':
1357+
mctx.rect(px - ms, py - ms, ms * 2, ms * 2);
1358+
break;
1359+
case '^':
1360+
mctx.moveTo(px, py - ms);
1361+
mctx.lineTo(px + ms, py + ms);
1362+
mctx.lineTo(px - ms, py + ms);
1363+
mctx.closePath();
1364+
break;
1365+
case 'v':
1366+
mctx.moveTo(px, py + ms);
1367+
mctx.lineTo(px + ms, py - ms);
1368+
mctx.lineTo(px - ms, py - ms);
1369+
mctx.closePath();
1370+
break;
1371+
case 'D':
1372+
mctx.moveTo(px, py - ms);
1373+
mctx.lineTo(px + ms, py);
1374+
mctx.lineTo(px, py + ms);
1375+
mctx.lineTo(px - ms, py);
1376+
mctx.closePath();
1377+
break;
1378+
case '+':
1379+
mctx.moveTo(px - ms, py); mctx.lineTo(px + ms, py);
1380+
mctx.moveTo(px, py - ms); mctx.lineTo(px, py + ms);
1381+
break;
1382+
case 'x':
1383+
mctx.moveTo(px - ms, py - ms); mctx.lineTo(px + ms, py + ms);
1384+
mctx.moveTo(px + ms, py - ms); mctx.lineTo(px - ms, py + ms);
1385+
break;
1386+
default:
1387+
mctx.arc(px, py, ms, 0, Math.PI * 2);
1388+
}
1389+
}
1390+
1391+
// Stroke-only markers (no meaningful fill area)
1392+
const _MARKER_STROKE_ONLY = new Set(['+', 'x']);
1393+
1394+
function _drawLine(yData, lineXArr, color, lw, linestyle, alpha, marker, markersize) {
1395+
if (!yData || !yData.length) return;
1396+
const n = yData.length;
1397+
const dash = _LINESTYLE_DASH[linestyle || 'solid'] || [];
1398+
const eff_alpha = (alpha != null && alpha < 1.0) ? alpha : 1.0;
1399+
const ms = Math.max(1, markersize || 4);
1400+
const doMarker = marker && marker !== 'none';
1401+
1402+
ctx.save();
1403+
if (eff_alpha < 1.0) ctx.globalAlpha = eff_alpha;
1404+
ctx.setLineDash(dash);
1405+
ctx.beginPath();
1406+
ctx.strokeStyle = color; ctx.lineWidth = lw; ctx.lineJoin = 'round';
1407+
1408+
const pts = doMarker ? [] : null;
1409+
let first = true;
1410+
for (let i = 0; i < n; i++) {
1411+
const xFrac = lineXArr.length >= 2
1412+
? (lineXArr[i] - lineXArr[0]) / ((lineXArr[lineXArr.length - 1] - lineXArr[0]) || 1)
1413+
: i / ((n - 1) || 1);
1414+
const px = _fracToPx1d(xFrac, x0, x1, r);
1415+
const py = _valToPy1d(yData[i], dMin, dMax, r);
1416+
if (first) { ctx.moveTo(px, py); first = false; } else { ctx.lineTo(px, py); }
1417+
if (pts) pts.push([px, py]);
13501418
}
13511419
ctx.stroke();
1420+
ctx.setLineDash([]);
1421+
1422+
// Per-point marker symbols
1423+
if (doMarker && pts && pts.length) {
1424+
ctx.strokeStyle = color;
1425+
ctx.fillStyle = color;
1426+
ctx.lineWidth = Math.max(1, lw * 0.8);
1427+
const strokeOnly = _MARKER_STROKE_ONLY.has(marker);
1428+
for (const [px, py] of pts) {
1429+
ctx.beginPath();
1430+
_drawMarkerSymbol(ctx, marker, px, py, ms);
1431+
if (!strokeOnly) ctx.fill();
1432+
ctx.stroke();
1433+
}
1434+
}
1435+
1436+
ctx.restore();
13521437
}
13531438

1354-
_drawLine(st.data,xArr,st.line_color||'#4fc3f7',st.line_linewidth||1.5);
1355-
for(const ex of (st.extra_lines||[])){
1356-
_drawLine(ex.data||[],ex.x_axis||xArr,ex.color||(theme.dark?'#fff':'#333'),ex.linewidth||1.5);
1439+
_drawLine(st.data, xArr,
1440+
st.line_color || '#4fc3f7', st.line_linewidth || 1.5,
1441+
st.line_linestyle || 'solid',
1442+
st.line_alpha != null ? st.line_alpha : 1.0,
1443+
st.line_marker || 'none', st.line_markersize || 4);
1444+
for (const ex of (st.extra_lines || [])) {
1445+
_drawLine(ex.data || [], ex.x_axis || xArr,
1446+
ex.color || (theme.dark ? '#fff' : '#333'), ex.linewidth || 1.5,
1447+
ex.linestyle || 'solid',
1448+
ex.alpha != null ? ex.alpha : 1.0,
1449+
ex.marker || 'none', ex.markersize || 4);
13571450
}
13581451
ctx.restore();
13591452

@@ -1400,12 +1493,28 @@ function render({ model, el }) {
14001493

14011494
// Legend
14021495
const labels=[];
1403-
if(st.line_label) labels.push({color:st.line_color||'#4fc3f7',text:st.line_label});
1404-
for(const ex of (st.extra_lines||[])) if(ex.label) labels.push({color:ex.color||(theme.dark?'#fff':'#333'),text:ex.label});
1496+
if(st.line_label) labels.push({
1497+
color: st.line_color||'#4fc3f7', text: st.line_label,
1498+
linestyle: st.line_linestyle||'solid',
1499+
marker: st.line_marker||'none', ms: st.line_markersize||4,
1500+
});
1501+
for(const ex of (st.extra_lines||[])) if(ex.label) labels.push({
1502+
color: ex.color||(theme.dark?'#fff':'#333'), text: ex.label,
1503+
linestyle: ex.linestyle||'solid',
1504+
marker: ex.marker||'none', ms: ex.markersize||4,
1505+
});
14051506
if(labels.length){
14061507
ctx.font='10px monospace'; let ly=r.y+6;
14071508
for(const lb of labels){
1408-
ctx.strokeStyle=lb.color;ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(r.x+8,ly+5);ctx.lineTo(r.x+24,ly+5);ctx.stroke();
1509+
const ldash=_LINESTYLE_DASH[lb.linestyle||'solid']||[];
1510+
ctx.strokeStyle=lb.color; ctx.lineWidth=2;
1511+
ctx.setLineDash(ldash);
1512+
ctx.beginPath(); ctx.moveTo(r.x+8,ly+5); ctx.lineTo(r.x+24,ly+5); ctx.stroke();
1513+
ctx.setLineDash([]);
1514+
if(lb.marker && lb.marker!=='none'){
1515+
ctx.strokeStyle=lb.color; ctx.fillStyle=lb.color; ctx.lineWidth=1.5;
1516+
ctx.beginPath(); _drawMarkerSymbol(ctx,lb.marker,r.x+16,ly+5,Math.min(lb.ms||4,4)); ctx.fill(); ctx.stroke();
1517+
}
14091518
ctx.fillStyle=theme.tickText;ctx.textAlign='left';ctx.textBaseline='top';ctx.fillText(lb.text,r.x+28,ly);ly+=16;
14101519
}
14111520
}

0 commit comments

Comments
 (0)