text-and-plots 0.1.1.0 → 0.2.0.0
raw patch · 4 files changed
+175/−39 lines, 4 filesdep +textdep −aesonPVP ok
version bump matches the API change (PVP)
Dependencies added: text
Dependencies removed: aeson
API changes (from Hackage documentation)
+ Text.DocL: (/:) :: ToObj a => String -> a -> Prop
+ Text.DocL: javascript :: String -> Obj
+ Text.DocL.Javascript: (/:) :: ToObj a => String -> a -> Prop
+ Text.DocL.Javascript: class ToObj a where toObjList = List . map toObj
+ Text.DocL.Javascript: data Obj
+ Text.DocL.Javascript: data Prop
+ Text.DocL.Javascript: encode :: Obj -> Text
+ Text.DocL.Javascript: instance IsString Obj
+ Text.DocL.Javascript: instance Show Obj
+ Text.DocL.Javascript: instance ToObj ()
+ Text.DocL.Javascript: instance ToObj Bool
+ Text.DocL.Javascript: instance ToObj Char
+ Text.DocL.Javascript: instance ToObj Double
+ Text.DocL.Javascript: instance ToObj Int
+ Text.DocL.Javascript: instance ToObj Integer
+ Text.DocL.Javascript: instance ToObj Obj
+ Text.DocL.Javascript: instance ToObj Prop
+ Text.DocL.Javascript: instance ToObj Text
+ Text.DocL.Javascript: instance ToObj a => ToObj [a]
+ Text.DocL.Javascript: javascript :: String -> Obj
+ Text.DocL.Javascript: merge :: Obj -> Obj -> Obj
+ Text.DocL.Javascript: toObj :: ToObj a => a -> Obj
+ Text.DocL.Javascript: toObjList :: ToObj a => [a] -> Obj
- Text.DocL: col' :: ToJSON b => String -> (a -> b) -> Column a
+ Text.DocL: col' :: ToObj b => String -> (a -> b) -> Column a
- Text.DocL: plot' :: Foldable f => f a -> Column a -> [Column a] -> Value -> Doc
+ Text.DocL: plot' :: Foldable f => f a -> Column a -> [Column a] -> [Prop] -> Doc
- Text.DocL: rawPlot :: ToJSON a => a -> Doc
+ Text.DocL: rawPlot :: [Prop] -> Doc
Files
- CHANGELOG +3/−0
- src/Text/DocL.hs +29/−34
- src/Text/DocL/Javascript.hs +136/−0
- text-and-plots.cabal +7/−5
CHANGELOG view
@@ -1,2 +1,5 @@+- 0.2.0.0+ * Using the Text.DocL.Javascript module instead of aeson+ (since not everything we want to ship to C3.js is JSON encodable). - 0.1.1.0 * Bumped blaze-html dependency to <0.9.
src/Text/DocL.hs view
@@ -30,10 +30,11 @@ Column, col, col', plot, plot', rawPlot, -- * Utilities- linspace+ linspace,+ -- * Re-exports from "Text.DocL.Javascript"+ (/:), javascript ) where -import Data.Aeson (ToJSON, object, toJSON, (.=)) import Data.Foldable import Data.HashMap.Strict (insert, unionWith) import Data.Monoid@@ -41,15 +42,15 @@ import Data.String (IsString (..)) import Text.Blaze.Html.Renderer.Utf8 (renderHtml) import Text.Blaze.Html5 ((!))+import Text.DocL.Javascript -import qualified Text.Markdown as Markdown-import qualified Data.Aeson as Aeson+import qualified Data.ByteString.Lazy as B import qualified System.IO as IO import qualified Text.Blaze.Html5 as H import qualified Text.Blaze.Html5.Attributes as A-import qualified Data.ByteString.Lazy as B+import qualified Text.Markdown as Markdown -data Node = Chart Aeson.Value | Html H.Html+data Node = Chart [Prop] | Html H.Html -- | Base type representing a document. This type allows composition via the -- 'Monoid' instance.@@ -59,19 +60,14 @@ -- details. data Column a = Column { _header :: String- , _extract :: a -> Aeson.Value+ , _extract :: a -> Obj } --- | Deep merge of two 'Aeson.Value's. Used to merge in options in 'plot''.-merge :: Aeson.Value -> Aeson.Value -> Aeson.Value-merge (Aeson.Object x) (Aeson.Object y) = Aeson.Object $ unionWith merge x y-merge _ x = x- -- | Polymorphic version of 'col'. This allows, for instance, 'Int' and 'String' -- values to be used in plots. It is up to the caller to ensure that the values -- make sense to <http://c3js.org/ C3.js>.-col' :: ToJSON b => String -> (a -> b) -> Column a-col' h f = Column h (toJSON . f)+col' :: ToObj b => String -> (a -> b) -> Column a+col' h f = Column h (toObj . f) -- | Data sets are supplied to 'plot' as a collection of elements of type /a/. -- This function sets up a 'Column', which knows how to extract one 'Double'@@ -95,38 +91,38 @@ -- Plots the data in /ds/ using the column /x/ for the values on the /x/-axis -- and with one line on the plot for each column in /ys/. See also 'col'. plot :: Foldable f => f a -> Column a -> [Column a] -> Doc-plot d x ys = plot' d x ys (Aeson.Object mempty)+plot d x ys = plot' d x ys [] --- | Same as 'plot', but takes a final argument which should be a "Data.Aeson"--- object. This is merged with the configuration object supplied to--- <http://c3js.org/ C3.js>. This allows the caller to customize the plot.+-- | Same as 'plot', but takes a final argument which is merged with the+-- configuration object supplied to <http://c3js.org/ C3.js>. This allows the+-- caller to customize the plot. -- -- @ -- -- Plot x² vs x with the points hidden. -- plot' [1..10] (col "x" id) [col "x²" $ \\x -> x*x] $--- object ["point" .= object ["show" .= False]]+-- ["point" //: ["show" //: False]] -- @ -- -- See <http://c3js.org/reference.html> for the many properties that can be used -- here.-plot' :: Foldable f => f a -> Column a -> [Column a] -> Aeson.Value -> Doc-plot' d x ys options = rawPlot $ merge obj options+plot' :: Foldable f => f a -> Column a -> [Column a] -> [Prop] -> Doc+plot' d x ys options = rawPlot $ obj ++ options where- obj = object- [ "data" .= object- [ "rows" .= (toJSON (_header x : map _header ys) : map f (toList d))- , "x" .= _header x+ obj =+ [ "data" /:+ [ "rows" /: (toObj (_header x : map _header ys) : map f (toList d))+ , "x" /: _header x ] ]- f p = toJSON $ map (`_extract` p) (x:ys)+ f p = toObj $ map (`_extract` p) (x:ys) --- | This function sends the supplied json object directly to+-- | This function sends an object with the supplied properties directly to -- <http://c3js.org/ C3.js>. This is the do-it-yourself option which exposes all -- of <http://c3js.org/ C3.js>. The object receives a -- <http://c3js.org/reference.html#bindto "bindto"> property, -- targeting a @\<div\>@ tag placed appropriately.-rawPlot :: Aeson.ToJSON a => a -> Doc-rawPlot = Doc . singleton . Chart . Aeson.toJSON+rawPlot :: [Prop] -> Doc+rawPlot = Doc . singleton . Chart -- | Creates a 'Doc' representing raw html. html :: H.Html -> Doc@@ -162,15 +158,14 @@ H.body $ foldMap f (zip [(0::Int)..] $ toList doc) f (_, (Html t)) = t- f (i, (Chart v)) = divTag <> scriptTag+ f (i, (Chart props)) = divTag <> scriptTag where name = "plot" <> show i divTag = H.div ! A.id (fromString name) $ mempty- (Aeson.Object obj) = v- obj' = insert "bindto" (fromString $ "#" <> name) obj- json = H.unsafeLazyByteString $ Aeson.encode obj'+ obj = props ++ ["bindto" /: ("#" ++ name)]+ js = encode $ toObj obj scriptTag = H.script ! A.type_ "text/javascript" $- H.preEscapedToMarkup $ "c3.generate(" <> json <> ");"+ H.preEscapedToMarkup $ "\nc3.generate(" <> js <> ");\n" -- | If the file exists, it will be overwritten. renderToFile :: FilePath -> Doc -> IO ()
+ src/Text/DocL/Javascript.hs view
@@ -0,0 +1,136 @@+module Text.DocL.Javascript (+-- * Rationale+-- | This is basically a JSON encoding library, however it goes beyond JSON by+-- allowing raw javascript to be used as a value. This allows, for instance,+-- function calls and anonymous functions to be placed in the data when used in+-- a javascript context. This is needed to expose some options in+-- <http://c3js.org/ C3.js>, e.g. customizing tick formatting.++-- * The 'Obj' type+ Obj,+ ToObj (..),+-- * Constructing javascript types+ Prop, (/:), javascript,+-- * Encoding+ encode,+-- * Miscellanea+ merge+ ) where++import Data.Char (isControl, ord)+import Data.Foldable hiding (elem)+import Data.List (intersperse)+import Data.Monoid+import Data.String (IsString (..))+import Data.Text.Lazy (Text)+import Text.Printf (printf)++import qualified Data.HashMap.Strict as Map+import qualified Data.Text.Lazy as T+import qualified Data.Text.Lazy.Builder as B++-- | A key\/value pair created with the '/:'-operator.+--+-- A list of type ['Prop'] can be converted to an object using 'toObj'. If the+-- list contains duplicate keys, then 'merge' is used to combine the values.+--+-- >>> toObj ["legs" /: 4, "ears" /: "floppy"]+-- {"ears":"floppy","legs":4}+data Prop = Prop Text Obj++-- | Represents a javascript object or scalar value.+data Obj = Obj (Map.HashMap Text Obj) | List [Obj] | Scalar Text++instance IsString Obj where+ fromString = toObj++-- | /'show' x/ returns /x/ encoded.+instance Show Obj where+ show = T.unpack . encode++-- | Class of types that can be converted to an 'Obj'.+class ToObj a where+ -- | Converts to an 'Obj'.+ toObj :: a -> Obj++ -- | Only for writing instances. This function serves the same role as+ -- 'showList'+ toObjList :: [a] -> Obj+ toObjList = List . map toObj++-- | A ['Prop'] becomes a javascript object.+instance ToObj Prop where+ toObj x = toObj [x]+ toObjList = Obj . Map.fromListWith merge . map (\(Prop k v) -> (k, v))++-- | Becomes a javascript array.+instance ToObj a => ToObj [a] where+ toObj = toObjList++-- | Constructs a 'Prop'.+--+-- >>> toObj ["tail" /: True, "nose" /: "wet"]+-- {"nose":"wet","tail":true}+(/:) :: ToObj a => String -> a -> Prop+k /: v = Prop (fromString k) $ toObj v++-- | /'merge' x y/ returns /y/ unless both /x/ and /y/ are javascript objects,+-- in which case the properties of the two are merged using 'merge' recursively+-- for duplicate keys.+merge :: Obj -> Obj -> Obj+merge (Obj x) (Obj y) = Obj $ Map.unionWith merge x y+merge _ x = x++-- | Raw unescaped javascript.+--+-- >>> toObj ["speak" /: javascript "function(){alert('Woof!');}"]+-- {"speak":function(){alert('Woof!');}}+javascript :: String -> Obj+javascript = Scalar . fromString++-- | 'ToObj' is the identity for an 'Obj' value.+instance ToObj Obj where+ toObj = id++instance ToObj Double where+ -- This trivial implementation even handles NaN and Infinity correctly.+ toObj = Scalar . fromString . show+instance ToObj Int where+ toObj = Scalar . fromString . show+instance ToObj Integer where+ toObj = Scalar . fromString . show++-- | Becomes a javascript /null/ object+instance ToObj () where+ toObj () = Scalar "null"++instance ToObj Bool where+ toObj x = Scalar $ if x then "true" else "false"++-- | A 'String' becomes a javascript string literal.+instance ToObj Char where+ toObj c = toObj [c]+ toObjList xs = Scalar . B.toLazyText $ "\"" <> foldMap esc xs <> "\""+ where+ mustEscape = "\"\\>\n\r\x2028\x2029"+ esc x =+ if isControl x || elem x mustEscape+ then fromString . printf "\\u%04x" $ ord x+ else B.singleton x++-- | Becomes a javascript string literal.+instance ToObj Text where+ toObj = toObj . T.unpack++-- | Encode an 'Obj' to a lazy 'Text'.+encode :: Obj -> Text+encode = B.toLazyText . encode'++encode' :: Obj -> B.Builder+encode' obj = case obj of+ Scalar x -> B.fromLazyText x+ List xs -> "[" <> commas (map encode' xs) <> "]"+ Obj m -> "{" <> commas (map f $ Map.toList m) <> "}"+ where+ commas = fold . intersperse ","+ f (k, v) = encode' (toObj k) <> ":" <> encode' v
text-and-plots.cabal view
@@ -1,5 +1,5 @@ name: text-and-plots-version: 0.1.1.0+version: 0.2.0.0 synopsis: EDSL to create HTML documents with plots based on the C3.js library. description:@@ -8,6 +8,7 @@ <https://github.com/andersjel/haskell-text-and-plots github page> for an introduction. homepage: https://github.com/andersjel/haskell-text-and-plots+bug-reports: https://github.com/andersjel/haskell-text-and-plots/issues license: MIT license-file: LICENSE author: Anders Jellinggaard@@ -16,7 +17,7 @@ category: Text build-type: Simple cabal-version: >=1.10-Extra-source-files:+extra-source-files: CHANGELOG source-repository head@@ -26,19 +27,20 @@ source-repository this type: git location: git://github.com/andersjel/haskell-text-and-plots.git- tag: v0.1.1.0+ tag: v0.2.0.0 library exposed-modules: Text.DocL+ Text.DocL.Javascript build-depends:- aeson >=0.8.0.0 && <0.9, base >=4.7.0.0 && <4.8, blaze-html >=0.7.0.0 && <0.9, bytestring >=0.10.4.0 && <0.11, containers >=0.5.5.1 && <0.6, markdown >=0.1.7.1 && <0.2,- unordered-containers >=0.2.3.3 && <0.3+ unordered-containers >=0.2.3.3 && <0.3,+ text >=1.2.0.2 && <1.3 hs-source-dirs: src default-language: Haskell2010 default-extensions: