<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>PeakFinder SVG Tool</title>
<style>
body {
font-family: sans-serif;
margin: 20px;
max-width: 1200px;
margin: 0 auto;
}
#controls {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 5px;
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
}
#svg-container {
border: 1px solid #ccc;
margin-top: 20px;
overflow: auto;
max-height: 80vh;
transform-origin: top left;
}
.hidden {
opacity: 0.3;
pointer-events: auto;
}
.hidden text,
.hidden line {
fill: grey !important;
stroke: grey !important;
}
.hovered {
fill: red !important;
stroke: red !important;
}
text:hover,
line:hover {
cursor: pointer;
}
.hidden:hover {
opacity: 1;
z-index: 10;
}
#status {
margin-top: 10px;
font-weight: bold;
}
</style>
</head>
<body>
<h1>PeakFinder SVG Tool</h1>
<div id="controls">
<div class="control-group">
<h3>SVG Upload</h3>
<input type="file" id="fileInput" accept=".svg" />
<label for="zoomLevel">Zoom Level:</label>
<input type="range" id="zoomLevel" min="1" max="5" step="0.1" value="2" />
</div>
<div class="control-group">
<h3>Element Visibility</h3>
<div>
<label>
<input type="checkbox" id="toggleLogo" checked> Logo
</label>
<label>
<input type="checkbox" id="toggleViewpoint" checked> Viewpoint
</label>
<label>
<input type="checkbox" id="toggleRuler" checked> Ruler
</label>
<label>
<input type="checkbox" id="toggleBackground" checked> Background
</label>
</div>
</div>
<div class="control-group">
<h3>Line & Label Styling</h3>
<label>Label Stroke Width (pt):
<input type="number" id="labelStrokeWidth" value="1" min="0.1" step="0.1">
</label>
<label>Line Stroke Width (pt):
<input type="number" id="lineStrokeWidth" value="1" min="0.1" step="0.1">
</label>
</div>
<div class="control-group">
<h3>Export</h3>
<button id="exportSVG">Download SVG</button>
</div>
</div>
<div id="status"></div>
<div id="svg-container"></div>
<script>
(function () {
const fileInput = document.getElementById("fileInput");
const svgContainer = document.getElementById("svg-container");
const statusDiv = document.getElementById("status");
const zoomLevelInput = document.getElementById("zoomLevel");
const exportButton = document.getElementById("exportSVG");
// Visibility toggles
const logoToggle = document.getElementById("toggleLogo");
const viewpointToggle = document.getElementById("toggleViewpoint");
const rulerToggle = document.getElementById("toggleRuler");
const backgroundToggle = document.getElementById("toggleBackground");
// Stroke width inputs
const labelStrokeWidthInput = document.getElementById("labelStrokeWidth");
const lineStrokeWidthInput = document.getElementById("lineStrokeWidth");
let prominentTextsGroup, prominentLinesGroup, allHiddenTextsGroup, allHiddenLinesGroup, currentSvg;
fileInput.addEventListener("change", handleFileUpload);
zoomLevelInput.addEventListener("input", handleZoom);
exportButton.addEventListener("click", exportSVG);
// Visibility toggles
logoToggle.addEventListener("change", updateElementVisibility);
viewpointToggle.addEventListener("change", updateElementVisibility);
rulerToggle.addEventListener("change", updateElementVisibility);
backgroundToggle.addEventListener("change", updateElementVisibility);
// Stroke width updates
labelStrokeWidthInput.addEventListener("input", updateStrokeWidth);
lineStrokeWidthInput.addEventListener("input", updateStrokeWidth);
function handleZoom() {
const zoomLevel = zoomLevelInput.value;
svgContainer.style.transform = `scale(${zoomLevel})`;
}
async function handleFileUpload() {
if (!fileInput.files || !fileInput.files.length) {
updateStatus("Keine Datei ausgewählt.", "error");
return;
}
const file = fileInput.files[0];
if (!file.name.toLowerCase().endsWith(".svg")) {
updateStatus("Bitte eine SVG-Datei auswählen.", "error");
return;
}
const text = await file.text().catch((err) => {
updateStatus("Fehler beim Lesen der Datei: " + err, "error");
});
if (!text) return;
const parser = new DOMParser();
const doc = parser.parseFromString(text, "image/svg+xml");
currentSvg = doc.documentElement;
if (currentSvg.nodeName.toLowerCase() !== "svg") {
updateStatus("Die ausgewählte Datei ist kein gültiges SVG.", "error");
return;
}
svgContainer.innerHTML = "";
svgContainer.appendChild(currentSvg);
initStructure(currentSvg);
if (!setupEventHandlers()) {
updateStatus("Konnte die benötigte Struktur nicht finden.", "error");
} else {
updateStatus("SVG erfolgreich geladen.", "success");
updateElementVisibility();
updateStrokeWidth();
}
}
function initStructure(svgRoot) {
const labelsGroup = svgRoot.querySelector('g#labels, g > g[label="labels"], g[label=labels]');
const allHidden = labelsGroup?.querySelector("g#all_hidden, g[id=all_hidden]");
const prominent = labelsGroup?.querySelector("g#prominent, g[id=prominent]");
allHiddenTextsGroup = allHidden?.querySelector("g#texts, g[id=texts]");
allHiddenLinesGroup = allHidden?.querySelector("g#lines, g[id=lines]");
prominentTextsGroup = prominent?.querySelector("g#texts, g[id=texts]");
prominentLinesGroup = prominent?.querySelector("g#lines, g[id=lines]");
const allHiddenTexts = Array.from(allHidden?.querySelectorAll("text") || []);
const allHiddenLines = Array.from(allHidden?.querySelectorAll("line, path") || []);
const prominentTexts = Array.from(prominentTextsGroup?.querySelectorAll("text") || []);
const prominentLines = Array.from(prominentLinesGroup?.querySelectorAll("line, path") || []);
function isTextInProminent(text) {
return prominentTexts.some((promText) => promText.textContent.trim() === text.textContent.trim());
}
function isLineInProminent(line) {
return prominentLines.some((promLine) => promLine.isEqualNode(line));
}
console.log("Initializing structure...");
console.log(`Found ${allHiddenTexts.length} hidden texts.`);
console.log(`Found ${allHiddenLines.length} hidden lines.`);
console.log(`Found ${prominentTexts.length} prominent texts.`);
console.log(`Found ${prominentLines.length} prominent lines.`);
allHiddenTexts.forEach((text) => {
if (!isTextInProminent(text)) {
console.log(`Moving hidden text: ${text.textContent.trim()} to prominent.`);
prominentTextsGroup.appendChild(text);
text.classList.add("hidden");
} else {
console.log(`Duplicate text detected and removed: ${text.textContent.trim()}`);
allHiddenTextsGroup.removeChild(text);
}
});
allHiddenLines.forEach((line, index) => {
if (!isLineInProminent(line)) {
console.log(`Moving hidden line at index: ${index} to prominent.`);
prominentLinesGroup.appendChild(line);
line.classList.add("hidden");
} else {
console.log(`Duplicate line detected and removed at index: ${index}`);
allHiddenLinesGroup.removeChild(line);
}
});
prominentTexts.forEach((text) => {
console.log(`Ensuring visibility for prominent text: ${text.textContent.trim()}`);
text.classList.remove("hidden");
});
prominentLines.forEach((line, index) => {
console.log(`Ensuring visibility for prominent line at index: ${index}`);
line.classList.remove("hidden");
});
}
function setupEventHandlers() {
if (!prominentTextsGroup || !prominentLinesGroup) return false;
const prominentTexts = Array.from(prominentTextsGroup.children);
const prominentLines = Array.from(prominentLinesGroup.children);
prominentTexts.forEach((text, index) => {
const correspondingLine = prominentLines[index];
text.addEventListener("mouseenter", () => handleHoverEnter(text, correspondingLine));
text.addEventListener("mouseleave", () => handleHoverLeave(text, correspondingLine));
text.addEventListener("click", (event) => handleClick(event, text, correspondingLine));
});
prominentLines.forEach((line, index) => {
const correspondingText = prominentTexts[index];
line.addEventListener("mouseenter", () => handleHoverEnter(correspondingText, line));
line.addEventListener("mouseleave", () => handleHoverLeave(correspondingText, line));
line.addEventListener("click", (event) => handleClick(event, correspondingText, line));
});
return true;
}
function handleHoverEnter(textEl, lineEl) {
textEl.classList.add("hovered");
lineEl.classList.add("hovered");
}
function handleHoverLeave(textEl, lineEl) {
textEl.classList.remove("hovered");
lineEl.classList.remove("hovered");
}
function handleClick(event, textEl, lineEl) {
if (event.shiftKey) {
console.log(`Removing text: ${textEl.textContent.trim()} and its line.`);
moveToHidden(textEl, lineEl);
} else {
console.log(`Toggling visibility for text: ${textEl.textContent.trim()} and its line.`);
toggleVisibility(textEl, lineEl);
}
}
function toggleVisibility(textEl, lineEl) {
if (textEl.classList.contains("hidden")) {
console.log(`Making visible: ${textEl.textContent.trim()}`);
textEl.classList.remove("hidden");
lineEl.classList.remove("hidden");
} else {
console.log(`Hiding: ${textEl.textContent.trim()}`);
textEl.classList.add("hidden");
lineEl.classList.add("hidden");
}
}
function moveToHidden(textEl, lineEl) {
console.log(`Moving text: ${textEl.textContent.trim()} and its line to all_hidden.`);
allHiddenTextsGroup.appendChild(textEl);
allHiddenLinesGroup.appendChild(lineEl);
textEl.classList.add("hidden");
lineEl.classList.add("hidden");
}
function updateElementVisibility() {
if (!currentSvg) return;
const logoElements = currentSvg.querySelectorAll('[id*="logo"]');
const viewpointElements = currentSvg.querySelectorAll('[id*="viewpoint"]');
const rulerElements = currentSvg.querySelectorAll('[id*="ruler"]');
const backgroundElements = currentSvg.querySelectorAll('[id*="background"]');
logoElements.forEach(el => el.style.display = logoToggle.checked ? '' : 'none');
viewpointElements.forEach(el => el.style.display = viewpointToggle.checked ? '' : 'none');
rulerElements.forEach(el => el.style.display = rulerToggle.checked ? '' : 'none');
backgroundElements.forEach(el => el.style.display = backgroundToggle.checked ? '' : 'none');
}
function updateStrokeWidth() {
if (!currentSvg) return;
const labelStrokeWidth = labelStrokeWidthInput.value + 'pt';
const lineStrokeWidth = lineStrokeWidthInput.value + 'pt';
const labels = currentSvg.querySelectorAll('text');
labels.forEach(label => {
label.style.strokeWidth = labelStrokeWidth;
});
const lines = currentSvg.querySelectorAll('line, path');
lines.forEach(line => {
line.style.strokeWidth = lineStrokeWidth;
});
}
function exportSVG() {
if (!currentSvg) {
updateStatus("Bitte laden Sie zuerst ein SVG.", "error");
return;
}
// Clone the current SVG to avoid modifying the original
const svgClone = currentSvg.cloneNode(true);
// Remove all elements with the 'hidden' class
svgClone.querySelectorAll('.hidden').forEach(el => el.remove());
// Remove all elements that are hidden via 'display: none'
svgClone.querySelectorAll('*').forEach(el => {
const style = el.getAttribute('style');
if (style && style.includes('display: none')) {
el.remove();
}
});
// Serialize the cleaned SVG
const serializer = new XMLSerializer();
let svgString = serializer.serializeToString(svgClone);
// Add XML namespaces if missing
if (!svgString.includes('xmlns="http://www.w3.org/2000/svg"')) {
svgString = svgString.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
}
// Create a Blob from the SVG string
const blob = new Blob([svgString], {type: 'image/svg+xml'});
const url = URL.createObjectURL(blob);
// Create a temporary link to trigger the download
const a = document.createElement('a');
a.href = url;
a.download = 'peakfinder_export.svg';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Revoke the object URL to free up memory
URL.revokeObjectURL(url);
updateStatus("SVG erfolgreich exportiert.", "success");
}
function updateStatus(message, type) {
statusDiv.textContent = message;
statusDiv.style.color = type === "error" ? "red" : "green";
}
})();
</script>
</body>
</html>