|
|
@ -291,7 +291,7 @@ def simplify_project(project_data, full_data, follow_mirrors=False): # {{{ |
|
|
|
"name": project_data.get("nm", ""), |
|
|
|
"id": project_data.get("id", ""), |
|
|
|
"children": [], |
|
|
|
"description": project_data.get("no", ""), |
|
|
|
"description": project_data.get("no", "").rstrip().replace("\n$", ""), |
|
|
|
"format": project_data.get("metadata", {}).get("layoutMode", None) |
|
|
|
} |
|
|
|
children = project_data.get("ch", []) |
|
|
@ -398,8 +398,6 @@ def filter_project_all(project_data, filters, include_headers=False): # {{{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def replace(project_data, regex, replacement): # {{{ |
|
|
|
project_data["name"] = re.sub(regex, replacement, project_data["name"]) |
|
|
|
children = project_data.get("children", []) |
|
|
@ -480,7 +478,7 @@ colors2 = { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def recolor(project_data, colors): |
|
|
|
def recolor(project_data, colors): # {{{ |
|
|
|
for key, value in colors.items(): |
|
|
|
if key in project_data["name"]: |
|
|
|
if key.startswith("x"): |
|
|
@ -488,6 +486,10 @@ def recolor(project_data, colors): |
|
|
|
else: |
|
|
|
project_data["name"] = f"[{value}]{project_data['name'].strip()}[/]" |
|
|
|
break |
|
|
|
# if project_data["format"] == "h1": |
|
|
|
# project_data["name"] = f"[base3][underline]{project_data['name']}[/]" |
|
|
|
# if project_data["format"] == "h2": |
|
|
|
# project_data["name"] = f"[base1][underline]{project_data['name']}[/]" |
|
|
|
children = project_data.get("children", []) |
|
|
|
for child in children: |
|
|
|
recolor(child, colors) |
|
|
@ -497,28 +499,210 @@ def recolor(project_data, colors): |
|
|
|
|
|
|
|
def print_pretty(data, indent=0, color="grey", show_description=True, show_id=False): # {{{ |
|
|
|
try: |
|
|
|
# href = f"https://workflowy.com/#/{data['id'].split('-')[4]}" |
|
|
|
for item in data["children"]: |
|
|
|
|
|
|
|
if item["format"] == "h1": |
|
|
|
console.print("") |
|
|
|
console.print(" " * indent + f"[base3]•[/] [base3][underline]{item['name']}[/][/][base01]{' ' + item['id'].split('-')[4] if show_id else ''}[/]") |
|
|
|
elif item["format"] == "h2": |
|
|
|
console.print(" " * indent + f"[base3]•[/] [base1][underline]{item['name']}[/][/][base01]{' ' + item['id'].split('-')[4] if show_id else ''}[/]") |
|
|
|
|
|
|
|
else: |
|
|
|
console.print(" " * indent + f"[base3]•[/] [{color}]{item['name']}[/][base01]{' ' + item['id'].split('-')[4] if show_id else ''}[/]") |
|
|
|
|
|
|
|
|
|
|
|
console.print(" " * indent + f"[base3]•[/] [{color}]{item['name']}[/][base01]{' ' + item['id'].split('-')[4] if show_id else ''}[/]") |
|
|
|
if item["description"] and show_description: |
|
|
|
console.print(" " * (indent + 1) + f"[base01]{item['description'].replace('\n', '\n' + ' ' * (indent + 1))}[/]") |
|
|
|
|
|
|
|
if item["children"]: |
|
|
|
print_pretty(item, indent + 1, color, show_description=show_description, show_id=show_id) |
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
console.log(f"Error: {e}") |
|
|
|
|
|
|
|
|
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
|
def generate_d3_mindmap_html(data, show_description=True, show_id=False): # {{{ |
|
|
|
import json |
|
|
|
import re |
|
|
|
|
|
|
|
# Map of Rich tag colors to hex values or CSS color names |
|
|
|
color_map = { |
|
|
|
"base3": "#073642", |
|
|
|
"base2": "#002b36", |
|
|
|
"base1": "#586e75", |
|
|
|
"base0": "#657b83", |
|
|
|
"base00": "#839496", |
|
|
|
"base01": "#93a1a1", |
|
|
|
"base02": "#eee8d5", |
|
|
|
"base03": "#fdf6e3", |
|
|
|
"yellow": "#b58900", |
|
|
|
"orange": "#cb4b16", |
|
|
|
"red": "#dc322f", |
|
|
|
"magenta": "#d33682", |
|
|
|
"violet": "#6c71c4", |
|
|
|
"blue": "#268bd2", |
|
|
|
"cyan": "#2aa198", |
|
|
|
"green": "#859900", |
|
|
|
} |
|
|
|
|
|
|
|
def parse_rich_tags(text): |
|
|
|
text = re.sub(r"\[(underline)? ?([a-zA-Z0-9]+)?\](.*?)\[/\]", |
|
|
|
lambda m: f'<tspan style="{"text-decoration: underline;" if m.group(1) else ""}' |
|
|
|
f'{"fill: " + color_map[m.group(2)] + ";" if m.group(2) else ""}">{m.group(3)}</tspan>', |
|
|
|
text) |
|
|
|
return text |
|
|
|
|
|
|
|
def remove_rich_tags(text): |
|
|
|
text = re.sub(r"\[(underline)? ?([a-zA-Z0-9]+)?\](.*?)\[/\]", r"\3", text) |
|
|
|
return text |
|
|
|
|
|
|
|
# Recursively transform data to add the parsed Rich-style text |
|
|
|
def transform_data(item): |
|
|
|
node = { |
|
|
|
"name": remove_rich_tags(parse_rich_tags(item["name"])), # Parse Rich tags in name |
|
|
|
"id": item["id"].split("-")[4] if show_id else "", |
|
|
|
"description": parse_rich_tags(item["description"]) if show_description else "" |
|
|
|
} |
|
|
|
if item["children"]: |
|
|
|
node["children"] = [transform_data(child) for child in item["children"]] |
|
|
|
return node |
|
|
|
|
|
|
|
transformed_data = transform_data(data) |
|
|
|
|
|
|
|
# Create the HTML content with D3.js code |
|
|
|
html_content = f""" |
|
|
|
<!DOCTYPE html> |
|
|
|
<html lang="en"> |
|
|
|
<head> |
|
|
|
<meta charset="UTF-8"> |
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
|
<title>Mind Map</title> |
|
|
|
<script src="https://d3js.org/d3.v6.min.js"></script> |
|
|
|
<style> |
|
|
|
body {{ |
|
|
|
font-family: Arial, sans-serif; |
|
|
|
background-color: #073642; |
|
|
|
color: #fdf6e3; |
|
|
|
}} |
|
|
|
.node {{ |
|
|
|
cursor: pointer; |
|
|
|
}} |
|
|
|
.node circle {{ |
|
|
|
fill: #586e75; |
|
|
|
stroke: #586e75; |
|
|
|
stroke-width: 3px; |
|
|
|
}} |
|
|
|
.node text {{ |
|
|
|
# color: #fdf6e3 !important; |
|
|
|
fill: #fdf6e3; |
|
|
|
font: 12px sans-serif; |
|
|
|
}} |
|
|
|
.link {{ |
|
|
|
fill: none; |
|
|
|
stroke: #586e75; |
|
|
|
stroke-width: 2px; |
|
|
|
}} |
|
|
|
</style> |
|
|
|
</head> |
|
|
|
<body> |
|
|
|
<svg width="100vw" height="100vh"></svg> |
|
|
|
|
|
|
|
<script> |
|
|
|
var data = {json.dumps(transformed_data)}; |
|
|
|
|
|
|
|
var svg = d3.select("svg") |
|
|
|
.attr("width", "100vw") |
|
|
|
.attr("height", "100vh"); |
|
|
|
|
|
|
|
var zoom = d3.zoom() |
|
|
|
.scaleExtent([0.5, 5]) |
|
|
|
.on("zoom", function(event) {{ |
|
|
|
g.attr("transform", "translate(" + event.transform.x + "," + event.transform.y + ") scale(" + event.transform.k + ") rotate(" + currentRotation + ")"); |
|
|
|
currentTransform = event.transform; |
|
|
|
}}); |
|
|
|
|
|
|
|
svg.call(zoom); |
|
|
|
|
|
|
|
var g = svg.append("g"); |
|
|
|
|
|
|
|
// .attr("transform", "translate(" + window.innerWidth / 2 + "," + window.innerHeight / 2 + ")"); |
|
|
|
|
|
|
|
var currentRotation = 0; |
|
|
|
var currentTransform = d3.zoomIdentity; |
|
|
|
var isRotating = false; |
|
|
|
var lastX = 0; |
|
|
|
|
|
|
|
document.addEventListener("keydown", function(event) {{ |
|
|
|
if (event.key === "r" || event.key === "R") {{ |
|
|
|
isRotating = !isRotating; |
|
|
|
}} |
|
|
|
}}); |
|
|
|
|
|
|
|
svg.on("mousedown", function(event) {{ |
|
|
|
if (isRotating) {{ |
|
|
|
lastX = event.clientX; |
|
|
|
}} |
|
|
|
}}); |
|
|
|
|
|
|
|
svg.on("mousemove", function(event) {{ |
|
|
|
if (isRotating) {{ |
|
|
|
var dx = event.clientX - lastX; |
|
|
|
currentRotation += dx * 0.1; |
|
|
|
g.attr("transform", "translate(" + currentTransform.x + "," + currentTransform.y + ") scale(" + currentTransform.k + ") rotate(" + currentRotation + ")"); |
|
|
|
lastX = event.clientX; |
|
|
|
}} |
|
|
|
}}); |
|
|
|
|
|
|
|
svg.on("mouseup", function() {{ |
|
|
|
isRotating = false; |
|
|
|
}}); |
|
|
|
|
|
|
|
var tree = d3.tree() |
|
|
|
.size([360, Math.min(window.innerWidth, window.innerHeight) / 2 - 100]) |
|
|
|
.separation(function(a, b) {{ return (a.parent === b.parent ? 1 : 2) / a.depth; }}); |
|
|
|
|
|
|
|
var root = d3.hierarchy(data); |
|
|
|
|
|
|
|
tree(root); |
|
|
|
|
|
|
|
var link = g.selectAll(".link") |
|
|
|
.data(root.links()) |
|
|
|
.enter().append("path") |
|
|
|
.attr("class", "link") |
|
|
|
.attr("d", d3.linkRadial() |
|
|
|
.angle(function(d) {{ return d.x / 180 * Math.PI; }}) |
|
|
|
.radius(function(d) {{ return d.y; }})); |
|
|
|
|
|
|
|
var node = g.selectAll(".node") |
|
|
|
.data(root.descendants()) |
|
|
|
.enter().append("g") |
|
|
|
.attr("class", "node") |
|
|
|
.attr("transform", function(d) {{ return "translate(" + radialPoint(d.x, d.y) + ")"; }}); |
|
|
|
|
|
|
|
node.append("circle") |
|
|
|
.attr("r", 4.5); |
|
|
|
|
|
|
|
node.append("text") |
|
|
|
.attr("dy", ".31em") |
|
|
|
.attr("x", 10) |
|
|
|
.attr("text-anchor", "start") |
|
|
|
.attr("transform", function(d) {{ |
|
|
|
return "rotate(" + (d.x - 90) + ") translate(10, 0)"; |
|
|
|
}}) |
|
|
|
.html(function(d) {{ |
|
|
|
return d.data.name; |
|
|
|
}}); |
|
|
|
|
|
|
|
function radialPoint(x, y) {{ |
|
|
|
return [(y = +y) * Math.cos((x - 90) / 180 * Math.PI), y * Math.sin((x - 90) / 180 * Math.PI)]; |
|
|
|
}} |
|
|
|
</script> |
|
|
|
</body> |
|
|
|
</html> |
|
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Write the HTML content to a file |
|
|
|
with open("d3_mindmap.html", "w") as f: |
|
|
|
f.write(html_content) |
|
|
|
|
|
|
|
print("D3.js mind map HTML generated as 'd3_mindmap.html'.") |
|
|
|
|
|
|
|
|
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
@ -561,6 +745,7 @@ def show(parent_id, flat=False, filters_all=None, filters_any=None, color="grey" |
|
|
|
console.print("") |
|
|
|
print_pretty(project_data, color=color, show_description=show_description, show_id=show_id) |
|
|
|
console.print("") |
|
|
|
generate_d3_mindmap_html(project_data, show_description=show_description, show_id=show_id) |
|
|
|
return True |
|
|
|
|
|
|
|
# }}} |
|
|
|