Skip to content

Commit

Permalink
Add Polypath (#51)
Browse files Browse the repository at this point in the history
Add Polypath drawing!
- Left click to add points
- Double click to remove latest point
- Right click to close figure

Changes in behavior:
* Right-click context menu was deactivated for all other tools tools
* When pressing the bin button in the toolbar, all history is deleted and empty state is sent to Streamlit, even if the user has disabled realtime update.

Commits:
* polypath draft
* polypath: double-click to cancel previous points; right-click to complete polygon and update
* fix __init__.py and DrawableCanvas.tsx
* fix bugs and limit drawing functions to left-mouse only; also enable right-click to update
* fix DrawableCanvasState.tsx
* fix DrawableCanvas*.tsx
  • Loading branch information
hiankun authored Jun 6, 2021
1 parent dec96a2 commit 459f48b
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 10 deletions.
6 changes: 3 additions & 3 deletions streamlit_drawable_canvas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ def st_canvas(
Height of canvas in pixels. Defaults to 400.
width: int
Width of canvas in pixels. Defaults to 600.
drawing_mode: {'freedraw', 'transform', 'line', 'rect', 'circle'}
Enable free drawing when "freedraw", object manipulation when "transform", "line", "rect", "circle".
drawing_mode: {'freedraw', 'transform', 'line', 'rect', 'circle', 'polypath'}
Enable free drawing when "freedraw", object manipulation when "transform", "line", "rect", "circle", "polypath".
Defaults to "freedraw".
initial_drawing: dict
Redraw canvas with given initial_drawing. If changed to None then empties canvas.
Expand Down Expand Up @@ -123,7 +123,7 @@ def st_canvas(
strokeColor=stroke_color,
backgroundColor=background_color,
backgroundImage=background_image,
realtimeUpdateStreamlit=update_streamlit,
realtimeUpdateStreamlit=update_streamlit and (drawing_mode != 'polypath'),
canvasHeight=height,
canvasWidth=width,
drawingMode=drawing_mode,
Expand Down
9 changes: 8 additions & 1 deletion streamlit_drawable_canvas/frontend/src/DrawableCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
* State initialization
*/
const [canvas, setCanvas] = useState(new fabric.Canvas(""))
canvas.stopContextMenu = true
canvas.fireRightClick = true

const [backgroundCanvas, setBackgroundCanvas] = useState(
new fabric.StaticCanvas("")
)
Expand Down Expand Up @@ -142,8 +145,11 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
strokeColor: strokeColor,
})

canvas.on("mouse:up", () => {
canvas.on("mouse:up", (e: any) => {
saveState(canvas.toJSON())
if (e["button"] === 3) {
forceStreamlitUpdate()
}
})

canvas.on("mouse:dblclick", () => {
Expand All @@ -164,6 +170,7 @@ const DrawableCanvas = ({ args }: ComponentProps) => {
drawingMode,
initialDrawing,
saveState,
forceStreamlitUpdate,
])

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ const SEND_TO_STREAMLIT: CanvasAction = {
forceSendToStreamlit: true,
}

const RELOAD_AND_SEND_TO_STREAMLIT: CanvasAction = {
shouldReloadCanvas: true,
forceSendToStreamlit: true,
}

interface CanvasState {
history: CanvasHistory
action: CanvasAction
Expand Down Expand Up @@ -165,7 +170,7 @@ const canvasStateReducer = (
undoStack: [],
redoStack: [],
},
action: { ...RELOAD_CANVAS },
action: { ...RELOAD_AND_SEND_TO_STREAMLIT },
initialState: action.state,
currentState: action.state,
}
Expand Down
5 changes: 4 additions & 1 deletion streamlit_drawable_canvas/frontend/src/lib/circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class CircleTool extends FabricTool {

onMouseDown(o: any) {
let canvas = this._canvas
let _clicked = o.e["button"]
this.isMouseDown = true
let pointer = canvas.getPointer(o.e)
this.currentStartX = pointer.x
Expand All @@ -55,7 +56,9 @@ class CircleTool extends FabricTool {
evented: false,
radius: this._minRadius,
})
canvas.add(this.currentCircle)
if (_clicked === 0) {
canvas.add(this.currentCircle)
}
}

onMouseMove(o: any) {
Expand Down
2 changes: 2 additions & 0 deletions streamlit_drawable_canvas/frontend/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import PolypathTool from "./polypath"
import CircleTool from "./circle"
import FabricTool from "./fabrictool"
import FreedrawTool from "./freedraw"
Expand All @@ -7,6 +8,7 @@ import TransformTool from "./transform"

// TODO: Should make TS happy on the Map of selectedTool --> FabricTool
const tools: any = {
polypath: PolypathTool,
circle: CircleTool,
freedraw: FreedrawTool,
line: LineTool,
Expand Down
9 changes: 8 additions & 1 deletion streamlit_drawable_canvas/frontend/src/lib/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class LineTool extends FabricTool {

onMouseDown(o: any) {
let canvas = this._canvas
let _clicked = o.e["button"]
this.isMouseDown = true
var pointer = canvas.getPointer(o.e)
var points = [pointer.x, pointer.y, pointer.x, pointer.y]
Expand All @@ -44,7 +45,9 @@ class LineTool extends FabricTool {
selectable: false,
evented: false,
})
canvas.add(this.currentLine)
if (_clicked === 0) {
canvas.add(this.currentLine)
}
}

onMouseMove(o: any) {
Expand All @@ -58,6 +61,10 @@ class LineTool extends FabricTool {

onMouseUp(o: any) {
this.isMouseDown = false
let canvas = this._canvas
if (this.currentLine.width === 0 && this.currentLine.height === 0) {
canvas.remove(this.currentLine)
}
}

onMouseOut(o: any) {
Expand Down
159 changes: 159 additions & 0 deletions streamlit_drawable_canvas/frontend/src/lib/polypath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { fabric } from "fabric"
import FabricTool, { ConfigureCanvasProps } from "./fabrictool"

class PolypathTool extends FabricTool {
isMouseDown: boolean = false
fillColor: string = "#ffffff"
strokeWidth: number = 10
strokeColor: string = "#ffffff"
startCircle: fabric.Circle = new fabric.Circle()
currentLine: fabric.Line = new fabric.Line()
currentPath: fabric.Path = new fabric.Path()
_pathString: string = "M "

configureCanvas({
strokeWidth,
strokeColor,
fillColor,
}: ConfigureCanvasProps): () => void {
this._canvas.isDrawingMode = false
this._canvas.selection = false
this._canvas.forEachObject((o) => (o.selectable = o.evented = false))

this.strokeWidth = strokeWidth
this.strokeColor = strokeColor
this.fillColor = fillColor

this._canvas.on("mouse:down", (e: any) => this.onMouseDown(e))
this._canvas.on("mouse:move", (e: any) => this.onMouseMove(e))
this._canvas.on("mouse:up", (e: any) => this.onMouseUp(e))
this._canvas.on("mouse:out", (e: any) => this.onMouseOut(e))
this._canvas.on("mouse:dblclick", (e: any) => this.onMouseDoubleClick(e))
return () => {
this._canvas.off("mouse:down")
this._canvas.off("mouse:move")
this._canvas.off("mouse:up")
this._canvas.off("mouse:out")
this._canvas.off("mouse:dblclick")
}
}

onMouseDown(o: any) {
let canvas = this._canvas
let _clicked = o.e["button"]
let _start = false
if (this._pathString === "M ") {
_start = true
}

this.isMouseDown = true
var pointer = canvas.getPointer(o.e)

var points = [pointer.x, pointer.y, pointer.x, pointer.y]
canvas.remove(this.currentLine)
this.currentLine = new fabric.Line(points, {
strokeWidth: this.strokeWidth,
fill: this.strokeColor,
stroke: this.strokeColor,
originX: "center",
originY: "center",
selectable: false,
evented: false,
})
if (_clicked === 0) {
canvas.add(this.currentLine)
}

if (_start && _clicked === 0) {
// Initialize pathString
this._pathString += `${pointer.x} ${pointer.y} `
this.startCircle = new fabric.Circle({
left: pointer.x,
top: pointer.y,
originX: "center",
originY: "center",
strokeWidth: this.strokeWidth,
stroke: this.strokeColor,
fill: this.strokeColor,
selectable: false,
evented: false,
radius: this.strokeWidth,
})
canvas.add(this.startCircle)

_start = false
} else {
canvas.remove(this.currentPath)
if (_clicked === 0) {
// Update pathString
this._pathString += `L ${pointer.x} `
this._pathString += `${pointer.y} `
}
if (_clicked === 2) {
// Close pathString
this._pathString += "z"
canvas.remove(this.startCircle)
}
}
this.currentPath = new fabric.Path(this._pathString, {
strokeWidth: this.strokeWidth,
fill: this.fillColor,
stroke: this.strokeColor,
originX: "center",
originY: "center",
selectable: false,
evented: false,
})
if (this.currentPath.width !== 0 && this.currentPath.height !== 0) {
canvas.add(this.currentPath)
}
if (_clicked === 2) {
this._pathString = "M "
}
}

onMouseMove(o: any) {
if (!this.isMouseDown) return
let canvas = this._canvas
var pointer = canvas.getPointer(o.e)
this.currentLine.set({ x2: pointer.x, y2: pointer.y })
this.currentLine.setCoords()
canvas.renderAll()
}

onMouseUp(o: any) {
this.isMouseDown = true
}

onMouseOut(o: any) {
this.isMouseDown = false
}

onMouseDoubleClick(o: any) {
let canvas = this._canvas
// Double click adds two more points at the end, so we have to move back twice more...
for (let i = 0; i < 3; i++) {
let _last_pt_idx = this._pathString.lastIndexOf("L")
if (_last_pt_idx === -1) {
this._pathString = "M "
canvas.remove(this.startCircle)
} else {
this._pathString = this._pathString.slice(0, _last_pt_idx)
}
}

canvas.remove(this.currentLine)
canvas.remove(this.currentPath)
this.currentPath = new fabric.Path(this._pathString, {
strokeWidth: this.strokeWidth,
fill: this.fillColor,
stroke: this.strokeColor,
originX: "center",
originY: "center",
selectable: false,
evented: false,
})
canvas.add(this.currentPath)
}
}
export default PolypathTool
9 changes: 6 additions & 3 deletions streamlit_drawable_canvas/frontend/src/lib/rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class RectTool extends FabricTool {

onMouseDown(o: any) {
let canvas = this._canvas
let _clicked = o.e["button"]
this.isMouseDown = true
let pointer = canvas.getPointer(o.e)
this.currentStartX = pointer.x
Expand All @@ -48,8 +49,8 @@ class RectTool extends FabricTool {
top: this.currentStartY,
originX: "left",
originY: "top",
width: pointer.x - this.currentStartX,
height: pointer.y - this.currentStartY,
width: this._minLength,
height: this._minLength,
stroke: this.strokeColor,
strokeWidth: this.strokeWidth,
fill: this.fillColor,
Expand All @@ -60,7 +61,9 @@ class RectTool extends FabricTool {
noScaleCache: false,
angle: 0,
})
canvas.add(this.currentRect)
if (_clicked === 0) {
canvas.add(this.currentRect)
}
}

onMouseMove(o: any) {
Expand Down

0 comments on commit 459f48b

Please sign in to comment.