-- | -- Module : Graphics.Text.Annotation -- Copyright : (c) Justus Sagemüller 2015 -- License : GPL v3 -- -- Maintainer : (@) sagemueller $ geo.uni-koeln.de -- Stability : experimental -- Portability : requires GHC>6 extensions {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE StandaloneDeriving #-} module Graphics.Text.Annotation where import Graphics.Dynamic.Plot.Colour import Graphics.Dynamic.Plot.Internal.Types import qualified Prelude import Diagrams.Prelude ((^&), (&), _x, _y, (|||), (===)) import qualified Diagrams.Prelude as Dia import qualified Diagrams.TwoD.Size as Dia import qualified Diagrams.TwoD.Types as DiaTypes import qualified Diagrams.TwoD.Text as DiaTxt import Diagrams.BoundingBox (BoundingBox) import qualified Diagrams.BoundingBox as DiaBB import qualified Diagrams.Backend.Cairo as Cairo import qualified Diagrams.Backend.Cairo.Text as CairoTxt import Control.Monad.Trans (liftIO) import qualified Control.Category.Hask as Hask import Control.Category.Constrained.Prelude hiding ((^)) import Control.Arrow.Constrained hiding ((|||)) import Control.Monad.Constrained import Control.Lens hiding ((...), (<.>)) import Data.List (foldl', sort, intercalate, isPrefixOf, isInfixOf, find, zip4) import qualified Data.Vector as Arr import Data.Maybe import Data.Semigroup import Data.Foldable (fold, foldMap) import Data.Function (on) import Data.VectorSpace import Data.Basis import Data.AffineSpace import Data.Manifold.PseudoAffine import Data.Manifold.TreeCover import qualified Data.Map.Lazy as Map import Data.Tagged import Text.Printf prettyFloatShow :: Int -> Double -> String prettyFloatShow _ 0 = "0" prettyFloatShow preci x | preci >= 0, preci < 4 = show $ round x | preci < 0, preci > -2 = printf "%.1f" x | otherwise = case ceiling (0.01 + lg (abs x/10^^(preci+1))) + preci of 0 | preci < 0 -> printf "%.*f" (-preci) x expn | expn>preci -> printf "%.*f×₁₀%s" (expn-preci) (x/10^^expn) (showExponentAsSuperscript expn) | otherwise -> printf "%i×₁₀%s" (round $ x/10^^expn :: Int) (showExponentAsSuperscript expn) showExponentAsSuperscript :: Int -> String showExponentAsSuperscript = map sup . show where sup ch = case lookup ch $ zip "0123456789-" "⁰¹²³⁴⁵⁶⁷⁸⁹⁻" of Just ch -> ch maybeRead :: Read a => String -> Maybe a maybeRead = fmap fst . listToMaybe . reads data Annotation = Annotation { getAnnotation :: AnnotationObj , placement :: AnnotationPlace , isOptional :: Bool } data AnnotationObj = TextAnnotation TextObj TextAlignment data AnnotationPlace = ExactPlace R2 data TextObj = PlainText String data TextAlignment = TextAlignment { hAlign, vAlign :: Alignment } -- , blockSpread :: Bool } data Alignment = AlignBottom | AlignMid | AlignTop type TxtStyle = Dia.Style Dia.V2 R data DiagramTK = DiagramTK { textTools :: TextTK, viewScope :: GraphWindowSpecR2 } data TextTK = TextTK { txtCairoStyle :: TxtStyle , txtSize, xAspect, padding, extraTopPad :: R } defaultTxtStyle :: TxtStyle defaultTxtStyle = mempty & Dia.fontSizeO 9 & Dia.fc Dia.grey & Dia.lc Dia.grey prerenderAnnotation :: DiagramTK -> Annotation -> IO PlainGraphicsR2 prerenderAnnotation (DiagramTK{ textTools = TextTK{..}, viewScope = GraphWindowSpecR2{..} }) (Annotation{..}) | TextAnnotation (PlainText str) (TextAlignment{..}) <- getAnnotation , ExactPlace p₀ <- placement = do let dtxAlign = DiaTxt.BoxAlignedText (case hAlign of {AlignBottom -> 0; AlignMid -> 0.5; AlignTop -> 1}) (case vAlign of {AlignBottom -> 0; AlignMid -> 0.5; AlignTop -> 1}) rnTextLines <- mapM (CairoTxt.textVisualBoundedIO txtCairoStyle . DiaTxt.Text mempty dtxAlign ) $ lines str let lineWidths = map ((/6 {- Magic number ??? -}) . Dia.width) rnTextLines nLines = length lineWidths lineHeight = 1 + extraTopPad + 2*padding ζx = ζy * xAspect ζy = txtSize -- / lineHeight width = (maximum $ 0 : lineWidths) + 2*padding height = fromIntegral nLines * lineHeight y₀ = case vAlign of AlignBottom -> padding AlignMid -> 0 AlignTop -> - padding fullText = mconcat $ zipWith3 ( \n w -> let y = n' * lineHeight n' = n - case vAlign of AlignTop -> 0 AlignMid -> fromIntegral nLines / 2 AlignBottom -> fromIntegral nLines in (Dia.translate $ Dia.r2 (case hAlign of AlignBottom -> ( padding, y₀-y ) AlignMid -> ( 0 , y₀-y ) AlignTop -> (-padding, y₀-y ) ) ) ) [0..] lineWidths rnTextLines p = px ^& py where px = max l' . min r' $ p₀^._x py = max b' . min t' $ p₀^._y (l', r') = case hAlign of AlignBottom -> (lBound , rBound - w ) AlignMid -> (lBound + w/2, rBound - w/2) AlignTop -> (lBound + w , rBound ) (b', t') = case vAlign of AlignBottom -> (bBound' , tBound - h ) AlignMid -> (bBound' + h/2, tBound - h/2) AlignTop -> (bBound' + h , tBound ) w = ζx * width; h = ζy * height bBound' = bBound + lineHeight*ζy return . Dia.translate p . Dia.scaleX ζx . Dia.scaleY ζy $ Dia.lc Dia.grey fullText lg :: Floating a => a -> a lg = logBase 10 data LegendEntry = LegendEntry { plotObjectTitle :: TextObj , customLegendObject :: Option () } prerenderLegend :: TextTK -> ColourScheme -> [(LegendEntry, Maybe AColour)] -> IO PlainGraphicsR2 prerenderLegend _ _ [] = return mempty prerenderLegend TextTK{..} cscm l = do let bgColour = cscm neutral defColour = cscm (paler contrast) lRends <- fmap Dia.vcat $ forM l `id` \( LegendEntry{ plotObjectTitle = PlainText str , customLegendObject = Option Nothing }, c ) -> do txtR <- CairoTxt.textVisualBoundedIO txtCairoStyle $ DiaTxt.Text mempty (DiaTxt.BoxAlignedText 0 0.5) str let h = Dia.height txtR return $ Dia.hsep 5 [ Dia.rect h h & Dia.fcA (maybe defColour id c) , txtR ] & Dia.centerXY & Dia.frame 2 & Dia.alignL let w = Dia.width lRends h = Dia.height lRends return $ ( lRends & Dia.centerXY & Dia.translate (3^&3) ) <> ( Dia.rect (w+1) (h+1) & Dia.fcA (cscm $ paler grey) )