Example program - Using SVG for vector graphics


SVG is the W3C standard markup language for vector graphics. It is both powerful and compact supporting advanced features such as animation, scripting and user interaction. It exploits existing W3C standards such as XML, DOM, CSS and Javascript which will be familiar to most developers.

SVG can be rendered in the KCML client by embedding the Adobe SVG viewer OCX/ActiveX. This OCX component will also add SVG support to Firefox and Internet Explorer neither of which will render vector grahics on their own. However there are Firefox and Mozilla builds available with native SVG support.

This example builds an XML DOM on the server containing the SVG markup for the picture. The DOM is then serialized to a file on the server and copied down to the client using COPY OBJECT. In the show() event the client will tell the OCX to load the file from the cache having discovered its location using the 'KCMLGetCacheFileName() internal $DECLARE.

This is not intended to be an SVG showcase and it just creates a few primitives such as rectangles and lines. You could use an SVG document or fragment created using other SVG applications such as Adobe Illustrator in a more complex example.

// Create SVG drawing object to display on a form using Adobe SVG viewer ActiveX
// 
// While it would be neat to be able to do this in place using the DOM there are 
// a number of practical shortcoming.  The MS HTML control has no native SVG support 
// (nor has Firefox 1) so we must use the Adobe ActiveX.  It has no implementation support
// for creating an empty document so we must create the document as a file separately.  Theoretically
// you should be able to manipulate the SVG DOM using getSVGDocument() but I have been unable
// to get this to work.
// The approach here is to create an SVG document as a file on the server and copy it down
// to the client.
DIM OBJECT x, OBJECT imp, OBJECT doc, OBJECT node, OBJECT a, OBJECT n, OBJECT dtd, OBJECT root
DIM xml$0, OBJECT g
OBJECT x = CREATE "dynamic", "dyndom"
REM get the DOM implementation
OBJECT imp = x.Implementation
// standard DOCTYPE for SVG docs (required)
OBJECT dtd = imp.createDocumentType("svg", "-//W3C//DTD SVG 1.0//EN", "http://www.w3.org/TR/SVG/DTD/svg10.dtd")
// create an empty SVG document with namespace and DTD
DIM SVG_namespace$="http://www.w3.org/2000/svg"
OBJECT doc = imp.createDocument(0, "svg", OBJECT dtd)
doc.Encoding$ = "UTF-8"
// get the root node
OBJECT root = doc.DocumentElement
root.setAttribute("viewBox", "0 0 270 400")
root.setAttribute("xml:space", "preserve")
// inject some CSS stuff
DIM css$0
REDIM css$ = ".str {stroke-width:1} " & ".fnt {font-size:20;font-family:'Arial'} " & " .red {fill:red}" & " .blue {fill:blue}" & " .yellow {fill:yellow}" & " .green {fill:green}" & " .frn {fill-rule:nonzero}"
OBJECT n = doc.createElement("defs")
root.appendChild(OBJECT n)
// add a comment infront of this tag
OBJECT a = doc.createComment("CSS styles")
root.insertBefore(OBJECT a, OBJECT n)
OBJECT a = doc.createElement("style")
n.appendChild(OBJECT a)
a.setAttribute("type", "text/css")
OBJECT n = doc.createCDATASection(css$)
a.appendChild(OBJECT n)
OBJECT a = NULL
// main drawing layer
OBJECT g = doc.createElement("g")
root.appendChild(OBJECT g)
g.setAttribute("id", "mainlayer")
// add a rect
OBJECT n = 'NewObj(OBJECT g, "rect", "red str")
'Position(OBJECT n, 15, 15)
'Size(OBJECT n, 50, 100)
'caption(OBJECT g, "rect", 44, 88)
// add rounded rect
OBJECT n = 'NewObj(OBJECT g, "rect", "blue str")
'Position(OBJECT n, 150, 15)
'Size(OBJECT n, 50, 100)
n.setAttribute("rx", "12")
n.setAttribute("ry", "18")
'caption(OBJECT g, "rounded rect", 144, 88)
'Position(OBJECT n, 150, 15)
// add some lines as axes
'straightline(0, 250, 200, 250)
'straightline(0, 250, 0, 100)
// and a polyline as the graph
OBJECT n = 'NewObj(OBJECT g, "polyline", "str")
n.setAttribute("style", "fill:none; stroke: green;")
n.setAttribute("points", "0,250 10,220 80,200 120,160")
// add a circle
OBJECT n = 'NewObj(OBJECT g, "circle", "yellow str")
n.setAttribute("cx", "300")
n.setAttribute("cy", "300")
n.setAttribute("r", "50")
// and an ellipse
OBJECT n = 'NewObj(OBJECT g, "ellipse", "green str")
n.setAttribute("cx", "0")
n.setAttribute("cy", "300")
n.setAttribute("rx", "50")
n.setAttribute("ry", "20")
// and animate the ellipse
OBJECT n = 'NewObj(OBJECT n, "animate")
n.setAttribute("attributeName", "fill")
n.setAttribute("from", "green")
n.setAttribute("to", "red")
n.setAttribute("begin", "load")
n.setAttribute("dur", "2s")
n.setAttribute("fill", "restore")
// serialize doc to a string
REDIM xml$ = 'serialize$(OBJECT x, OBJECT doc)
// drop the DOM
OBJECT n = NULL
OBJECT g = NULL
OBJECT root = NULL
OBJECT doc = NULL
OBJECT dtd = NULL
OBJECT imp = NULL
OBJECT x = NULL
// write to a temp file on the server
DIM tmpfile$0, hFile, nLen
REDIM tmpfile$ = $PRINTF("/tmp/svgdoc_%d.svg", #PART)
hFile = OPEN tmpfile$, "w+"
nLen = WRITE #hFile, xml$
CLOSE #hFile
// and copy it into the client cache
// the filename should be unique across all forms
DIM cachefile$="svgdoc"
COPY OBJECT tmpfile$ TO cachefile$
REMOVE tmpfile$
// open the form
DEFFORM SVGTestForm()={.form,.form$,.Style=0x50c000c4,.Width=528,.Height=288,.Text$="SVG demo",.Id=1024,.Show()#},{.ok,.button$,.Style=0x50010001,.Left=474,.Top=4,.Width=50,.Height=14,.Text$="OK",.__Anchor=5,.Id=1},{.cancel,.button$,.Style=0x50010000,.Left=474,.Top=20,.Width=50,.Height=14,.Text$="Cancel",.__Anchor=5,.Id=2},{.ocxControl1,.kcmlocx$,.Style=0x50010000,.Left=59,.Top=18,.Width=379,.Height=234,.Id=1002,.Definition=.lblControl1},{.lblControl1,.ocxdef$,.OCX$="Adobe.SVGCtl"}
LOCAL DIM OBJECT h, svgfile$0
DEFEVENT SVGTestForm.Show()
	OBJECT h = .ocxControl1
	REDIM svgfile$ = "file://" & 'GetCacheFile$(BYREF cachefile$)
	h.setSrc(svgfile$)
END EVENT
FORM END
SVGTestForm.Open()
END
DEFSUB 'serialize$(OBJECT x, OBJECT doc)
	// serialize tree
	LOCAL DIM OBJECT impls, OBJECT writer, OBJECT s
	// create a writer object and set attributes
	OBJECT impls = x.getDOMImplementation("LS")
	OBJECT writer = impls.createDOMWriter()
	writer.setFeature("format-pretty-print", TRUE)
	writer.NewLine$ = HEX(0A)
	// print to a string
	OBJECT s = writer.writeDocToString(OBJECT doc)
	RETURN s.Text$
END SUB
DEFSUB 'NewObj(OBJECT parent, type$, class$="")
	LOCAL DIM OBJECT n
	OBJECT n = parent.OwnerDocument.createElement(type$)
	parent.appendChild(OBJECT n)
	IF (class$ != " ")
		n.setAttribute("class", class$)
	END IF
	RETURN OBJECT n
END SUB
DEFSUB 'Position(OBJECT node, x, y)
	// add position
	node.setAttribute("x", $PRINTF("%d", x))
	node.setAttribute("y", $PRINTF("%d", y))
END SUB
DEFSUB 'Size(OBJECT node, height, width)
	// add position
	node.setAttribute("height", $PRINTF("%d", height))
	node.setAttribute("width", $PRINTF("%d", width))
END SUB
DEFSUB 'lineends(OBJECT n, x1, y1, x2, y2)
	n.setAttribute("x1", $PRINTF("%d", x1))
	n.setAttribute("y1", $PRINTF("%d", y1))
	n.setAttribute("x2", $PRINTF("%d", x2))
	n.setAttribute("y2", $PRINTF("%d", y2))
END SUB
DEFSUB 'straightline(x1, y1, x2, y2)
	LOCAL DIM OBJECT n
	OBJECT n = 'NewObj(OBJECT g, "line", "")
	'lineends(OBJECT n, x1, y1, x2, y2)
	n.setAttribute("style", "fill:none; stroke: black; stroke-width:2")
	RETURN OBJECT n
END SUB
DEFSUB 'caption(OBJECT parent, text$, x, y)
	// text caption
	LOCAL DIM OBJECT n, OBJECT t, OBJECT doc
	OBJECT doc = parent.OwnerDocument
	OBJECT n = doc.createElement("text")
	parent.appendChild(OBJECT n)
	n.setAttribute("class", "fnt")
	'Position(OBJECT n, x, y)
	OBJECT t = doc.createTextNode(text$)
	n.appendChild(OBJECT t)
	RETURN OBJECT n
END SUB
DEFSUB 'GetCacheFile$(BYREF file$)
	// get directory on client used for cache
	$DECLARE 'KCMLGetCacheFileName(STR(),RETURN STR(),INT())
	LOCAL DIM cache$_MAX_PATH
	'KCMLGetCacheFileName(file$, cache$, LEN(STR(cache$)))
	RETURN cache$
END SUB

When creating your own forms you need to install the OCX in the forms editor (File|Install OCX...) before it will appear in the component palette. It is listed as "SVG document" and has a ProgId of "Adobe.SVGCtl".

An alternative approach might be to create the graphic as an XML file in a directory exposed through the web server component of the Connection Manager or through an Apache web server. Ensure the web server uses the correct MIME type (image/svg+xml) however. You could then launch an SVG enabled Firefox or IE session to view it using a $DECLARE of ShellExecute().

As far as I know it is not possible for the Adobe OCX to generate an event that KCML can handle on the server so while client side events are possible, they remain in the client. However a Javascript handler in the session could use XMLHttpRequest to post a SOAP call back to a web server.

See also

SVG zone [www.adobe.com]
SVG example site [www.carto.net]
SVG interactivity tutorial [ibm.com]