EditDistViz.hs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. {-# LANGUAGE OverloadedStrings #-}
  2. -- ghc --make EditDistViz.hs -main-is EditDistViz.main
  3. module EditDistViz where
  4. import Data.Array
  5. import Data.List (intersperse)
  6. import Control.Arrow ((***))
  7. import qualified Data.Set as S
  8. -- write file
  9. import System.IO
  10. data Cell a = Val {val::Int, reasons::[Reason a]}
  11. deriving (Show)
  12. data Reason a = Init | X a | Y a | Eq a | Neq a
  13. deriving (Show)
  14. data SVG = SVG {mainElement::XMLElement}
  15. data XMLString = XMLString {unescaped::String}
  16. data XMLAttribute = Attr {attrName::XMLString, attrValue::XMLString}
  17. data XMLElement = Tag {tagName::XMLString, attributes::[XMLAttribute], content::[Either XMLElement XMLString]}
  18. instance Show XMLString where
  19. show (XMLString unescaped) = concatMap f unescaped
  20. where f '<' = "&lt;"
  21. f '>' = "&gt;"
  22. f '&' = "&amp;"
  23. f '\'' = "&apos;"
  24. f '"' = "&quot;"
  25. f x = [x]
  26. mkTag x a =
  27. Tag
  28. (XMLString x)
  29. (map (uncurry Attr . (XMLString *** XMLString)) a)
  30. rgb r g b =
  31. "rgb(" ++ show (255 * r) ++
  32. "," ++ show (255 * g) ++
  33. "," ++ show (255 * b) ++ ")"
  34. red = rgb 1 0 0
  35. yello = rgb 1 1 0
  36. green = rgb 0 1 0
  37. cyan = rgb 0 1 1
  38. blu = rgb 0 0 1
  39. purpl = rgb 1 0 1
  40. {- trace successful path back from (m,n) point to origin -}
  41. successpath table =
  42. let (_, (m,n)) = bounds table
  43. aux' (0,0) = []
  44. -- arbitrary preference? no, let's show all successful paths.
  45. aux' (i,j) =
  46. let diag = any (\x -> case x of (Eq _) -> True ; (Neq _) -> True ; _ -> False) (reasons (table ! (i,j)))
  47. horz = any (\x -> case x of (X _) -> True ; _ -> False) (reasons (table ! (i,j)))
  48. vert = any (\x -> case x of (Y _) -> True ; _ -> False) (reasons (table ! (i,j)))
  49. in
  50. [(i-1,j-1) | diag] ++ [(i-1,j) | horz] ++ [(i,j-1) | vert]
  51. aux front accum =
  52. case S.minView front of
  53. Nothing -> accum
  54. Just ((i,j), s) ->
  55. let contributions = aux' (i,j)
  56. in
  57. aux (S.union s (S.fromList contributions))
  58. (S.union accum (S.fromList (map (\x -> (x, (i,j))) contributions)))
  59. in
  60. aux (S.fromList [(m,n)]) (S.empty)
  61. toSVG :: Array (Int,Int) (Cell Char) -> SVG
  62. toSVG table =
  63. let (_, (m,n)) = bounds table
  64. -- square edge width
  65. sqmeasure = 50.0 :: Float
  66. rmeasure = 5.0
  67. -- stroke line width:
  68. smeasure = 1.0
  69. width = sqmeasure * fromIntegral m
  70. height = sqmeasure * fromIntegral n
  71. valmaxf = fromIntegral $ maximum (map val (elems table))
  72. palette = cycle [red, yello, green, cyan, blu, purpl]
  73. s_path = S.toList (successpath table)
  74. in
  75. SVG $
  76. mkTag "svg"
  77. [("xmlns","http://www.w3.org/2000/svg"), ("width", show width), ("height", show height)
  78. ,("viewBox", show (-0.5*sqmeasure) ++ " " ++ show (-0.5*sqmeasure) ++ " " ++ show (width + 1*sqmeasure) ++ " " ++ show (height + 1*sqmeasure))
  79. ]
  80. $
  81. -- white background
  82. [ Left (mkTag "rect" [("x", show $ -0.5*sqmeasure),
  83. ("y", show $ -0.5*sqmeasure),
  84. ("width", show $ width + 1*sqmeasure),
  85. ("height", show $ height + 1*sqmeasure),
  86. ("fill", "white")
  87. ] [])]
  88. ++
  89. -- horizontal lettering
  90. [ Left (mkTag "text"
  91. [("x", show x), ("y", show (-sqmeasure/4)),
  92. ("fill", rgb 0.0 0.0 0.0)
  93. ]
  94. [Right $ XMLString c])
  95. | i <- [1..m]
  96. , let c = case reasons (table ! (i, 0)) of [X c] -> [c] ; _ -> ""
  97. , let x = fromIntegral i * sqmeasure - 0.5 * sqmeasure ]
  98. ++
  99. -- vertical lettering
  100. [ Left (mkTag "text"
  101. [("y", show y), ("x", show (-sqmeasure/4)),
  102. ("fill", rgb 0.0 0.0 0.0)
  103. ]
  104. [Right $ XMLString c])
  105. | j <- [1..n]
  106. , let c = case reasons (table ! (0, j)) of [Y c] -> [c] ; _ -> ""
  107. , let y = fromIntegral j * sqmeasure - 0.5 * sqmeasure ]
  108. ++
  109. -- little square tiles
  110. [ Left (mkTag "rect"
  111. [("x", show rx), ("y", show ry),
  112. ("width", show sqmeasure),
  113. ("height", show sqmeasure),
  114. ("fill", color),
  115. ("fill-opacity", show 1.0),
  116. ("stroke", color),
  117. ("stroke-width", show smeasure)
  118. ] [])
  119. | i <- [0..m-1], j <- [0..n-1]
  120. , let rx = fromIntegral i * sqmeasure
  121. , let ry = fromIntegral j * sqmeasure
  122. , let vf = fromIntegral $ val (table ! (i+1,j+1))
  123. , let shade = vf / valmaxf
  124. , let color = rgb shade shade shade ]
  125. ++
  126. -- highlight successful path(s)
  127. [ Left (mkTag "line"
  128. [("x1", show x1)
  129. ,("y1", show y1)
  130. ,("x2", show x2)
  131. ,("y2", show y2)
  132. ,("stroke", color)
  133. ,("stroke-width", show s'measure)]
  134. [])
  135. | ((i',j'), (i,j)) <- s_path
  136. -- a nice possibility: draw a thicker line first
  137. , let s'measure = 5.0
  138. , let x1 = fromIntegral i' * sqmeasure
  139. , let y1 = fromIntegral j' * sqmeasure
  140. , let x2 = fromIntegral i * sqmeasure
  141. , let y2 = fromIntegral j * sqmeasure
  142. , let color = rgb 0.0 0.0 1.0 ]
  143. ++
  144. -- dashed lines for same letter
  145. [ Left (mkTag "line"
  146. [("x1", show x1)
  147. ,("y1", show y1)
  148. ,("x2", show x2)
  149. ,("y2", show y2)
  150. ,("stroke", color)
  151. ,("stroke-width", show s'measure)
  152. ,("stroke-dasharray", show 5)]
  153. [])
  154. | i <- [0..m-1], j <- [0..n-1]
  155. , let s'measure = smeasure
  156. , let x1 = fromIntegral i * sqmeasure
  157. , let y1 = fromIntegral j * sqmeasure
  158. , let x2 = fromIntegral (i+1) * sqmeasure
  159. , let y2 = fromIntegral (j+1) * sqmeasure
  160. , let rr = reasons (table ! (i+1,j+1))
  161. , any (\x -> case x of (Eq _) -> True ; _ -> False) rr
  162. , let color = palette !! val (table ! (i, j)) ]
  163. ++
  164. -- horizontal
  165. [ Left (mkTag "line"
  166. [("x1", show x1)
  167. ,("y1", show y1)
  168. ,("x2", show x2)
  169. ,("y2", show y2)
  170. ,("stroke", color)
  171. ,("stroke-width", show s'measure)]
  172. [])
  173. | i <- [0..m-1], j <- [0..n-1]
  174. , let s'measure = smeasure
  175. , let x1 = fromIntegral i * sqmeasure
  176. , let y1 = fromIntegral (j+1) * sqmeasure
  177. , let x2 = fromIntegral (i+1) * sqmeasure
  178. , let y2 = fromIntegral (j+1) * sqmeasure
  179. , let rr = reasons (table ! (i+1,j+1))
  180. , any (\x -> case x of (X _) -> True ; _ -> False) rr
  181. , let color = palette !! val (table ! (i, j)) ]
  182. ++
  183. -- vertical
  184. [ Left (mkTag "line"
  185. [("x1", show x1)
  186. ,("y1", show y1)
  187. ,("x2", show x2)
  188. ,("y2", show y2)
  189. ,("stroke", color)
  190. ,("stroke-width", show s'measure)]
  191. [])
  192. | i <- [0..m-1], j <- [0..n-1]
  193. , let s'measure = smeasure
  194. , let x1 = fromIntegral (i+1) * sqmeasure
  195. , let y1 = fromIntegral j * sqmeasure
  196. , let x2 = fromIntegral (i+1) * sqmeasure
  197. , let y2 = fromIntegral (j+1) * sqmeasure
  198. , let rr = reasons (table ! (i+1,j+1))
  199. , any (\x -> case x of (Y _) -> True ; _ -> False) rr
  200. , let color = palette !! val (table ! (i, j))]
  201. ++
  202. -- diagonal, different letter
  203. [ Left (mkTag "line"
  204. [("x1", show x1)
  205. ,("y1", show y1)
  206. ,("x2", show x2)
  207. ,("y2", show y2)
  208. ,("stroke", color)
  209. ,("stroke-width", show s'measure)]
  210. [])
  211. | i <- [0..m-1], j <- [0..n-1]
  212. , let s'measure = smeasure
  213. , let x1 = fromIntegral i * sqmeasure
  214. , let y1 = fromIntegral j * sqmeasure
  215. , let x2 = fromIntegral (i+1) * sqmeasure
  216. , let y2 = fromIntegral (j+1) * sqmeasure
  217. , let rr = reasons (table ! (i+1,j+1))
  218. , any (\x -> case x of (Neq _) -> True ; _ -> False) rr
  219. , let color = palette !! val (table ! (i, j)) ]
  220. ++
  221. [ Left (mkTag "circle"
  222. [("cx", show cx), ("cy", show cy), ("r", show rmeasure),
  223. ("fill", rgb 0.0 0.0 (vf / valmaxf)),
  224. ("fill-opacity", show 1)
  225. ] [])
  226. | i <- [0..m], j <- [0..n]
  227. , let cx = fromIntegral i * sqmeasure
  228. , let cy = fromIntegral j * sqmeasure
  229. , let vf = fromIntegral $ val (table ! (i,j)) ]
  230. instance Show SVG where
  231. show (SVG elt) = show elt
  232. instance Show XMLElement where
  233. show (Tag name attrs content) =
  234. concat $ ["<", show name] ++
  235. [" "| not (null attrs)] ++
  236. intersperse " " (map show attrs) ++ ["/>" | null content] ++
  237. [">" | not (null content)] ++
  238. [case c of
  239. Left c -> show c
  240. Right c -> show c | c <- content] ++
  241. ["</" ++ show name ++ ">" | not (null content)]
  242. instance Show XMLAttribute where
  243. show (Attr name value) =
  244. concat [show name, "=\"", show value, "\""]
  245. edtable :: (Eq a) => [a] -> [a] -> Array (Int, Int) (Cell a)
  246. edtable v w =
  247. let res = array ((0,0), (length v , length w)) $
  248. [((0,0), Val 0 [Init])] ++
  249. [((x,0), Val x [X vx]) | (x,vx) <- zip [1..] v] ++
  250. [((0,y), Val y [Y wy]) | (y,wy) <- zip [1..] w] ++
  251. [((x,y), k)
  252. | (x,vx) <- zip [1..] v
  253. , (y,wy) <- zip [1..] w
  254. , let nx = 1 + val (res ! (x-1, y))
  255. ny = 1 + val (res ! (x, y-1))
  256. nd = (if vx == wy then 0 else 1) + val(res ! (x-1, y-1))
  257. n = minimum [nx, ny, nd]
  258. , let k = Val n $
  259. [X vx | n == nx] ++
  260. [Y wy | n == ny] ++
  261. [Eq vx | n == nd, vx == wy] ++
  262. [Neq vx | n == nd, vx /= wy]
  263. ]
  264. in res
  265. -- (s1,s2) = ("preternatural", "unintentional")
  266. (s1,s2) = ("efghabcd", "abcdefgh")
  267. main :: IO ()
  268. main = do
  269. (pathOfTempFile, h) <- openTempFile "/tmp" "edtable.svg"
  270. putStrLn ("Writing to " ++ pathOfTempFile)
  271. hPutStr h $ show $ toSVG (edtable s1 s2)
  272. hClose h