dataframe-viz 1.0.2.0 → 1.0.3.0
raw patch · 6 files changed
+119/−21 lines, 6 filesdep ~dataframe-coredep ~textPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: dataframe-core, text
API changes (from Hackage documentation)
+ DataFrame.Display.Web.Chart: includeZero :: Channel -> Bool -> Chart -> Chart
+ DataFrame.Display.Web.Chart.Typed: includeZero :: forall (cols :: [Type]). Channel -> Bool -> Chart cols -> Chart cols
+ DataFrame.Display.Web.Plot: [includeZero] :: Line -> Bool
- DataFrame.Display.Web.Plot: Line :: Text -> [Text] -> Maybe Text -> Size -> Line
+ DataFrame.Display.Web.Plot: Line :: Text -> [Text] -> Maybe Text -> Size -> Bool -> Line
- DataFrame.Display.Web.Plot: Scatter :: Text -> Text -> Maybe Text -> Maybe Text -> Size -> Scatter
+ DataFrame.Display.Web.Plot: Scatter :: Text -> Text -> Maybe Text -> Maybe Text -> Size -> Bool -> Scatter
Files
- dataframe-viz.cabal +2/−3
- src/DataFrame/Display/Internal/Common.hs +11/−1
- src/DataFrame/Display/Internal/VegaLite.hs +38/−6
- src/DataFrame/Display/Web/Chart.hs +40/−6
- src/DataFrame/Display/Web/Chart/Typed.hs +5/−0
- src/DataFrame/Display/Web/Plot.hs +23/−5
dataframe-viz.cabal view
@@ -1,7 +1,6 @@ cabal-version: 2.4 name: dataframe-viz-version: 1.0.2.0-+version: 1.0.3.0 synopsis: Visualisation/plotting helpers for the dataframe ecosystem. description: Display harness plus terminal and web plotters. Built on top of@@ -38,7 +37,7 @@ build-depends: base >= 4 && < 5, aeson >= 0.11.0.0 && < 3, containers >= 0.6.7 && < 0.9,- dataframe-core ^>= 1.0,+ dataframe-core ^>= 1.1, directory >= 1.3.0.0 && < 2, granite >= 0.6 && < 1, process ^>= 1.6,
src/DataFrame/Display/Internal/Common.hs view
@@ -47,7 +47,12 @@ import GHC.Stack (HasCallStack) import Type.Reflection (TypeRep, typeRep) -import DataFrame.Internal.Column (Column (..), Columnable, isNumeric)+import DataFrame.Internal.Column (+ Column (..),+ Columnable,+ isNumeric,+ materializePacked,+ ) import DataFrame.Internal.DataFrame (DataFrame (..), getColumn) import DataFrame.Internal.Types @@ -164,12 +169,14 @@ Nothing -> V.toList $ V.map (T.pack . show) vec UnboxedColumn _ vec -> V.toList $ VG.map (T.pack . show) (VG.convert vec)+ PackedText _ _ -> columnToStrings (materializePacked col) -- | Coerce a numeric column to @[Double]@; errors if the element type is not numeric. columnToDoubles :: (HasCallStack) => Column -> [Double] columnToDoubles col = case col of BoxedColumn _ vec -> vectorToDoubles vec UnboxedColumn _ vec -> unboxedVectorToDoubles vec+ PackedText _ _ -> columnToDoubles (materializePacked col) vectorToDoubles :: forall a. (Columnable a, Show a) => V.Vector a -> [Double] vectorToDoubles vec =@@ -206,6 +213,9 @@ Just (countBoxed (typeRep @a) vec) UnboxedColumn _ (vec :: VU.Vector a) -> Just (countUnboxed (typeRep @a) vec)+ PackedText _ _ -> case materializePacked col of+ BoxedColumn _ (vec :: V.Vector a) -> Just (countBoxed (typeRep @a) vec)+ _ -> Nothing where countBoxed :: forall a. (Show a) => TypeRep a -> V.Vector a -> [(T.Text, Double)]
src/DataFrame/Display/Internal/VegaLite.hs view
@@ -21,6 +21,9 @@ Channel (..), FieldType (..), ChannelEnc (..),+ ScaleSpec (..),+ ScaleType (..),+ defaultScale, Transform (..), VLSpec (..), emptySpec,@@ -125,13 +128,29 @@ , ceType :: FieldType , ceAggregate :: Maybe T.Text , ceBin :: Bool- , ceLogScale :: Bool+ , ceScale :: ScaleSpec } --- | A bare channel encoding with no aggregation, binning, or log scale.+-- | A bare channel encoding with no aggregation, binning, or scale options. chanEnc :: Channel -> T.Text -> FieldType -> ChannelEnc-chanEnc ch fld ft = ChannelEnc ch fld ft Nothing False False+chanEnc ch fld ft = ChannelEnc ch fld ft Nothing False defaultScale +{- | Per-channel scale configuration, mirroring Vega-Lite's @scale@ object.+'Nothing' fields leave the Vega-Lite default in place; grow this record+(domain, nice, clamp, scheme, ...) as the public vocabulary grows.+-}+data ScaleSpec = ScaleSpec+ { scaleType :: Maybe ScaleType+ , scaleZero :: Maybe Bool+ }+ deriving (Eq, Show)++data ScaleType = LogS+ deriving (Eq, Show)++defaultScale :: ScaleSpec+defaultScale = ScaleSpec Nothing Nothing+ data Transform = -- | Fit @regression(yField) on xField@ (used inside a layer). RegressionT T.Text T.Text@@ -294,10 +313,23 @@ , Just ("type" .= fieldTypeName (ceType e)) , fmap ("aggregate" .=) (ceAggregate e) , if ceBin e then Just ("bin" .= True) else Nothing- , if ceLogScale e- then Just ("scale" .= object ["type" .= ("log" :: T.Text)])- else Nothing+ , fmap ("scale" .=) (scaleValue (ceScale e)) ]++-- | Render a scale spec, or 'Nothing' when every field is at its default.+scaleValue :: ScaleSpec -> Maybe Value+scaleValue s+ | null pairs = Nothing+ | otherwise = Just (object pairs)+ where+ pairs =+ catMaybes+ [ fmap (("type" .=) . scaleTypeName) (scaleType s)+ , fmap ("zero" .=) (scaleZero s)+ ]++scaleTypeName :: ScaleType -> T.Text+scaleTypeName LogS = "log" transformValue :: Transform -> Value transformValue (RegressionT yField xField) =
src/DataFrame/Display/Web/Chart.hs view
@@ -50,6 +50,7 @@ regression, density, logScale,+ includeZero, title, size, @@ -78,6 +79,8 @@ FieldType (..), Mark (..), ResolvedField (..),+ ScaleSpec (..),+ ScaleType (..), Transform (DensityT, RegressionT), VLSpec (..), chanEnc,@@ -155,10 +158,21 @@ binX = withChannel X (\e -> e{ceBin = True}) binY = withChannel Y (\e -> e{ceBin = True}) --- | Put a channel on a log scale.+{- | Put a channel on a log scale. Like all channel modifiers, apply it+after 'enc' on the same channel ('enc' replaces the whole encoding).+-} logScale :: Channel -> Chart -> Chart-logScale ch = withChannel ch (\e -> e{ceLogScale = True})+logScale ch = withScale ch (\s -> s{scaleType = Just LogS}) +{- | Anchor (@True@) or release (@False@) a channel's scale at zero.+Vega-Lite includes zero on quantitative scales by default, which squashes+data far from the origin (e.g. geographic coordinates) against the chart+edge; @includeZero X False@ lets the axis fit the data instead. Apply it+after 'enc' on the same channel ('enc' replaces the whole encoding).+-}+includeZero :: Channel -> Bool -> Chart -> Chart+includeZero ch b = withScale ch (\s -> s{scaleZero = Just b})+ -- | Facet into small multiples by a column (alias for 'column'). facet :: (Columnable a) => Expr a -> Chart -> Chart facet = column@@ -246,10 +260,18 @@ -- One-shot convenience plots (the simple single-line path) -- --------------------------------------------------------------------------- --- | Scatter plot of two expressions.+-- | Scatter plot of two expressions. Axes fit the data (no zero anchor). scatter :: (Columnable a, Columnable b) => Expr a -> Expr b -> DataFrame -> IO ()-scatter xE yE df = showChart (chart df & mark Point & enc X xE & enc Y yE)+scatter xE yE df =+ showChart+ ( chart df+ & mark Point+ & enc X xE+ & enc Y yE+ & includeZero X False+ & includeZero Y False+ ) -- | Count of rows per category, as bars. bar :: (Columnable a) => Expr a -> DataFrame -> IO ()@@ -260,9 +282,17 @@ histogram xE df = showChart (chart df & mark Bar & enc X xE & binX & aggregateOn Y Count) --- | Line chart of @y@ over @x@.+-- | Line chart of @y@ over @x@. Axes fit the data (no zero anchor). line :: (Columnable a, Columnable b) => Expr a -> Expr b -> DataFrame -> IO ()-line xE yE df = showChart (chart df & mark Line & enc X xE & enc Y yE)+line xE yE df =+ showChart+ ( chart df+ & mark Line+ & enc X xE+ & enc Y yE+ & includeZero X False+ & includeZero Y False+ ) -- | Pie chart counting rows per category. pie :: (Columnable a) => Expr a -> DataFrame -> IO ()@@ -287,6 +317,10 @@ in if any ((== ch) . ceChannel) encs then c{chEncs = map (\e -> if ceChannel e == ch then f e else e) encs} else c{chEncs = encs ++ [f (chanEnc ch "" Quantitative)]}++-- | Modify a channel's scale spec (creating the encoding if absent).+withScale :: Channel -> (ScaleSpec -> ScaleSpec) -> Chart -> Chart+withScale ch f = withChannel ch (\e -> e{ceScale = f (ceScale e)}) aggVegaName :: Agg -> T.Text aggVegaName a = case a of
src/DataFrame/Display/Web/Chart/Typed.hs view
@@ -55,6 +55,7 @@ regression, density, logScale,+ includeZero, title, size, @@ -119,6 +120,10 @@ -- | Put a channel on a log scale. logScale :: Channel -> Chart cols -> Chart cols logScale ch (Chart c) = Chart (C.logScale ch c)++-- | Anchor (@True@) or release (@False@) a channel's scale at zero.+includeZero :: Channel -> Bool -> Chart cols -> Chart cols+includeZero ch b (Chart c) = Chart (C.includeZero ch b c) -- | Facet into small multiples by a column (alias for 'column'). facet :: (Columnable a) => TExpr cols a -> Chart cols -> Chart cols
src/DataFrame/Display/Web/Plot.hs view
@@ -136,6 +136,10 @@ let spec' = spec{vlWidth = sz.width, vlHeight = sz.height} return $ T.unpack $ specHtml chartId fields spec' +-- | Pin a channel's zero anchor explicitly, on or off.+anchorZero :: Bool -> VL.ChannelEnc -> VL.ChannelEnc+anchorZero b e = e{VL.ceScale = VL.defaultScale{VL.scaleZero = Just b}}+ -- --------------------------------------------------------------------------- -- Bar -- ---------------------------------------------------------------------------@@ -226,16 +230,22 @@ -- Scatter -- --------------------------------------------------------------------------- +{- | 'includeZero' anchors both axes at zero (Vega-Lite's own default).+It is off by default so the axes fit the data, as expected of a scatter —+data far from the origin (e.g. geographic coordinates) would otherwise be+squashed against the chart edge.+-} data Scatter = Scatter { x :: T.Text , y :: T.Text , color :: Maybe T.Text , title :: Maybe T.Text , size :: Size+ , includeZero :: Bool } mkScatter :: T.Text -> T.Text -> Scatter-mkScatter xc yc = Scatter xc yc Nothing Nothing defaultSize+mkScatter xc yc = Scatter xc yc Nothing Nothing defaultSize False scatter :: (HasCallStack) => Scatter -> DataFrame -> IO String scatter spec df = do@@ -245,7 +255,10 @@ xVals = extractNumericColumn spec.x df yVals = extractNumericColumn spec.y df baseFields = [numField spec.x xVals, numField spec.y yVals]- baseEncs = [chanEnc X spec.x Quantitative, chanEnc Y spec.y Quantitative]+ baseEncs =+ map+ (anchorZero spec.includeZero)+ [chanEnc X spec.x Quantitative, chanEnc Y spec.y Quantitative] (fields, encs) = case spec.color of Nothing -> (baseFields, baseEncs) Just grp ->@@ -263,15 +276,20 @@ -- Line -- --------------------------------------------------------------------------- +{- | 'includeZero' anchors the axes at zero (Vega-Lite's own default). Off+by default so the axes fit the data — a line is position-encoded, so a+forced zero baseline only squashes series far from the origin.+-} data Line = Line { x :: T.Text , y :: [T.Text] , title :: Maybe T.Text , size :: Size+ , includeZero :: Bool } mkLine :: T.Text -> [T.Text] -> Line-mkLine xc ys = Line xc ys Nothing defaultSize+mkLine xc ys = Line xc ys Nothing defaultSize False line :: (HasCallStack) => Line -> DataFrame -> IO String line spec df = do@@ -297,8 +315,8 @@ vlSpec = (emptySpec VL.Line) { vlEncodings =- [ chanEnc X spec.x Quantitative- , chanEnc Y "value" Quantitative+ [ anchorZero spec.includeZero (chanEnc X spec.x Quantitative)+ , anchorZero spec.includeZero (chanEnc Y "value" Quantitative) , chanEnc Color "series" Nominal ] , vlTitle = Just chartTitle