cpsa-2.0.0: src/CPSA/Graph/SVG.hs
-- Exports SVG primitives.
-- Copyright (c) 2009 The MITRE Corporation
--
-- This program is free software: you can redistribute it and/or
-- modify it under the terms of the BSD License as published by the
-- University of California.
module CPSA.Graph.SVG where
import qualified Numeric as N
import CPSA.Graph.XMLOutput
import CPSA.Graph.Config
-- Show a coordinate as a length
showsL :: Float -> ShowS
showsL len s =
N.showFFloat (Just 3) len s
showL :: Float -> String
showL len = showsL len []
-- Shortens a line from (x1, y1) to (x2, y2) by d.
shorten :: Float -> Float -> Float -> Float -> Float -> (Float, Float)
shorten d x1 y1 x2 y2 =
(x3 + x1, y3 + y1)
where
dx, dy, fd :: Float
dx = x2 - x1
dy = y2 - y1
fd = 1.0 - d / sqrt (dx * dx + dy * dy)
x3 = fd * dx
y3 = fd * dy
-- Tooltip
tooltip :: String -> [Element] -> Element
tooltip tip content =
ec "a" [("xlink:title", tip)] content
-- Centered text
text :: Config -> Float -> Float -> String -> Element
text _ x y content =
mc "text" [("x", showL x), ("y", showL y), ("style", style)] content
where
style = props [("text-anchor", "middle")]
-- Circle with radius from configuration
circ :: Config -> Bool -> Float -> Float -> Element
circ conf color x y =
ec "circle" attrs []
where
dims = [("cx", showL x), ("cy", showL y), ("r", showL (br conf))]
style = ("style", props [("fill", "red")])
attrs = if color then (style : dims) else dims
rect :: Config -> Float -> Float -> Float -> Float -> Element
rect conf x y w h =
ec "rect" [("x", showL x), ("y", showL y),
("width", showL w), ("height", showL h),
("style", props style)] []
where
style = [("fill", "none"), ("stroke", "black"),
("stroke-width", showL (stroke conf))]
-- Gray line
line :: Config -> Float -> Float -> Float -> Float -> Element
line conf x1 y1 x2 y2 =
ec "line" [("x1", showL x1), ("y1", showL y1),
("x2", showL x2), ("y2", showL y2),
("style", props style)] []
where
style = [("stroke-width", showL (stroke conf)), ("stroke", "gray")]
-- An arrow that is curved when its horizontal length exceeds a limit.
arrow :: Config -> Bool -> Float -> Float -> Float -> Float -> Element
arrow conf solid x1 y1 x3 y3 =
ec "path" [("d", path), ("style", props style)] []
where
(x2, y2) = shorten (br conf) x1 y1 x3 y3
path = showString "M " $
showsL x1 $ showChar ' ' $ showsL y1 $ showString " Q " $
showsL xq $ showChar ' ' $ showsL yq $ showChar ' ' $
showsL x2 $ showChar ' ' $ showL y2
xq = (x1 + x2) / 2
yq = (y1 + y2 - delta) / 2
delta = if abs (x1 - x2) > dx conf then dy conf else 0
style = if solid then solidStyle else dashedStyle
solidStyle =
[("stroke-width", showL (stroke conf)),
("stroke", "black"),
("marker-end", "url(#arrow)"),
("fill", "none")]
dashedStyle = ("stroke-dasharray",
showsL (dash conf) $ showString "," $ showL (gap conf))
: solidStyle
data ButtonKind
= AliveTree -- tree node is alive
| AliveDup -- duplicate node is alive
| DeadTree -- tree node is dead
| DeadDup -- duplication node is dead
-- A button that displays a preskeleton
kbutton :: Config -> Float -> Float -> ButtonKind -> Int -> Element
kbutton conf x y kind label =
mc "text" attrs (show label)
where
attrs = [("x", showL x), ("y", showL y), ("style", props style),
("onclick", onclick conf label)]
style = italic kind [("text-anchor", "middle"), ("fill", color kind)]
color AliveTree = "black"
color AliveDup = "green"
color DeadTree = "red"
color DeadDup = "orange"
italic AliveTree props = props
italic AliveDup props = ("font-style", "italic"):props
italic DeadTree props = props
italic DeadDup props = ("font-style", "italic"):props
onclick :: Config -> Int -> String
onclick conf label =
if compact conf then
showString "showk(evt, \"k" $ shows label "\")"
else
showString "window.open(\"#i" $ shows label "\", \"_self\")"
-- A button that displays a tree and its first preskeleton
tbutton :: Config -> Float -> Float -> Int -> Element
tbutton _ x y label =
mc "text" attrs (content [])
where
attrs = [("x", showL x), ("y", showL y),
("style", style), ("onclick", script)]
style = props [("text-anchor", "middle")]
script = showString "showt(evt, \"t" $ content $
showString "\", \"k" $ content "\")"
content = shows label
-- The script for the buttons
ecmascript :: String
ecmascript =
"\nvar t;\n\
\var k;\n\n\
\function init(evt) {\n\
\ t = evt.target.ownerDocument.getElementById(\"t0\");\n\
\ t.setAttribute(\"visibility\", \"visible\");\n\
\ k = evt.target.ownerDocument.getElementById(\"k0\");\n\
\ k.setAttribute(\"visibility\", \"visible\");\n\
\}\n\n\
\function showk(evt, kid) {\n\
\ k.setAttribute(\"visibility\", \"hidden\");\n\
\ k = evt.target.ownerDocument.getElementById(kid);\n\
\ k.setAttribute(\"visibility\", \"visible\");\n\
\}\n\n\
\function showt(evt, tid, kid) {\n\
\ showk(evt, kid);\n\
\ t.setAttribute(\"visibility\", \"hidden\");\n\
\ t = evt.target.ownerDocument.getElementById(tid);\n\
\ t.setAttribute(\"visibility\", \"visible\");\n\
\}\n"
script :: Element
script = mc "script" [("type", "application/ecmascript")] ecmascript
-- The marker for arrow heads
defs :: Config -> Element
defs _ =
ec "defs" [] [marker]
where
marker =
ec "marker"
[("id", "arrow"), ("orient", "auto"),
("markerWidth", "5"), ("markerHeight", "10"),
("refX", "5"), ("refY", "5")] [path]
path = ec "path" [("d", "M 0 0 5 5 0 10"), ("style", props style)] []
style = [("stroke-width", "2"), ("fill", "none"), ("stroke", "black")]