The fglsvgcanvas web component
The fglsvgcanvas
built-in web component implements a drawing canvas for
Scalable Vector Graphics content.
fglsvgcanvas
, consider learning SVG, with tutorials found on the internet. It is
especially mandatory to properly understand the root SVG viewport / viewBox / preserveAspectRatio
concepts.fglsvgcanvas
built-in web component is a gICAPI web component."10em"
, and
including the % percentage unit like in "50%"
, some fglsvgcanvas functions use the
STRING
type for parameters such as x, y, width, height. In SVG, the decimal
separator for numeric values must always be a dot. When computing coordinates and sizes with numeric
types such as DECIMAL
or when passing decimal values directly to fglsvgcanvas
functions, pay attention to numeric to string conversion. By default, the DBMONEY/DBFORMAT settings
apply and can produce a comma for the decimal separator. As a general advice, when possible, define
a large SVG viewBox (like "0 0 1000 1000"
), in order to use only integer numbers
for coordinates and sizes, or %
percentage units.The fglsvgcanvas
web component HTML page is basically a simple HTML container.
It is delivered with the utility library FGLDIR/src/webcomponents/fglsvgcanvas/fglsvgcanvas.4gl, that can be
used to produce SVG content.
The programming pattern is based on the built-in
om.*
API. Create DOM nodes with the utility functions, and construct the
root <svg/>
element by adding child nodes created from the fglsvgcanvas
functions.
Defining the fglsvgcanvas
web component in the form file
In the .per form definition file, define the SVG container as a WEBCOMPONENT
form
item with the COMPONENTTYPE
attribute set to the "fglsvgcanvas"
value.
Since the SVG canvas web component provides built-in scrollbars, the SCROLLBARS
attribute can be set
to NONE
.
Use SIZEPOLICY=FIXED
and
STRETCH=BOTH
, to get an SVG
canvas that resizes with the parent window.
Additional fglsvgcanvas web component configuration options can be defined with the PROPERTIES
attribute (details
will be discussed later in this topic).
For example:
LAYOUT
GRID
{
[cv ]
[ ]
[ ]
...
ATTRIBUTES
WEBCOMPONENT cv = FORMONLY.canvas,
COMPONENTTYPE = "fglsvgcanvas",
PROPERTIES = ( selection="item_selection" ),
SIZEPOLICY = FIXED,
STRETCH = BOTH,
SCROLLBARS = NONE;
...
The fglsvgcanvas.4gl library
SVG can be used to draw complex content such as an agenda or a graph, with advanced SVG concepts
such as CSS styles, patterns, nested <svg/>
elements, etc.
To simplify SVG programming, Genero BDL provides the FGLDIR/src/webcomponents/fglsvgcanvas/fglsvgcanvas.4gl utility library. This library implements a set of functions that produce common SVG elements.
- Attribute sets
- CSS styles
- Patterns
- Masks
- Filters
- Gradients
- Shapes (rect, circle, polygon, etc)
- Simple text, text on path, text tspan
- Animation
- Clickable elements
- Clipping paths
- RGB color utilities (shade, tint)
fglsvgcanvas library initialization and finalization
CALL fglsvgcanvas.initialize()
...
CALL fglsvgcanvas.finalize()
For more details see initialize() and finalize().
Creating an SVG canvas handler
SMALLINT
variable to hold
the SVG canvas handler id that is returned by the create()
function.
This function takes the WEBCOMPONENT field name as attribute, to bind the form field to the SVG
canvas
handle:DEFINE cid SMALLINT
...
LET cid = fglsvgcanvas.create("formonly.canvas")
Selecting an SVG canvas handler
The SVG canvas is identified by the id returned by the create()
function. After
creating an SVG canvas, it is automatically defined as the current canvas, and any subsequent calls
to an fglsvgcanvas function will apply to that current canvas handler. If you want to manipulate
several SVG canvases, select the current canvas with the setCurrent()
function:
CALL fglsvgcanvas.setCurrent(cid)
The root SVG node
The root SVG DOM node is created when calling the create()
function.
However, before drawing your SVG, you need to define essential root SVG attributes.
To define the root SVG attributes, use the setRootSVGAttributes()
function.
This function returns the root om.DomNode
of the SVG tree:
DEFINE root_svg, n om.DomNode
...
LET root_svg = fglsvgcanvas.setRootSVGAttributes(
"myrootsvg",
"10em", "10em", -- viewport
"0 0 500 500", -- viewbox
"xMidYMid meet" -- preserveAspectRatio
)
...
- the viewport defines the viewing area for the SVG image (use
NULL,NULL
for auto-resize; default unit ispx
, consider usingem
unit), - the viewBox defines the internal coordinate system (with a (0,0,500,200) viewBox, point (250,100) is the middle),
- the parameters to preserve the aspect ratio.
For more details about the <svg/>
element attributes, see the W3 SVG
specification.
Destroying an SVG canvas handler
WEBCOMPONENT
), you can release resources allocated for
the SVG handle by calling the destroy()
function:DEFINE cid SMALLINT
...
LET cid = fglsvgcanvas.create("formonly.canvas")
...
CALL fglsvgcanvas.destroy( cid )
destroy()
function, the SVG canvas handler that was created
before the destroyed handler, will be set as the new current SVG
canvas:DEFINE cid1, cid2 SMALLINT
...
LET cid1 = fglsvgcanvas.create("formonly.canvas1") -- current canvas is cid1
...
LET cid2 = fglsvgcanvas.create("formonly.canvas2") -- current canvas is cid2
...
CALL fglsvgcanvas.destroy( cid2 ) -- current canvas is cid1
...
CALL fglsvgcanvas.destroy( cid1 ) -- no current canvas
Building the SVG DOM tree
setRootSVGAttributes()
function, create other DOM element with fglsvgcanvas
functions, and append the child nodes to the root element or
sub-elements:DEFINE root_svg, n, g om.DomNode
...
LET n = fglsvgcanvas.svg( ... ) -- creates an <svg/> sub-node.
CALL root_svg.appendChild( n )
...
LET g = fglsvgcanvas.g( ... ) -- creates a <g/> sub-node.
CALL n.appendChild( g )
...
Cleaning the SVG canvas
clean()
function:CALL fglsvgcanvas.clean( cid )
Defining CSS styles
SVG supports CSS styling. Styles must be defined in the <defs/>
element, and
can then be referenced in SVG drawing elements by using the class
attribute.
om.SaxAttributes
object. For attribute names, use the predefined SVGATT_*
constants
available in the fglsvgcanvas library.om.SaxAttributes
, to define several reusable attribute
sets.CONSTANT COLORS_OCEAN = 1
CONSTANT COLORS_SAHARA = 2
DEFINE attr DYNAMIC ARRAY OF om.SaxAttributes
...
LET attr[COLORS_OCEAN] = om.SaxAttributes.create()
CALL attr[COLORS_OCEAN].addAttribute(SVGATT_FILL, "cyan" )
CALL attr[COLORS_OCEAN].addAttribute(SVGATT_FILL_OPACITY, "0.3" )
CALL attr[COLORS_OCEAN].addAttribute(SVGATT_STROKE, "blue" )
CALL attr[COLORS_OCEAN].addAttribute(SVGATT_STROKE_WIDTH, "5" )
CALL attr[COLORS_OCEAN].addAttribute(SVGATT_STROKE_OPACITY, "0.3" )
LET attr[COLORS_SAHARA] = om.SaxAttributes.create()
CALL attr[COLORS_SAHARA].addAttribute(SVGATT_FILL, "yellow" )
...
om.SaxAttributes
object can be used in
different manners:- To explicitly set attributes in an SVG DOM node, with the
setAttributes()
function. - To define a CSS style with a selector and a list of
name:value;
pairs, with thestyleDefinition()
function. - To set an inline-style in an DOM node, defining a list of
name:value;
pairs, with thestyleAttributeList()
function.
To create a set of CSS styles, create a <defs/>
SVG element, containing a
<style/>
element including your attributes sets.
The <style/>
element is created with then styleList()
function, and each CSS style string is created with the styleDefinition()
function, from the om.SaxAttributes
objects:
DEFINE defs om.DomNode
...
LET defs = fglsvgcanvas.defs( NULL )
CALL defs.appendChild( fglsvgcanvas.styleList(
fglsvgcanvas.styleDefinition(".style_1",attr[STYLE_1])
|| fglsvgcanvas.styleDefinition(".style_2",attr[STYLE_2])
|| fglsvgcanvas.styleDefinition(".style_3",attr[STYLE_3])
)
)
CALL root_svg.appendChild( defs )
<rect/>
element that references the
style defined with in the <defs/>
node:DEFINE r om.DomNode
LET r = fglsvgcanvas.rect(10,10,30,40,NULL,NULL)
CALL r.setAttribute(SVGATT_CLASS, "style_1")
Displaying the SVG content
After creating the SVG content, it must be sent to the front-end for rendering.
To display the SVG content to the WEBCOMPONENT
field associated with the SVG
canvas handle, use the display()
function:
CALL fglsvgcanvas.display( cid )
Detecting SVG element selection
WEBCOMPONENT
form field with a
"selection"
property in the PROPERTIES
attribute. This property
defines the action that will be fired when the user clicks on an SVG element. Use lowercase action
names:WEBCOMPONENT cv = FORMONLY.canvas,
...
PROPERTIES = (selection="item_selection"),
...
Define clickable SVG elements by using the <g/>
SVG grouping element. In the
group element, define the SVGATT_ONCLICK/"onclick"
attribute to
SVGVAL_ELEM_CLICKED/"elem_clicked(this)"
.
The SVGVAL_ELEM_CLICKED/"elem_clicked(this)
JavaScript function is predefined in
fglsvgcanvas.html, and will be fired the action defined by the
selection
property defined by the WEBCOMPONENT
form field.
The <g/>
group element (and each child element below the group), must be
defined with an "id"
attribute, that will be used to identify the clicked
elements:
DEFINE root_svg, g, c om.DomNode,
x INT,
id STRING
...
LET id = "shape_1"
LET g = fglsvgcanvas.g(id)
CALL g.setAttribute(SVGATT_ONCLICK,SVGVAL_ELEM_CLICKED)
LET c = fglsvgcanvas.circle(x,y,s)
CALL c.setAttribute("id",id)
CALL c.setAttribute(SVGATT_STYLE, 'stroke:gray;fill:blue;fill-opacity:0.3' )
CALL g.appendChild( c )
LET c = fglsvgcanvas.circle(x,y,20)
CALL c.setAttribute("id",id)
CALL c.setAttribute(SVGATT_STYLE, 'stroke:gray;fill:yellow;fill-opacity:0.8' )
CALL root_svg.appendChild( g )
In the program code, detect SVG element selection with an ON ACTION
handler
using the action name that matches the "selection" property of the WEBCOMPONENT
field. When the action is fired, the id of the selected SVG element is provided in the
WEBCOMPONENT
field value:
DEFINE rec RECORD
canvas STRING, -- Variable bound to the WEBCOMPONENT form field
...
END RECORD
...
INPUT BY NAME rec.* ATTRIBUTES(UNBUFFERED)
ON ACTION item_selection ATTRIBUTES(DEFAULTVIEW = NO)
DISPLAY "Clicked element:", rec.canvas
...
Information about the clicked SVG element is provided in JSON notation, for example:
{"id":"shape_1","source":"action","action":"item_selection"}
fglsvgcanvas.getItemId()
function to get the id of the clicked SVG element.
However, this produces a frontcall that can be avoided, since the element id is available in the
WEBCOMPONENT
field value. The getItemId()
function should only be
used when the WEBCOMPONENT
field does not have the focus, for example to handle
mouse hovering
events with mouse_event_focus = false
.Detecting mouse hovering SVG events
SVG elements can be defined with the mouse hovering events "onmouseover
" and
"onmouseout
", respectively defined as the SVGATT_ONMOUSEOVER
and
SVGATT_ONMOUSEOUT
constants in fglsvgcanvas.4gl for
convenience and code readability.
DEFINE n om.DomNode
LET n = fglsvgcanvas.rect(50,50,20,30,2,2)
CALL n.setAttribute(SVGATT_ONMOUSEOVER, "evt.target.setAttribute('opacity', '0.5');")
CALL n.setAttribute(SVGATT_ONMOUSEOUT, "evt.target.setAttribute('opacity', '1.0');")
The fglsvgcanvas web component can be configured to bind the
onmouseover/onmouseout
SVG mouse hovering events to ON ACTION
handlers and trigger code, when the mouse goes over the SVG elements.
onmouseover
and onmouseout
events occurred respectively before and after the onclick
SVG event.WEBCOMPONENT cv=FORMONLY.canvas,
...
PROPERTIES=(
...
mouse_over = "item_mouse_over",
mouse_out = "item_mouse_out",
mouse_event_timeout = 600,
mouse_event_focus = false
),
The mouse_over = "action-name"
property identifies the
ON ACTION action-name
handler to be triggered, when the
onmouseover
SVG event occurs. Use lowercase to define mouse hovering action
names.
The mouse_out = "action-name"
property identifies the
ON ACTION action-name
handler to be triggered, when the
onmouseout
SVG event occurs. Use lowercase to define mouse hovering action names.
Defining the mouse out action is optional.
mouse_event_timeout = milliseconds
is used to avoid network
clogging: This property defines the number of milliseconds to wait before sending the action, after
an SVG mouse event has occurred. A JavaScript setTimeout()
timer is created with
this value. The timer is re-initialized each time the event occurs. Default is 500 milliseconds.
mouse_event_focus = {true|false}
is boolean indicating if
the web component must have the focus in order to fire the mouse actions.
this
)
as parameter.- The
SVGATT_ONMOUSEOVER/"onmouseover"
SVG event must be bound toSVGVAL_ELEM_MOUSE_OVER/"elem_mouse_over(this)"
. - The
SVGATT_ONMOUSEOUT/"onmouseout"
SVG event must be bound toSVGVAL_ELEM_MOUSE_OUT/"elem_mouse_out(this)"
.
DEFINE n om.DomNode
...
LET n = fglsvgcanvas.rect(x, y, w, h, NULL, NULL)
CALL n.setAttribute(SVGATT_ONMOUSEOVER, SVGVAL_ELEM_MOUSE_OVER)
CALL n.setAttribute(SVGATT_ONMOUSEOUT, SVGVAL_ELEM_MOUSE_OUT)
ON ACTION
handlers to execute code, when the
actions are fired.mouse_event_focus=true
), the involved SVG element id is available in the field
value, like for the item selection action. If the mouse hovering action can be fired when the web
component field does not have the focus, use the fglsvgcanvas.getItemId()
function to get the id of the SVG
element....
ON ACTION item_mouse_over ATTRIBUTES(DEFAULTVIEW = NO)
MESSAGE SFMT("%1 : mouse over item : %2",
CURRENT HOUR TO FRACTION(5),
fglsvgcanvas.getItemId(cid) )
ON ACTION item_mouse_out ATTRIBUTES(DEFAULTVIEW = NO)
MESSAGE ""
...
Getting the bounding box of an SVG element
After rendering your SVG, it is possible to get the bounding box of an element.
With elements such as SVG text using a specific font, it is difficult to compute the bounding box of the text, until it has been rendered.
fglsvgcanvas.t_svg_rect
type, then (after displaying the SVG with
fglsvgcanvas.display(cid)
), call the fglsvgcanvas.getBBox(cid,
element-id)
function, to get the bounding box of the element
identified by
element-id:DEFINE rect fglsvgcanvas.t_svg_rect
...
ON ACTION get_bbox
CALL fglsvgcanvas.getBBox(cid, "label_23") RETURNING rect.*
DISPLAY rect.x, rect.y, rect.width, rect.height
The bounding box coordinates and size are returned in the current user space.
If the bounding box is required to place and size SVG element based on the position and size of
other elements, consider using SVG scripting instead of the getBBox()
function:
this will avoid several network roundtrips to render the final SVG image.
<svg version="1.1" baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
width="500px" height="500px" viewBox="0 0 2000 2000"
onload="setup()">
<script type="text/ecmascript">
// <![CDATA[
function setup_rect(box,bbox){
box.setAttributeNS(null, "x", bbox.x - 2);
box.setAttributeNS(null, "y", bbox.y - 2);
box.setAttributeNS(null, "width", bbox.width + 4);
box.setAttributeNS(null, "height", bbox.height + 4);
}
function setup(evt){
setup_rect( document.getElementById("label1Box"), label1.getBBox() );
}
// ]]>
</script>
<g id="label1">
<text x="150" y="250" font-family="Verdana" font-size="55">Hello everybody!</text>
</g>
<rect id="label1Box" stroke="red" stroke-width="3px" fill="none"/>
</svg>