commit 963e11498fa62d22787fcc74f63e401422bc80f8
parent cd6e7869ba9e826d7703dddf9b3140e789e88288
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date: Sat, 10 Aug 2024 10:28:08 -0700
Add scaling to the floorplan
- Create a few SVG groups for certain classes of elements
- Add document scaling
- Update other operations (line drawing, etc.) to work with scaling,
document offsets, etc.
- Add 'moves' and 'scales' element classes which tell the scripts
to apply the appropriate SVG transforms to scale to document scale
and re-position to view position. (Note that they cannot be
combined at the moment)
Diffstat:
4 files changed, 184 insertions(+), 63 deletions(-)
diff --git a/files/floorplans/floorplan/graphics.js b/files/floorplans/floorplan/graphics.js
@@ -16,3 +16,7 @@ export const svg = {
export function point(x, y) {
return { x: x, y: y }
}
+
+export function rect(x, y, width, height) {
+ return { x: x, y: y, width: width, height: height }
+}
diff --git a/files/floorplans/floorplan/index.html b/files/floorplans/floorplan/index.html
@@ -13,12 +13,18 @@
<div id="mode_selector"></div>
- <svg id="floorplan" width="1600" height="1600" viewbox="400 400 800 800">
+ <svg id="floorplan" width="4096" height="4096" viewbox="0 0 800 800">
<script type="module" href="./svg.js"></script>
<title>Floorplan</title>
- <g id="floorplan_data">
+ <!-- Everything here "scales" -->
+ <g id="svg_data" class="scales">
<polyline id="walls"/>
+ <g id="furnature"></g>
+ </g>
+
+ <g id="svg_ui">
+ <g id="grid" class="scales"></g>
</g>
</svg>
</body>
diff --git a/files/floorplans/floorplan/svg.css b/files/floorplans/floorplan/svg.css
@@ -1,5 +1,5 @@
* {
- stroke-width: 1em;
+ stroke-width: 6;
}
#walls {
@@ -14,10 +14,15 @@
#grid_1 > line {
stroke: black;
- stroke-width: 1px;
+ stroke-width: 1;
}
#grid_2 > line {
stroke: #FAFAFA;
- stroke-width: 1px;
+ stroke-width: 1;
+}
+
+#debug_cursor {
+ fill: red;
+ r: .25em;
}
diff --git a/files/floorplans/floorplan/svg.js b/files/floorplans/floorplan/svg.js
@@ -15,7 +15,7 @@ const units = {
// systems
imperial: {
inch: 10,
- foot: 10 * 12
+ foot: 10 * 12
},
metric: {
meter: 254,
@@ -23,8 +23,8 @@ const units = {
}
}
+
function init() {
- console.warn(modes)
let state = {
walls: etc.require_id("walls"),
// [ { x: X, y: Y }, ... ]
@@ -32,6 +32,9 @@ function init() {
points: [],
units: units["imperial"],
svg: etc.require_id("floorplan"),
+ svg_ui: etc.require_id("svg_ui"),
+ svg_data: etc.require_id("svg_data"),
+ scale: 1,
flags: {}
}
@@ -45,12 +48,19 @@ function init() {
}
state.svg.before(mode_selector)
- state.svg.prepend(make_grid(state.units, state.svg.getAttribute("width"), state.svg.getAttribute("height")))
+ let size = canvas_size(state)
+ etc.require_id("floorplan").prepend(make_grid(state.units, size.width, size.height))
+ //let view = ..XXXJKLDJF
+ //set_scale(state, view.width / size.width)
+ set_scale(state, 2)
+
+ update_movable(state)
}
function make_grid(units, width, height) {
let grid = graphics.svg.element("g")
grid.id = "grid"
+ grid.setAttribute("class", "scales")
let sorted = sort_units(units)
for (let i in sorted) {
@@ -123,10 +133,11 @@ let modes = {
event.preventDefault()
},
mousedown: viewbox_movement_handler,
- mousemove: [freedraw_move_handler, viewbox_movement_handler, debug_mouse_position],
+ mousemove: [freedraw_move_handler, viewbox_movement_handler, debug_mouse_position_handler],
mouseleave: viewbox_movement_handler,
mouseup: viewbox_movement_handler,
- click: freedraw_click_handler
+ click: freedraw_click_handler,
+ auxclick: viewbox_scale_handler
},
add: {
mousemove: freedraw_move_handler,
@@ -134,11 +145,28 @@ let modes = {
}
}
+// Listen on auxclick
+function viewbox_scale_handler(state, click) {
+ if (click.button != 1) {
+ return
+ }
+ console.log("scale handler")
+ let from = graphics.point(click.offsetX, click.offsetY)
+
+ if (click.shiftKey) {
+ set_scale(state, state.scale - .5, from)
+ } else {
+ set_scale(state, state.scale + .5, from)
+ }
+ click.preventDefault()
+}
+
+// Listen on mousedown, mouseup, mouseleave, and mousemove
function viewbox_movement_handler(state, mouse) {
if (mouse.type === "mouseleave") {
state.flags.moving = false
console.debug("Movement (left)", state.flags.moving)
- } else if (mouse.button & 2) {
+ } else if (mouse.button === 2) {
if (mouse.type === "mousedown") {
state.flags.moving = true
} else if (mouse.type === "mouseup") {
@@ -147,59 +175,44 @@ function viewbox_movement_handler(state, mouse) {
console.debug("Movement (up/down)", state.flags.moving)
}
if (state.flags.moving && mouse.type === "mousemove") {
- let docwidth = state.svg.getAttribute("width")
- let docheight = state.svg.getAttribute("height")
- let view = state.svg.viewBox.animVal
- let x = view.x - mouse.movementX
- let y = view.y - mouse.movementY
- if (mouse.movementX > 0 && x < 0) {
- x = 0
- } else if (x + view.width > docwidth) {
- x = docwidth - view.width
+ let view = viewbox(state)
+ let newpos =
+ graphics.point(view.x - mouse.movementX, view.y - mouse.movementY)
+
+ let size = canvas_size(state)
+ if (newpos.x < 0) {
+ newpos.x = 0
+ } else if (newpos.x + view.width > size.width) {
+ newpos.x = size.width - view.width
}
- if (mouse.movementY > 0 && y < 0) {
- y = 0
- } else if (y + view.height > docheight) {
- y = docheight - view.height;
+ if (newpos.y < 0) {
+ newpos.y = 0
+ } else if (newpos.y + view.height > size.height) {
+ newpos.y = size.height - view.height
}
- state.svg.setAttribute("viewBox", [x, y, state.svg.viewBox.animVal.width, state.svg.viewBox.animVal.height].join(' '))
- }
-}
-// listen on mousemove
-function debug_mouse_position(state, mouse) {
- let cursor = document.getElementById("debug_cursor")
- if (!cursor) {
- cursor = graphics.svg.element("circle")
- cursor.id = "debug_cursor"
- cursor.setAttribute("fill", "red")
- cursor.setAttribute("r", ".25em")
- state.svg.append(cursor)
+ update_viewbox(state, graphics.rect(newpos.x, newpos.y, view.width, view.height))
+ update_movable(state)
+ console.debug("Move", newpos.x, newpos.y)
}
- let text = document.getElementById("debug_cursor_text")
- if (!text) {
- text = document.createElement("span")
- text.id = "debug_cursor_text"
- text.setAttribute("position", "absolute")
- text.setAttribute("color", "red")
- document.body.append(text)
- }
- let p = view_to_canvas_point(state, { x: mouse.offsetX, y: mouse.offsetY })
- text.textContent = [mouse.offsetX, mouse.offsetY].join(", ")
- cursor.setAttribute("cx", p.x)
- cursor.setAttribute("cy", p.y)
}
+// Listen on mousemove
function freedraw_move_handler(state, mouse) {
let line = document.querySelector("line.preview")
if (!line) {
line = graphics.svg.element("line")
- line.setAttribute("class","preview")
+ line.setAttribute("class","preview scales")
state.svg.append(line)
+ update_scalable(state)
}
let last = last_point(state)
- let mp = state["preview_point"] = view_to_canvas_point(state, graphics.point(mouse.offsetX, mouse.offsetY))
+
+ let p = real_to_absolute(state, view_to_real(state, graphics.point(mouse.offsetX, mouse.offsetY)))
+
+ state["preview_point"] = p
+
if (!last) {
line.setAttribute("hidden", true)
} else {
@@ -208,17 +221,44 @@ function freedraw_move_handler(state, mouse) {
}
line.removeAttribute("hidden")
line.setAttribute("x1", last.x)
- line.setAttribute("y1",last.y)
- line.setAttribute("x2", mp.x)
- line.setAttribute("y2", mp.y)
+ line.setAttribute("y1", last.y)
+ line.setAttribute("x2", p.x)
+ line.setAttribute("y2", p.y)
}
}
+// Listen on click
function freedraw_click_handler(state, click) {
+ if (click.button != 0) {
+ return
+ }
if (!state["preview_point"]) {
throw new Error("Expected preview_point")
}
add_points(state, state["preview_point"])
+ click.preventDefault()
+}
+
+// listen on mousemove
+function debug_mouse_position_handler(state, mouse) {
+ let cursor = document.getElementById("debug_cursor")
+ if (!cursor) {
+ cursor = graphics.svg.element("circle")
+ cursor.id = "debug_cursor"
+ cursor.setAttribute("class", "moves")
+ state.svg_ui.append(cursor)
+ }
+ let text = document.getElementById("debug_cursor_text")
+ if (!text) {
+ text = document.createElement("span")
+ text.id = "debug_cursor_text"
+ document.body.append(text)
+ }
+
+ let p = view_to_real_scaled(state, graphics.point(mouse.offsetX, mouse.offsetY))
+ text.textContent = `Mouse: ${p["x"]}x${p["y"]}`
+ cursor.setAttribute("cx", p.x)
+ cursor.setAttribute("cy", p.y)
}
function remove_mode_handlers(element, mode_handlers) {
@@ -239,6 +279,21 @@ function add_mode_handlers(element, mode_handlers) {
}
}
+function update_movable(state) {
+ let moving = Array.from(document.getElementsByClassName("moves"))
+ let view = viewbox(state)
+ for (let i in moving) {
+ moving[i].setAttribute("transform", "translate(" + view.x + "," + view.y + ")")
+ }
+}
+
+function update_scalable(state) {
+ let scaling = Array.from(document.getElementsByClassName("scales"))
+ for (let i in scaling) {
+ scaling[i].setAttribute("transform", "scale(" + state.scale + ")")
+ }
+}
+
function update_points_display(state) {
let s = ""
for (let i in state.points) {
@@ -278,21 +333,72 @@ function axis_snap_which(a, b) {
}
}
-function view_to_canvas_point(state, viewbox_point) {
- let view = viewbox(state)
-
- // NOTE:: I'm dividing by 2 because it works, but I'm not
- // sure if this is because of a universal property or the
- // mousemove offset[XY] values
- return { x: (viewbox_point.x / 2) + view.x, y: (viewbox_point.y / 2) + view.y }
-}
-
function viewbox(state) {
let a = state.svg.getAttribute("viewBox").split(' ')
for (let i in a) {
a[i] = Number(a[i])
}
- return { x: a[0], y: a[1], width: a[2], height: a[3] }
+ return graphics.rect(a[0], a[1], a[2], a[3])
+}
+
+function update_viewbox(state, newview) {
+ console.debug("Update viewbox:", newview)
+ state.svg.setAttribute("viewBox", [newview.x, newview.y, newview.width, newview.height].join(' '))
+}
+
+function canvas_size(state) {
+ return { width: Number(state.svg.getAttribute("width")), height: Number(state.svg.getAttribute("height")) }
+}
+
+function set_scale(state, scale, from) {
+ if (typeof scale !== "number") {
+ throw new Error(scale + ": Invalid scale")
+ }
+
+ let size = canvas_size(state)
+ let view = viewbox(state)
+ let furthest = Math.min(view.width / size.width, view.height / size.height)
+ console.log(furthest)
+ if (scale < furthest) {
+ scale = furthest
+ }
+
+ state["scale"] = scale
+ update_scalable(state)
+ console.log("Scale", scale)
+}
+
+function view_to_real(state, p) {
+ let view = viewbox(state)
+ let size = canvas_size(state)
+ let scale = state["scale"] * 1
+
+ let r = graphics.point(
+ ((p.x / (size.width / view.width))) / scale,
+ ((p.y / (size.height / view.height))) / scale
+ )
+ console.debug(`Viewbox to real coord: ${p.x}x${p.y} -> ${r.x}x${r.y} [scale ${state.scale}] [view ${viewbox(state).x}, ${viewbox(state).width}] [canvas ${canvas_size(state).width}]`)
+ return r
+}
+
+function view_to_real_scaled(state, p) {
+ let r = view_to_real(state, p)
+ let rs = graphics.point(
+ r.x * state["scale"],
+ r.y * state["scale"]
+ )
+
+ console.debug(`Real to real scaled coord: ${r.x}x${r.y} -> ${rs.x}x${rs.y} [scale ${state.scale}] [view ${viewbox(state).x}, ${viewbox(state).width}] [canvas ${canvas_size(state).width}]`)
+ return rs
+}
+
+function real_to_absolute(state, p) {
+ let view = viewbox(state)
+
+ return graphics.point(
+ p.x + (view.x / state["scale"]),
+ p.y+ (view.y / state["scale"])
+ )
}
init()