From 9b26bcb1b84943a42b0a8663a6b099126e3e2ff6 Mon Sep 17 00:00:00 2001 From: wonder Date: Sun, 21 Jun 2026 14:43:16 +0800 Subject: [PATCH] feat: move git graph legend to top-right corner to avoid overlap with graph --- static/js/graph.js | 79 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/static/js/graph.js b/static/js/graph.js index 21e8427..e784194 100644 --- a/static/js/graph.js +++ b/static/js/graph.js @@ -129,6 +129,10 @@ const GitGraph = { // Draw branch labels on the right side this._drawBranchLabels(g, nodeMap, this._laneBranchMap, laneWidth, maxLane); + // Draw legend in top-right corner + const svgWidth = Math.max(width, graphWidth); + this._drawLegend(svg, this._laneBranchMap, svgWidth); + // Store for updates this._nodeMap = nodeMap; @@ -429,6 +433,81 @@ const GitGraph = { }); }, + // Draw legend in top-right corner of SVG + _drawLegend(svg, laneBranchMap, svgWidth) { + const legendPadding = 12; + const itemHeight = 20; + const circleRadius = 5; + const fontSize = 11; + + // Collect unique branches + const branches = []; + const seen = new Set(); + Object.values(laneBranchMap).forEach(info => { + if (!seen.has(info.name)) { + seen.add(info.name); + branches.push(info); + } + }); + + if (branches.length === 0) return; + + // Create legend group + const legend = svg.append('g') + .attr('class', 'graph-legend'); + + // Calculate legend dimensions + const tempText = svg.append('text') + .attr('font-size', `${fontSize}px`) + .attr('font-family', 'system-ui, -apple-system, sans-serif'); + + let maxTextWidth = 0; + branches.forEach(b => { + tempText.text(b.name); + maxTextWidth = Math.max(maxTextWidth, tempText.node().getBBox().width); + }); + tempText.remove(); + + const legendWidth = circleRadius * 2 + 8 + maxTextWidth + legendPadding * 2; + const legendHeight = branches.length * itemHeight + legendPadding * 2; + + // Position in top-right + const legendX = svgWidth - legendWidth - 10; + const legendY = 10; + + legend.attr('transform', `translate(${legendX},${legendY})`); + + // Background + legend.append('rect') + .attr('width', legendWidth) + .attr('height', legendHeight) + .attr('rx', 6) + .attr('ry', 6) + .attr('fill', 'white') + .attr('stroke', '#e5e7eb') + .attr('stroke-width', 1) + .attr('opacity', 0.95); + + // Legend items + branches.forEach((branch, i) => { + const itemY = legendPadding + i * itemHeight + itemHeight / 2; + + legend.append('circle') + .attr('cx', legendPadding + circleRadius) + .attr('cy', itemY) + .attr('r', circleRadius) + .attr('fill', branch.color); + + legend.append('text') + .attr('x', legendPadding + circleRadius * 2 + 8) + .attr('y', itemY + 4) + .text(branch.name) + .attr('font-size', `${fontSize}px`) + .attr('fill', '#374151') + .attr('font-family', 'system-ui, -apple-system, sans-serif'); + }); + }, + _updateSelectionVisuals() { if (!this._nodes || !this._lanes) return;