diff --git a/boxline2.js b/boxline2.js index 16bd44e..2fdec05 100644 --- a/boxline2.js +++ b/boxline2.js @@ -32,10 +32,15 @@ function edgeName(link) { return `link-${link.index}` } -export function renderBoxGraph({nodes, links}, ignore, callback) { +export function renderBoxGraph({nodes, links}, direction, ignore, callback) { let [itemColors, recipeColors] = getColorMaps(nodes, links) + if (direction === "down") { + direction = "TB" + } else { + direction = "LR" + } let g = new dagre.graphlib.Graph({multigraph: true}) - g.setGraph({rankdir: "TB"}) + g.setGraph({rankdir: direction}) g.setDefaultEdgeLabel(() => {}) let testSVG = d3.select("body").append("svg").classed("test", true) @@ -181,7 +186,7 @@ export function renderBoxGraph({nodes, links}, ignore, callback) { .data(nodes) .join("g") .classed("node", true) - renderNode(rects, boxlineNodeMargin, recipeColors, ignore) + renderNode(rects, boxlineNodeMargin, "left", recipeColors, ignore) svg.append("g") .classed("overlay", true) diff --git a/calc.html b/calc.html index dd6cd19..149cb4e 100644 --- a/calc.html +++ b/calc.html @@ -26,7 +26,7 @@ var handlers = {} @@ -115,6 +116,16 @@ +
+ Graph direction:
+
+ + + + + +
+
diff --git a/events.js b/events.js index 6079b18..54f7adc 100644 --- a/events.js +++ b/events.js @@ -93,6 +93,8 @@ export function setVisualizerType(vt) { export function changeVisType(event) { visualizerType = event.target.value + visualizerDirection = getDefaultVisDirection() + d3.select(`#${visualizerDirection}_direction`).property("checked", true) spec.display() } @@ -109,6 +111,29 @@ export function changeVisRender(event) { spec.display() } +export let visualizerDirection + +export function getDefaultVisDirection() { + if (visualizerType === "sankey") { + return "right" + } else { + return "down" + } +} + +export function isDefaultVisDirection() { + return visualizerDirection === getDefaultVisDirection() +} + +export function setVisualizerDirection(vd) { + visualizerDirection = vd +} + +export function changeVisDir(event) { + visualizerDirection = event.target.value + spec.display() +} + // Number of SVG coordinate points per zoom level. const ZOOM_SCALE = 100 // Number of distinct zoom "steps." diff --git a/fragment.js b/fragment.js index b7873b7..42e97a1 100644 --- a/fragment.js +++ b/fragment.js @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ import { DEFAULT_RATE, DEFAULT_RATE_PRECISION, DEFAULT_COUNT_PRECISION, DEFAULT_FORMAT } from "./align.js" -import { DEFAULT_TAB, currentTab, DEFAULT_VISUALIZER, visualizerType, DEFAULT_RENDER, visualizerRender } from "./events.js" +import { DEFAULT_TAB, currentTab, DEFAULT_VISUALIZER, visualizerType, DEFAULT_RENDER, visualizerRender, isDefaultVisDirection, visualizerDirection } from "./events.js" import { spec, DEFAULT_BELT, DEFAULT_FUEL } from "./factory.js" import { Rational } from "./rational.js" import { currentMod, DEFAULT_TITLE, DEFAULT_COLOR_SCHEME, colorScheme } from "./settings.js" @@ -102,6 +102,9 @@ export function formatSettings(excludeTitle, overrideTab, targets) { if (visualizerRender !== DEFAULT_RENDER) { settings += "vr=" + visualizerRender + "&" } + if (!isDefaultVisDirection()) { + settings += "vd=" + visualizerDirection + "&" + } settings += "items=" let targetStrings = [] diff --git a/graph.js b/graph.js index 6b4a73e..5f75f6b 100644 --- a/graph.js +++ b/graph.js @@ -103,7 +103,14 @@ export function imageViewBox(obj) { return `${x1} ${y1} ${PX_WIDTH-1} ${PX_HEIGHT-1}` } -export function renderNode(rects, nodeMargin, recipeColors, ignore) { +export function renderNode(rects, nodeMargin, justification, recipeColors, ignore) { + rects.each(d => { + if (justification === "left") { + d.labelX = d.x0 + } else { + d.labelX = (d.x0 + d.x1)/2 - d.width/2 + } + }) // main rect rects.append("rect") .attr("x", d => d.x0) @@ -125,7 +132,7 @@ export function renderNode(rects, nodeMargin, recipeColors, ignore) { // recipe icon labeledNode.append("svg") .attr("viewBox", d => imageViewBox(d.recipe)) - .attr("x", d => d.x0 + nodeMargin + 0.5) + .attr("x", d => d.labelX + nodeMargin + 0.5) .attr("y", d => (d.y0 + d.y1) / 2 - iconSize/2 + 0.5) .attr("width", iconSize) .attr("height", iconSize) @@ -136,7 +143,7 @@ export function renderNode(rects, nodeMargin, recipeColors, ignore) { .attr("height", sheetHeight) // node text (building count, or plain rate if no building) labeledNode.append("text") - .attr("x", d => d.x0 + nodeMargin + iconSize + (d.building === null ? 0 : colonWidth + iconSize) /*+ 5*/) + .attr("x", d => d.labelX + nodeMargin + iconSize + (d.building === null ? 0 : colonWidth + iconSize) /*+ 5*/) .attr("y", d => (d.y0 + d.y1) / 2) .attr("dy", "0.35em") .text(d => d.text()) @@ -144,18 +151,18 @@ export function renderNode(rects, nodeMargin, recipeColors, ignore) { // colon buildingNode.append("circle") .classed("colon", true) - .attr("cx", d => d.x0 + nodeMargin + iconSize + colonWidth/2) + .attr("cx", d => d.labelX + nodeMargin + iconSize + colonWidth/2) .attr("cy", d => (d.y0 + d.y1) / 2 - 4) .attr("r", 1) buildingNode.append("circle") .classed("colon", true) - .attr("cx", d => d.x0 + nodeMargin + iconSize + colonWidth/2) + .attr("cx", d => d.labelX + nodeMargin + iconSize + colonWidth/2) .attr("cy", d => (d.y0 + d.y1) / 2 + 4) .attr("r", 1) // building icon buildingNode.append("svg") .attr("viewBox", d => imageViewBox(d.building)) - .attr("x", d => d.x0 + iconSize + colonWidth + nodeMargin + 0.5) + .attr("x", d => d.labelX + iconSize + colonWidth + nodeMargin + 0.5) .attr("y", d => (d.y0 + d.y1) / 2 - iconSize/2 + 0.5) .attr("width", iconSize) .attr("height", iconSize) diff --git a/sankey.js b/sankey.js index 62ab88d..a918ffa 100644 --- a/sankey.js +++ b/sankey.js @@ -95,7 +95,7 @@ function linkPath(d) { return makeCurve(1, 0, x0, y0, x1, y1, d.width) } -export function renderSankey(data, ignore) { +export function renderSankey(data, direction, ignore) { let maxNodeWidth = 0 let testSVG = d3.select("body").append("svg") .classed("sankey test", true) @@ -110,9 +110,17 @@ export function renderSankey(data, ignore) { text.remove() testSVG.remove() + let nw, np + if (direction === "down") { + nw = nodePadding + np = maxNodeWidth + } else if (direction === "right") { + nw = maxNodeWidth + np = nodePadding + } let sankey = d3sankey.sankey() - .nodeWidth(maxNodeWidth) - .nodePadding(nodePadding) + .nodeWidth(nw) + .nodePadding(np) .nodeAlign(d3sankey.sankeyRight) .maxNodeHeight(maxNodeHeight) .linkLength(columnWidth) @@ -121,6 +129,9 @@ export function renderSankey(data, ignore) { for (let link of links) { link.curve = linkPath(link) + if (direction === "down") { + link.curve = link.curve.transpose() + } let belts = [] if (link.beltCount !== null) { let dy = link.width / link.beltCount.toFloat() @@ -136,6 +147,13 @@ export function renderSankey(data, ignore) { link.belts = belts } + if (direction === "down") { + for (let node of nodes) { + [node.x0, node.y0] = [node.y0, node.x0]; + [node.x1, node.y1] = [node.y1, node.x1]; + } + } + let svg = d3.select("svg#graph") .classed("sankey", true) svg.selectAll("g").remove() @@ -148,7 +166,11 @@ export function renderSankey(data, ignore) { .join("g") .classed("node", true) - renderNode(rects, sankeyNodeMargin, recipeColors, ignore) + let nodeJust = "left" + if (direction === "down") { + nodeJust = "center" + } + renderNode(rects, sankeyNodeMargin, nodeJust, recipeColors, ignore) // Link paths let link = svg.append("g") @@ -190,23 +212,38 @@ export function renderSankey(data, ignore) { .attr("stroke-width", 1) link.append("title") .text(d => `${d.source.name} \u2192 ${d.target.name}\n${spec.format.rate(d.rate)}`) - link.filter(d => d.extra) + let linkIcon = link.filter(d => d.extra) .append("svg") .attr("viewBox", d => imageViewBox(d.item)) .attr("x", d => d.source.x1 + 2.25) .attr("y", d => d.y0 - iconSize/4 + 0.25) .attr("width", iconSize/2) .attr("height", iconSize/2) - .append("image") - .attr("xlink:href", "images/sprite-sheet-" + sheetHash + ".png") - .attr("width", sheetWidth) - .attr("height", sheetHeight) - link.append("text") + linkIcon.append("image") + .attr("xlink:href", "images/sprite-sheet-" + sheetHash + ".png") + .attr("width", sheetWidth) + .attr("height", sheetHeight) + if (direction === "down") { + linkIcon + .attr("x", d => d.y0 - iconSize/4 + 0.25) + .attr("y", d => d.source.y1 + 2.25) + } + let linkLabel = link.append("text") .attr("x", d => d.source.x1 + 2 + (d.extra ? iconSize/2 : 0)) .attr("y", d => d.y0) .attr("dy", "0.35em") .attr("text-anchor", "start") .text(d => (d.extra ? "\u00d7 " : "") + spec.format.rate(d.rate) + "/" + spec.format.rateName) + if (direction === "down") { + linkLabel + .attr("x", null) + .attr("y", null) + .attr("transform", d => { + let x = d.y0 + let y = d.source.y1 + 2 + (d.extra ? 16 : 0) + return `translate(${x},${y}) rotate(90)` + }) + } // Overlay transparent rect on top of each node, for click events. let rectElements = svg.selectAll("g.node rect").nodes() diff --git a/settings.js b/settings.js index 52fe422..82c586c 100644 --- a/settings.js +++ b/settings.js @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License.*/ import { DEFAULT_RATE, DEFAULT_RATE_PRECISION, DEFAULT_COUNT_PRECISION, DEFAULT_FORMAT, longRateNames } from "./align.js" import { colorSchemes } from "./color.js" -import { DEFAULT_TAB, clickTab, DEFAULT_VISUALIZER, visualizerType, setVisualizerType, DEFAULT_RENDER, visualizerRender, setVisualizerRender } from "./events.js" +import { DEFAULT_TAB, clickTab, DEFAULT_VISUALIZER, visualizerType, setVisualizerType, DEFAULT_RENDER, visualizerRender, setVisualizerRender, visualizerDirection, getDefaultVisDirection, setVisualizerDirection } from "./events.js" import { spec, DEFAULT_BELT, DEFAULT_FUEL, buildingSort } from "./factory.js" import { getRecipeGroups } from "./groups.js" import { changeMod } from "./init.js" @@ -40,7 +40,7 @@ export let MODIFICATIONS = new Map([ //["space-age", new Modification("Space Age 2.0.6", "space-age-2.0.6.json", false)], ]) -let DEFAULT_MODIFICATION = "2-0-6" +let DEFAULT_MODIFICATION = "2-0-7" // Ideally we'd write this as a generalized function, but for now we can hard- // code these version upgrades. @@ -525,6 +525,12 @@ function renderVisualizer(settings) { setVisualizerRender(DEFAULT_RENDER) } d3.select(`#${visualizerRender}_render`).property("checked", true) + if (settings.has("vd")) { + setVisualizerDirection(settings.get("vd")) + } else { + setVisualizerDirection(getDefaultVisDirection()) + } + d3.select(`#${visualizerDirection}_direction`).property("checked", true) } // default module diff --git a/visualize.js b/visualize.js index 3b8cf69..17a000c 100644 --- a/visualize.js +++ b/visualize.js @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ import { renderBoxGraph } from "./boxline2.js" -import { visualizerType, visualizerRender, installSVGEvents } from "./events.js" +import { visualizerType, visualizerRender, visualizerDirection, installSVGEvents } from "./events.js" import { spec } from "./factory.js" import { iconSize, colonWidth } from "./graph.js" import { zero, one } from "./rational.js" @@ -247,9 +247,9 @@ export function renderTotals(totals, ignore) { } if (visualizerType === "sankey") { - renderSankey(data, ignore) + renderSankey(data, visualizerDirection, ignore) callback() } else { - renderBoxGraph(data, ignore, callback) + renderBoxGraph(data, visualizerDirection, ignore, callback) } }