-
Notifications
You must be signed in to change notification settings - Fork 25
Creating Custom Marking Tools
At the moment, all marking tools are invoked by the drawing task (tasks/drawing.cjsx). This is not likely to change, although the ‘drawing’ task might be renamed to ‘marking.’
The simplest marking tool available is the point tool. As such, it is worth describing how it was implemented since more complicated tools, such as the rectangle and the textRow tools, are based on it.
The bulk of the tool is contained in point/index.cjsx. This is the top-level component that handles initialization, mouse events, and rendering. A general outline of the code is described in the code below:
REQUIRED DEPENDENCIES: This is essentially a list of ‘require’ statements that imports other components or libraries that are used by the tool. The Draggable library is itself a component that conveniently wraps around other components or SVG tags, in the case of the point tool. It creates and manages its own event listeners and fires custom event handlers to respond to click events that can be passed to it as props. Additional components used by the tool, such as the DeleteButton, must also be included. Obviously, the React library needs to be there too.
DISPLAY PARAMETERS: This is where attributes of the SVG elements that make up the tools are defined. Examples include circle radius, stroke width, fill color, etc. They are capitalized by convention.
REACT COMPONENT (CLASS) DECLARATION: This is the React component that defines the tool. STATIC INITIALIZATION METHODS: These methods are important because they set the initial values of the mark, which is a representation of the tool and its attributes. Think of ‘mark’ as the ‘model’ (in MVC terms) for the tool. HELPER METHODS: Any additional methods that make the code more legible and reduce redundant code. RENDER METHOD: The most important (and only mandatory method) in any react component. Re-draws the tool whenever the state changes. EVENT HANDLERS: Methods that respond to mouse events. (Sub) components within the tool should be wrapped with a Draggable component and passed the necessary event handlers as props. For example,
<Draggable onDrag={@handleDrag}>
<circle r={radius} />
</Draggable>
The outline has been applied to the point/index.cjsx code below:
# REQUIRED DEPENDENCIES
React = require 'react'
Draggable = require 'lib/draggable'
DeleteButton = require './delete-button'
# DISPLAY PARAMETERS
RADIUS = 10
SELECTED_RADIUS = 20
CROSSHAIR_SPACE = 0.2
CROSSHAIR_WIDTH = 1
DELETE_BUTTON_ANGLE = 45
STROKE_WIDTH = 1.5
SELECTED_STROKE_WIDTH = 2.5
# REACT COMPONENT (CLASS) DECLARATION
module.exports = React.createClass
displayName: 'PointTool'
# STATIC INITIALIZATION METHODS
statics:
defaultValues: ({x, y}) ->
{x, y}
initMove: ({x, y}) ->
{x, y}
# HELPER METHODS
getDeleteButtonPosition: ->
theta = (DELETE_BUTTON_ANGLE) * (Math.PI / 180)
x: (SELECTED_RADIUS / @props.xScale) * Math.cos theta
y: -1 * (SELECTED_RADIUS / @props.yScale) * Math.sin theta
# RENDER METHOD
render: ->
averageScale = (@props.xScale + @props.yScale) / 2
crosshairSpace = CROSSHAIR_SPACE / averageScale
crosshairWidth = CROSSHAIR_WIDTH / averageScale
selectedRadius = SELECTED_RADIUS / averageScale
radius = if @props.selected
SELECTED_RADIUS / averageScale
else
RADIUS / averageScale
scale = (@props.xScale + @props.yScale) / 2
<g
tool={this}
transform="translate(#{@props.mark.x}, #{@props.mark.y})"
onMouseDown={@handleMouseDown}
>
<g
className="drawing-tool-main"
fill='transparent'
stroke='#f60'
strokeWidth={SELECTED_STROKE_WIDTH/scale}
onMouseDown={@props.onSelect unless @props.disabled}
>
<Draggable onDrag={@handleDrag}>
<circle r={radius} />
</Draggable>
{ if @props.selected
<DeleteButton tool={this} getDeleteButtonPosition={@getDeleteButtonPosition} />
}
</g>
</g>
# EVENT HANDLERS
handleDrag: (e, d) ->
@props.mark.x += d.x / @props.xScale
@props.mark.y += d.y / @props.yScale
@props.onChange e
handleMouseDown: ->
@props.onSelect @props.mark # unless @props.disabled
The textRow tool is used to mark regions that only require ranges of y-values. This is similar to a rectangle tool, except the width of the rectangle is fixed at 100% of the width of the subject viewer. This tool enables users to mark entire rows of text in the document.
We begin by duplicating the point tool and renaming it, by setting displayName: ‘TextRowTool’. Next, we extend the static initialization methods defaultValues() and initMove() to include the yUpper and yLower values. These are the values we really care about since they store the upper and lower coordinates of the rectangle. The x and y values still remain, but they are used to store the location of the horizontal line that bisects the rectangle. In other words, the y-position (since the x value is irrelevant). The default dimensions are set using the DEFAULT_HEIGHT parameter. For practical reasons, namely to handle resizing events, y is used to represent the midpoint between yUpper and yLower (offset by - DEFAULT_HEIGHT/2). As such, it is not relevant to the final transcriptions.
SVG elements were modified to suit our needs (lines removed, etc.). An additional DragHandle component, based on DeleteButton, was introduced to serve as the resize handles of the mark. Since there are two handle buttons, one at the top, and one at the bottom of the mark, event handlers were created for both, handleUpperResize() and handleLowerResize(), respectively. These are located in the top-level index file and passed as props to the Draggable component. Helper methods, getUpperHandlePosition() and getLowerHandlePosition() were introduced to re-calculate where the handle buttons should be located after resize events. Lastly, a bunch of ugly math was added, as required, to calculate all the correct locations.
- Getting Started
-
Setting up your Project
- Setup Your Environment
- Configure your project
- Load your project
- Code & Technical Notes
- Project Reference