{-# LANGUAGE BangPatterns, FlexibleContexts #-}-- | This module contain functions for drawing diagrams of-- dendrograms.moduleDiagrams.Dendrogram(-- * High-level interface-- $runnableExampledendrogram,Width(..)-- * Low-level interface,dendrogramPath,fixedWidth,variableWidth,X,hcatB)where-- from baseimportControl.Arrow(first,second)-- from hierarchical-clusteringimportData.Clustering.Hierarchical(Dendrogram(..),elements)-- from diagrams-libimportDiagrams.Prelude-- $runnableExample---- Given a dendrogram @dendro :: 'Dendrogram' a@ and a function-- @drawItem :: a -> Diagram b R2@ for drawing the items on the-- leaves of @dendro@, just use @'dendrogram' 'Variable' drawItem-- dendro :: Diagram b R2@ to draw a diagram of @dendro@.---- Runnable example which produces something like-- <https://patch-tag.com/r/felipe/hierarchical-clustering-diagrams/snapshot/current/content/pretty/example.png>:---- @--import Data.Clustering.Hierarchical (Dendrogram(..))--import Diagrams.Prelude (Diagram, R2, atop, lw, pad, roundedRect, text, (\#))--import Diagrams.Backend.Cairo.CmdLine (Cairo, defaultMain)--import qualified Diagrams.Dendrogram as D----main :: IO ()--main = defaultMain diagram----diagram :: Diagram Cairo R2--diagram = D.'dendrogram' 'Fixed' char test \# lw 0.1 \# pad 1.1----char :: Char -> Diagram Cairo R2--char c = pad 1.3 $ roundedRect (1,1) 0.1 \`atop\` text [c]----test :: Dendrogram Char--test = Branch 5-- (Branch 2-- (Branch 1-- (Leaf \'A\')-- (Leaf \'B\'))-- (Leaf \'C\'))-- (Leaf \'D\')-- @-- | @dendrogram width drawItem dendro@ is a drawing of the-- dendrogram @dendro@ using @drawItem@ to draw its leafs. The-- @width@ parameter controls how whether all items have the same-- width or not ('Fixed' or 'Variable', respectively, see-- 'Width').---- Note: you should probably use 'alignT' to align your items.dendrogram::(Monoidm,Renderable(PathR2)b)=>Width->(a->AnnDiagrambR2m)->Dendrograma->AnnDiagrambR2mdendrogramwidth_drawItemdendro=(strokepath_#valuemempty)===(items#alignL)where(path_,items)=casewidth_ofFixed->letdrawnItems=mapdrawItem(elementsdendro)w=width(headdrawnItems)(dendro',_)=fixedWidthwdendroin(dendrogramPathdendro',hcatBdrawnItems)Variable->firstdendrogramPath$variableWidthdrawItemdendro-- | The width of the items on the leafs of a dendrogram.dataWidth=Fixed-- ^ @Fixed@ assumes that all items have a fixed width-- (which is automatically calculated). This mode is-- faster than @Variable@, especially when you have many-- items.|Variable-- ^ @Variable@ does not assume that all items have a fixed-- width, so each item may have a different width. This-- mode is slower since it has to calculate the width of-- each item separately.-- | A dendrogram path that can be 'stroke'd later. This function-- assumes that the 'Leaf'@s@ of your 'Dendrogram' are already in-- the right position.dendrogramPath::DendrogramX->PathR2dendrogramPath=mconcat.fst.go[]wheregoacc(Leafx)=(acc,(x,0))goacc(Branchdlr)=(path:acc'',pos)where(acc',(!xL,!yL))=goaccl(acc'',(!xR,!yR))=goacc'rpath=fromVertices[P(xL,yL),P(xL,d),P(xR,d),P(xR,yR)]pos=((xL+xR)/2,d)-- | The horizontal position of a dendrogram Leaf.typeX=Double-- | @fixedWidth w@ positions the 'Leaf'@s@ of a 'Dendrogram'-- assuming that they have the same width @w@. Also returns the-- total width.fixedWidth::Double->Dendrograma->(DendrogramX,Double)fixedWidthw=second(subtracthalf_w).gohalf_wwherehalf_w=w/2go!y(Leaf_)=(Leafy,y+w)go!y(Branchdlr)=(Branchdl'r',y'')where(l',!y')=goyl(r',!y'')=goy'r-- | @variableWidth draw@ positions the 'Leaf'@s@ of a-- 'Dendrogram' according to the diagram generated by 'draw'.-- Each 'Leaf' may have a different width. Also returns the-- resulting diagram having all 'Leaf'@s@ drawn side-by-side.---- Note: you should probably use 'alignT' to align your items.variableWidth::(Monoidm)=>(a->AnnDiagrambR2m)->Dendrograma->(DendrogramX,AnnDiagrambR2m)variableWidthdraw=finish.go0[]wherego!yacc(Leafa)=(Leafy',y'',dia:acc)wheredia=drawa!w=widthdia!y'=y+w/2!y''=y+wgo!yacc(Branchdlr)=(Branchdl'r',y'',acc'')where(l',!y',acc'')=goyacc'l-- yes, this is acc'(r',!y'',acc')=goy'accrfinish(dendro,_,dias)=(dendro,hcatBdias)-- We used to concatenate diagrams inside 'go' using (|||).-- However, pathological dendrograms (such as those created-- using single linkage) may be highly unbalanced, creating-- a performance problem for 'variableWidth'.-- | Like 'hcat', but balanced. Much better performance. Use it-- for concatenating the items of your dendrogram.hcatB::Monoidm=>[AnnDiagrambR2m]->AnnDiagrambR2mhcatB[y]=yhcatBys=hcatB$dubsyswheredubs(x1:x2:xs)=x1|||x2:dubsxsdubs[x]=[x]dubs[]=[]