packages feed

hvega-0.7.0.0: tests/MarkTests.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}

--
-- random mark-related tests
--

module MarkTests (testSpecs) where

import qualified Data.Text as T

import Data.List (intercalate)

#if !(MIN_VERSION_base(4, 12, 0))
import Data.Monoid ((<>))
#endif

import Text.Printf (printf)

import Graphics.Vega.VegaLite

testSpecs :: [(String, VegaLite)]
testSpecs = [("blendmode", blendMode)]


-- How does blend-mode work (added in Vega-Lite 4.6.0)?
-- This is based on
-- https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode#Examples
--

blendData :: Data
blendData =
  dataFromColumns []
  . dataColumn "x" (Numbers [0])
  . dataColumn "y" (Numbers [0])
  $ []


-- rotate an ellipse about the origin; it would be better if I learned
-- how to use the arc path segment
--
-- angle in degrees
ellipse :: Double -> Symbol
ellipse ang =
  let rad = ang * pi / 180
      cosRot = cos rad
      sinRot = sin rad
      
      rmajor = 1.0
      rminor = 0.3

      p = printf "%.2f"
            
      -- could learn to use the Arc path segment, but just do it manually
      pair t =
        let x = rmajor * cos t * cosRot - rminor * sin t * sinRot
            y = rmajor * cos t * sinRot + rminor * sin t * cosRot
        in "L " <> p x <> " " <> p y

      thetas = [0, 0.25 ..  2 * pi]
      path = intercalate " " (map pair thetas)
      
  in SymPath (T.pack path)
  

-- Is there a better way to do this?
blendMode :: VegaLite
blendMode =
  let ax t f = position t [ PName f
                          , PmType Quantitative
                          , PScale [SDomain (DNumbers [-5, 5])]
                          ]

      -- randomly trying to get similar results to the Mozilla page
      props 0 = [GrX1 0.5, GrX2 0.5, GrY1 1, GrY2 0] 
      props 1 = [GrX1 0.7, GrX2 0.3, GrY1 0.1, GrY2 1]
      props _ = [GrX1 1, GrX2 0, GrY1 1, GrY2 0]
      
      gradient f =
        let c | f == 0 = "rgb(0,255,0)"
              | f == 1 = "rgb(255,0,0)"
              | otherwise = "rgb(0,0,255)"
              
        in MFillGradient GrLinear [(0, "white"), (1, c)] (props f)

      lyr bm f =
        let a | f == 0 = 0
              | f == 1 = 45
              | otherwise = -45
              
        in asSpec [ mark Point [ MShape (ellipse a)
                               , MBlend bm
                               , gradient f
                               ]
                  ]

      createLayer (bm, ttl) =
        asSpec [ encoding . ax X "x" . ax Y "y" $ []
               , layer (map (lyr bm) [0::Int .. 2])
               , title ttl []
               ]

      layers = map createLayer [ (BMNormal, "Normal")
                               , (BMMultiply, "Multiply")
                               , (BMScreen, "Screen")
                               , (BMOverlay, "Overlay")
                               , (BMDarken, "Darken")
                               , (BMLighten, "Lighten")
                               , (BMColorDodge, "Color-Dodge")
                               , (BMColorBurn, "Color-Burn")
                               , (BMHardLight, "Hard-Light")
                               , (BMSoftLight, "Soft-Light")
                               , (BMDifference, "Difference")
                               , (BMExclusion, "Exclusion")
                               , (BMHue, "Hue")
                               , (BMSaturation, "Saturation")
                               , (BMColor, "Color")
                               , (BMLuminosity, "Luminosity")
                               ]
      
  in toVegaLite [ configure
                  . configuration (Axis [Domain False, Labels False, Ticks False, NoTitle])
                  . configuration (PointStyle [MOpacity 1, MSize 40000, MStroke ""])
                  -- note the interesting background
                  . configuration (BackgroundStyle "rgba(255,255,255,0)")
                  $ []
                , blendData
                , columns 4
                , vlConcat layers
                ]