diff --git a/README.md b/README.md index 9f908c5..dec92c9 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ How to use Load `d3.js` then load `roadmap.js`. That's it! + +Use the roadmap-editor.html to generate the code snippets + Example ------- diff --git a/roadmap-editor.html b/roadmap-editor.html new file mode 100644 index 0000000..0816791 --- /dev/null +++ b/roadmap-editor.html @@ -0,0 +1,519 @@ + + + + + + Roadmap.js Editor + + + + + +
+
+ +
+
+ + +
+ + + +
+
+ + + +
+
+ + +
+
+
+
+
+
+
+
+
+ + + +
+ + + + + + diff --git a/roadmap.js b/roadmap.js index f152974..aa748af 100644 --- a/roadmap.js +++ b/roadmap.js @@ -1,12 +1,110 @@ (function() { + // ========================================================================= + // THEMES - Roadmap color schemes + // ========================================================================= + var themes = { + default: { + background: '#ffffff', + text: '#000000', + textMuted: '#666666', + grid: '#dddddd', + groupBg: '#999999', + now: 'red', + colors: null // Use d3.scale.category20() + }, + light: { + background: '#ffffff', + text: '#1f2937', + textMuted: '#6b7280', + grid: '#e5e7eb', + groupBg: '#9ca3af', + now: '#ef4444', + colors: ['#6366f1', '#8b5cf6', '#ec4899', '#ef4444', '#f59e0b', '#10b981', '#06b6d4', '#3b82f6'] + }, + dark: { + background: '#1e1e2e', + text: '#cdd6f4', + textMuted: '#a6adc8', + grid: '#45475a', + groupBg: '#585b70', + now: '#f38ba8', + colors: ['#89b4fa', '#cba6f7', '#f5c2e7', '#f38ba8', '#fab387', '#a6e3a1', '#94e2d5', '#89dceb'] + }, + colorful: { + background: '#fffbeb', + text: '#1c1917', + textMuted: '#78716c', + grid: '#fde68a', + groupBg: '#d97706', + now: '#dc2626', + colors: ['#f43f5e', '#d946ef', '#8b5cf6', '#0ea5e9', '#14b8a6', '#84cc16', '#f97316', '#ec4899'] + }, + minimal: { + background: '#fafafa', + text: '#171717', + textMuted: '#737373', + grid: '#e5e5e5', + groupBg: '#a3a3a3', + now: '#171717', + colors: ['#404040', '#525252', '#737373', '#262626', '#171717', '#525252', '#404040', '#737373'] + }, + corporate: { + background: '#f8fafc', + text: '#0f172a', + textMuted: '#64748b', + grid: '#e2e8f0', + groupBg: '#94a3b8', + now: '#0369a1', + colors: ['#0284c7', '#0891b2', '#059669', '#65a30d', '#ca8a04', '#ea580c', '#dc2626', '#7c3aed'] + }, + ocean: { + background: '#0c4a6e', + text: '#f0f9ff', + textMuted: '#7dd3fc', + grid: '#075985', + groupBg: '#0369a1', + now: '#fbbf24', + colors: ['#06b6d4', '#22d3ee', '#67e8f9', '#0ea5e9', '#38bdf8', '#7dd3fc', '#0284c7', '#14b8a6'] + }, + sunset: { + background: '#1c1917', + text: '#fef3c7', + textMuted: '#fcd34d', + grid: '#292524', + groupBg: '#78350f', + now: '#fef3c7', + colors: ['#f97316', '#fb923c', '#fdba74', '#ef4444', '#f87171', '#fca5a5', '#eab308', '#facc15'] + }, + forest: { + background: '#14532d', + text: '#dcfce7', + textMuted: '#86efac', + grid: '#166534', + groupBg: '#15803d', + now: '#fef08a', + colors: ['#22c55e', '#4ade80', '#86efac', '#10b981', '#34d399', '#6ee7b7', '#059669', '#047857'] + } + }; + + // Helper to get theme color by index + function getThemeColor(theme, index) { + if (theme.colors && theme.colors.length > 0) { + return theme.colors[index % theme.colors.length]; + } + return null; + } + var refresh = function(filter, topPadding) { if (this.tasks.length > 0) { this.node.style.minHeight = this.node.clientHeight + "px"; this.node.innerHTML = ""; + + var theme = this.theme; + if (filter) { topPadding = topPadding < 150 ? 0 : topPadding - 150; - this.node.innerHTML = "← Back to the full roadmap"; + this.node.innerHTML = "← Back to the full roadmap"; var currentInstance = this; this.node.getElementsByTagName("A")[0].onclick = function() { refresh.apply(currentInstance); @@ -38,19 +136,31 @@ }; var parse = function() { - // Tasks - var colors = d3.scale.category20(); + // Default colors (original behavior) + var defaultColors = d3.scale.category20(); // For d3 var dateFormat = d3.time.format("%Y-%m-%d"); d3.selectAll("div.roadmap").each(function() { + // Get theme from data attribute + var themeName = this.getAttribute('data-theme') || 'default'; + var theme = themes[themeName] || themes.default; + + // Apply background color + this.style.backgroundColor = theme.background; + this.style.borderRadius = '8px'; + this.style.padding = '10px'; + var currentInstance = { tasks: [], people: [], - node: this + node: this, + theme: theme }; var currentTask = {}; + var groupIndex = 0; + var groupColorMap = {}; var lines = (currentInstance.node.textContent || currentInstance.node.innerHTML || "").split("\n"); for (var j = 0, line; line = lines[j], j < lines.length; j++) { @@ -65,7 +175,14 @@ currentTask.name = texts.slice(-1).join(",").trim(); currentTask.style = currentTask.group.match(/^\*/) ? "bold" : "normal"; currentTask.group = currentTask.group.replace(/^\*\s+/, ""); - currentTask.color = colors(currentTask.group); + + // Assign color based on group (will be overridden if @color is specified) + if (!groupColorMap[currentTask.group]) { + var themeColor = getThemeColor(theme, groupIndex); + groupColorMap[currentTask.group] = themeColor || defaultColors(currentTask.group); + groupIndex++; + } + currentTask.color = groupColorMap[currentTask.group]; continue; } @@ -78,6 +195,14 @@ continue; } + // NEW: Check for @color directive + if (line.match(/^@color:/i)) { + currentTask.color = line.replace(/^@color:/i, "").trim(); + // Update group color map so people inherit the same color + groupColorMap[currentTask.group] = currentTask.color; + continue; + } + // next lines, people if (line !== "") { var matches, involvement; @@ -97,7 +222,7 @@ to: currentTask.to, name: currentTask.group + " — " + currentTask.name, taskGroup: currentTask.group, - color: colors(currentTask.group), + color: currentTask.color, // Use task's color (may be custom) involvement: involvement }); continue; @@ -118,6 +243,7 @@ var draw = function(items, options) { var currentInstance = this; + var theme = this.theme; // Drawing var barHeight = 20; @@ -209,7 +335,7 @@ return d.count * gap - 4; }) .attr("stroke", "none") - .attr("fill", "#999") + .attr("fill", theme.groupBg) .attr("fill-opacity", 0.1); // Draw vertical labels @@ -231,7 +357,7 @@ }) .attr("text-anchor", "start") .attr("text-height", 14) - .attr("fill", "#000"); + .attr("fill", theme.text); var sidePadding = options.sidePadding || axisText[0].parentNode.getBBox().width + 15; @@ -272,7 +398,7 @@ .attr("class", "now"); xAxisGroup.selectAll(".now") - .attr("stroke", "red") + .attr("stroke", theme.now) .attr("opacity", 0.5) .attr("stroke-dasharray", "2,2") .attr("shape-rendering", "crispEdges"); @@ -280,13 +406,13 @@ xAxisGroup.selectAll("text") .style("text-anchor", "middle") - .attr("fill", "#000") + .attr("fill", theme.text) .attr("stroke", "none") .attr("font-size", 10) .attr("dy", "1em"); xAxisGroup.selectAll(".tick line") - .attr("stroke", "#dddddd") + .attr("stroke", theme.grid) .attr("shape-rendering", "crispEdges"); // Items group @@ -339,7 +465,7 @@ }) .attr("text-anchor", "middle") .attr("text-height", barHeight) - .attr("fill", "#000") + .attr("fill", theme.text) .style("pointer-events", "none"); // Draw vertical mouse helper @@ -349,7 +475,7 @@ .attr("y1", 0) .attr("x2", 0) .attr("y2", 0) - .style("stroke", "black") + .style("stroke", theme.text) .style("stroke-width", "1px") .style("stroke-dasharray", "2,2") .style("shape-rendering", "crispEdges") @@ -362,7 +488,7 @@ .attr("width", 50) .attr("height", barHeight) .attr("stroke", "none") - .attr("fill", "black") + .attr("fill", theme.text) .attr("fill-opacity", 0.8) .style("display", "none"); @@ -371,7 +497,7 @@ .attr("font-weight", "bold") .attr("text-anchor", "middle") .attr("text-height", barHeight) - .attr("fill", "white") + .attr("fill", theme.background) .style("display", "none"); var verticalMouseTopPadding = 40; @@ -430,6 +556,9 @@ } } + // Expose themes for external access + window.roadmapThemes = themes; + document.addEventListener("DOMContentLoaded", function(){ if(typeof window.__isRoadmapLoaded === "undefined") { parse(); diff --git a/roadmap.min.js b/roadmap.min.js index c3ede5a..a18e041 100644 --- a/roadmap.min.js +++ b/roadmap.min.js @@ -1,3 +1,2 @@ -/*! roadmap.js v0.0.1 - MIT license -2017-06-26 - Florent Solt */ -!function(){function a(a){switch(a.type){case"people":return a.taskGroup;case"task":return a.group;case"group":return a.name}}var b=function(a,c){if(this.tasks.length>0){if(this.node.style.minHeight=this.node.clientHeight+"px",this.node.innerHTML="",a){c=150>c?0:c-150,this.node.innerHTML="← Back to the full roadmap";var e=this;this.node.getElementsByTagName("A")[0].onclick=function(){b.apply(e)}}var f=d.apply(this,[this.tasks.filter(function(b){return a&&b.group!==a?!1:!0}),{}]);this.people.sort(function(a,b){return a.group>b.group?1:-1}),this.people.length>0&&d.apply(this,[this.people.filter(function(b){return a&&b.taskGroup!==a?!1:!0}),f])}},c=function(){var a=d3.scale.category20(),c=d3.time.format("%Y-%m-%d");d3.selectAll("div.roadmap").each(function(){for(var d,e={tasks:[],people:[],node:this},f={},g=(e.node.textContent||e.node.innerHTML||"").split("\n"),h=0;d=g[h],hb.from?1:-1:a.group>b.group?1:-1});for(var l=[],m=0,n=0;nu.domain()[0]&&xt?(z.attr("x1",a).attr("y1",10).attr("x2",a).attr("y2",k.attr("height")-20).style("display","block"),A.attr("x",a-25).attr("y",b-(f+8)/2+C).style("display","block"),B.attr("transform","translate("+a+","+(b+C)+")").text(d3.time.format("%b %d")(u.invert(a-t))).style("display","block")):(z.style("display","none"),A.style("display","none"),B.style("display","none"))}),k.on("mouseleave",function(){z.style("display","none"),A.style("display","none"),B.style("display","none")})}return{sidePadding:t,topPadding:i,svg:k}};document.addEventListener("DOMContentLoaded",function(){"undefined"==typeof window.__isRoadmapLoaded&&(c(),window.__isRoadmapLoaded=c)})}(); \ No newline at end of file +// @ts-nocheck +!function(){var t={default:{background:"#ffffff",text:"#000000",textMuted:"#666666",grid:"#dddddd",groupBg:"#999999",now:"red",colors:null},light:{background:"#ffffff",text:"#1f2937",textMuted:"#6b7280",grid:"#e5e7eb",groupBg:"#9ca3af",now:"#ef4444",colors:["#6366f1","#8b5cf6","#ec4899","#ef4444","#f59e0b","#10b981","#06b6d4","#3b82f6"]},dark:{background:"#1e1e2e",text:"#cdd6f4",textMuted:"#a6adc8",grid:"#45475a",groupBg:"#585b70",now:"#f38ba8",colors:["#89b4fa","#cba6f7","#f5c2e7","#f38ba8","#fab387","#a6e3a1","#94e2d5","#89dceb"]},colorful:{background:"#fffbeb",text:"#1c1917",textMuted:"#78716c",grid:"#fde68a",groupBg:"#d97706",now:"#dc2626",colors:["#f43f5e","#d946ef","#8b5cf6","#0ea5e9","#14b8a6","#84cc16","#f97316","#ec4899"]},minimal:{background:"#fafafa",text:"#171717",textMuted:"#737373",grid:"#e5e5e5",groupBg:"#a3a3a3",now:"#171717",colors:["#404040","#525252","#737373","#262626","#171717","#525252","#404040","#737373"]},corporate:{background:"#f8fafc",text:"#0f172a",textMuted:"#64748b",grid:"#e2e8f0",groupBg:"#94a3b8",now:"#0369a1",colors:["#0284c7","#0891b2","#059669","#65a30d","#ca8a04","#ea580c","#dc2626","#7c3aed"]},ocean:{background:"#0c4a6e",text:"#f0f9ff",textMuted:"#7dd3fc",grid:"#075985",groupBg:"#0369a1",now:"#fbbf24",colors:["#06b6d4","#22d3ee","#67e8f9","#0ea5e9","#38bdf8","#7dd3fc","#0284c7","#14b8a6"]},sunset:{background:"#1c1917",text:"#fef3c7",textMuted:"#fcd34d",grid:"#292524",groupBg:"#78350f",now:"#fef3c7",colors:["#f97316","#fb923c","#fdba74","#ef4444","#f87171","#fca5a5","#eab308","#facc15"]},forest:{background:"#14532d",text:"#dcfce7",textMuted:"#86efac",grid:"#166534",groupBg:"#15803d",now:"#fef08a",colors:["#22c55e","#4ade80","#86efac","#10b981","#34d399","#6ee7b7","#059669","#047857"]}};function e(t,e){return t.colors&&t.colors.length>0?t.colors[e%t.colors.length]:null}var r=function(t,e){if(this.tasks.length>0){this.node.style.minHeight=this.node.clientHeight+"px",this.node.innerHTML="";var a=this.theme;if(t){e=e<150?0:e-150,this.node.innerHTML="← Back to the full roadmap";var n=this;this.node.getElementsByTagName("A")[0].onclick=function(){r.apply(n)}}var i=o.apply(this,[this.tasks.filter(function(e){return!t||e.group===t}),{}]);this.people.sort(function(t,e){return t.group>e.group?1:-1}),this.people.length>0&&o.apply(this,[this.people.filter(function(e){return!t||e.taskGroup===t}),i])}},a=function(){var a=d3.scale.category20(),o=d3.time.format("%Y-%m-%d");d3.selectAll("div.roadmap").each(function(){var n=this.getAttribute("data-theme")||"default",i=t[n]||t.default;this.style.backgroundColor=i.background,this.style.borderRadius="8px",this.style.padding="10px";for(var s,l={tasks:[],people:[],node:this,theme:i},d={},c=0,p={},f=(l.node.textContent||l.node.innerHTML||"").split("\n"),u=0;s=f[u],ue.from?1:-1:t.group>e.group?1:-1});for(var f=[],u=0,g=0;gk.domain()[0]&&Mv?(H.attr("x1",t).attr("y1",10).attr("x2",t).attr("y2",p.attr("height")-20).style("display","block"),L.attr("x",t-25).attr("y",e-14+40).style("display","block"),z.attr("transform","translate("+t+","+(e+40)+")").text(d3.time.format("%b %d")(k.invert(t-v))).style("display","block")):(H.style("display","none"),L.style("display","none"),z.style("display","none"))}),p.on("mouseleave",function(){H.style("display","none"),L.style("display","none"),z.style("display","none")})}return{sidePadding:v,topPadding:d,svg:p}};function n(t){switch(t.type){case"people":return t.taskGroup;case"task":return t.group;case"group":return t.name}}window.roadmapThemes=t,document.addEventListener("DOMContentLoaded",function(){void 0===window.__isRoadmapLoaded&&(a(),window.__isRoadmapLoaded=a)})}(); \ No newline at end of file