From 69ec40a03825b9fbcb8b63f362e73dee279c63c7 Mon Sep 17 00:00:00 2001 From: Tony Day Date: Sun, 13 Oct 2024 10:47:41 +1000 Subject: [PATCH] switch to harpie (#41) * switch to harpie * polish --- .github/workflows/haskell-ci.yml | 42 +++--- ChangeLog.md | 5 + cabal.project | 9 -- chart-svg.cabal | 18 +-- readme.org => chart-svg.org | 0 readme.md | 45 ++++++ src/Data/Colour.hs | 240 +++++++++++-------------------- src/Data/Path.hs | 17 +-- 8 files changed, 166 insertions(+), 210 deletions(-) rename readme.org => chart-svg.org (100%) create mode 100644 readme.md diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml index 2908224..700b5e1 100644 --- a/.github/workflows/haskell-ci.yml +++ b/.github/workflows/haskell-ci.yml @@ -1,10 +1,18 @@ +name: build on: [push] -name: haskell-ci + +# INFO: The following configuration block ensures that only one build runs per branch, +# which may be desirable for projects with a costly build process. +# Remove this block from the CI workflow to let each CI job run to completion. +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + jobs: hlint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: haskell-actions/hlint-setup@v2 - uses: haskell-actions/hlint-run@v2 with: @@ -14,8 +22,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: haskell-actions/run-ormolu@v15 - cabal: + - uses: haskell-actions/run-ormolu@v16 + build: name: GHC ${{ matrix.ghc-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: @@ -39,14 +47,6 @@ jobs: with: ghc-version: ${{ matrix.ghc-version }} - - name: Installed minor versions of GHC and Cabal - shell: bash - run: | - GHC_VERSION=$(ghc --numeric-version) - CABAL_VERSION=$(cabal --numeric-version) - echo "GHC_VERSION=${GHC_VERSION}" >> "${GITHUB_ENV}" - echo "CABAL_VERSION=${CABAL_VERSION}" >> "${GITHUB_ENV}" - - name: Configure the build run: | cabal configure --enable-tests --enable-benchmarks --disable-documentation @@ -54,23 +54,25 @@ jobs: # The last step generates dist-newstyle/cache/plan.json for the cache key. - name: Restore cached dependencies - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: cache + env: + key: ${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-cabal-${{ steps.setup.outputs.cabal-version }} with: path: ${{ steps.setup.outputs.cabal-store }} - key: ${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-cabal-${{ env.CABAL_VERSION }}-plan-${{ hashFiles('**/plan.json') }} - restore-keys: | - ${{ runner.os }}-ghc-${{ env.GHC_VERSION }}-cabal-${{ env.CABAL_VERSION }}- + key: ${{ env.key }}-plan-${{ hashFiles('**/plan.json') }} + restore-keys: ${{ env.key }}- - name: Install dependencies + # If we had an exact cache hit, the dependencies will be up to date. + if: steps.cache.outputs.cache-hit != 'true' run: cabal build all --only-dependencies # Cache dependencies already here, so that we do not have to rebuild them should the subsequent steps fail. - name: Save cached dependencies - uses: actions/cache/save@v3 - # Caches are immutable, trying to save with the same key would error. - if: ${{ !steps.cache.outputs.cache-hit - || steps.cache.outputs.cache-primary-key != steps.cache.outputs.cache-matched-key }} + uses: actions/cache/save@v4 + # If we had an exact cache hit, trying to save the cache would error because of key clash. + if: steps.cache.outputs.cache-hit != 'true' with: path: ${{ steps.setup.outputs.cabal-store }} key: ${{ steps.cache.outputs.cache-primary-key }} diff --git a/ChangeLog.md b/ChangeLog.md index cc1af24..a022e71 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,8 @@ +0.7 +=== + +- switch to harpie + 0.6.1 === diff --git a/cabal.project b/cabal.project index 699bf51..99ad7b8 100644 --- a/cabal.project +++ b/cabal.project @@ -1,15 +1,6 @@ packages: chart-svg.cabal -allow-newer: - string-interpolate:template-haskell, - markup-parse:containers - -source-repository-package - type: git - branch: master - location: https://github.com/conal/vector-space.git - write-ghc-environment-files: always tests: True diff --git a/chart-svg.cabal b/chart-svg.cabal index 00efb98..85983a6 100644 --- a/chart-svg.cabal +++ b/chart-svg.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: chart-svg -version: 0.6.1.0 +version: 0.7.0.0 license: BSD-3-Clause license-file: LICENSE copyright: Tony Day (c) 2017 @@ -37,7 +37,7 @@ tested-with: extra-doc-files: ChangeLog.md other/*.svg - readme.org + readme.md source-repository head type: git @@ -62,19 +62,16 @@ library hs-source-dirs: src build-depends: , Color >=0.3.2 && <0.4 - , adjunctions >=4.0 && <5 - , attoparsec >=0.13.2 && <0.15 , base >=4.14 && <5 , bytestring >=0.11.3 && <0.13 , containers >=0.6 && <0.8 , cubicbezier >=0.6 && <0.7 , flatparse >=0.5 && <0.6 - , foldl >=1.4 && <1.5 , formatn >=0.3 && <0.4 + , harpie >=0.1 && <0.2 , markup-parse >=0.1 && <0.2 , mtl >=2.2.2 && <2.4 , numhask >=0.11 && <0.13 - , numhask-array >=0.10 && <0.12 , numhask-space >=0.10 && <0.12 , optics-core >=0.4 && <0.5 , random >=1.2 && <1.3 @@ -98,11 +95,10 @@ library test-suite doctests import: ghc2021-stanza - type: exitcode-stdio-1.0 - hs-source-dirs: test main-is: doctests.hs - ghc-options: -threaded + hs-source-dirs: test build-depends: - , base - , chart-svg + , base >=4.14 && <5 , doctest-parallel >=0.3 && <0.4 + ghc-options: -threaded + type: exitcode-stdio-1.0 diff --git a/readme.org b/chart-svg.org similarity index 100% rename from readme.org rename to chart-svg.org diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..88d16f9 --- /dev/null +++ b/readme.md @@ -0,0 +1,45 @@ + +# Table of Contents + +1. [Usage](#org829f981) +2. [Examples](#orge2856e9) + +[![img](https://img.shields.io/hackage/v/chart-svg.svg)](https://hackage.haskell.org/package/chart-svg) [![img](https://github.com/tonyday567/chart-svg/workflows/haskell-ci/badge.svg)](https://github.com/tonyday567/chart-svg/actions?query=workflow%3Ahaskell-ci) + +![img](other/banner.svg) + +A charting library targetting SVG. + + + + +# Usage + + :r + :set prompt "> " + :set -XOverloadedLabels + :set -XOverloadedStrings + import Chart + import Optics.Core + lines = [[Point 0.0 1.0, Point 1.0 1.0, Point 2.0 5.0],[Point 0.0 0.0, Point 2.8 3.0],[Point 0.5 4.0, Point 0.5 0]] + styles = (\c -> defaultLineStyle & #color .~ palette c & #size .~ 0.015) <$> [0..2] + cs = zipWith (\s x -> LineChart s [x]) styles lines + lineExample = mempty & #chartTree .~ named "line" cs & #hudOptions .~ defaultHudOptions :: ChartOptions + writeChartOptions "other/usage.svg" lineExample + +![img](other/usage.svg) + +See the haddock documentation for a detailed overview. + + + + +# Examples + +To redraw all the examples in Chart.Examples + + import Chart.Examples + writeAllExamples + + ok + diff --git a/src/Data/Colour.hs b/src/Data/Colour.hs index fa28c96..2f964e4 100644 --- a/src/Data/Colour.hs +++ b/src/Data/Colour.hs @@ -1,9 +1,7 @@ {-# LANGUAGE DataKinds #-} -{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE UndecidableInstances #-} -- | Colour representations and combinations. module Data.Colour @@ -18,11 +16,7 @@ module Data.Colour showOpacity, opac', opac, - hex, rgb, - toHex, - fromHex, - unsafeFromHex, -- * Palette colours palette, @@ -36,7 +30,6 @@ module Data.Colour -- * LCH model LCH (..), - pattern LCH, lLCH', cLCH', hLCH', @@ -45,11 +38,9 @@ module Data.Colour lch', alpha', RGB3 (..), - pattern RGB3, rgbd', rgb32colour', LAB (..), - pattern LAB, lcha2colour', xy2ch', @@ -71,23 +62,19 @@ module Data.Colour where import Chart.Data -import Data.Attoparsec.Text qualified as A -import Data.Bifunctor import Data.Bool (bool) import Data.ByteString (ByteString) -import Data.Char -import Data.Either import Data.FormatN -import Data.Functor.Rep import Data.List qualified as List import Data.String.Interpolate -import Data.Text (Text, pack) +import Data.Text (Text) import Data.Text qualified as Text -import GHC.Exts import GHC.Generics hiding (prec) import Graphics.Color.Model as M hiding (LCH) import Graphics.Color.Space qualified as S -import NumHask.Array.Fixed +import Harpie.Fixed (Array, array, (!)) +import Harpie.Fixed qualified as F +import Harpie.Shape (KnownNats) import Optics.Core import System.Random import System.Random.Stateful @@ -184,60 +171,10 @@ showOpacity c = opac' :: Lens' Colour Double opac' = lens opac (\(Colour r g b _) o -> Colour r g b o) --- | Convert to CSS hex representation. -hex :: Colour -> Text -hex c = toHex c - -- | Sets RGB color but not opacity rgb :: Colour -> Colour -> Colour rgb (Colour r g b _) (Colour _ _ _ o) = Colour r g b o --- | Parse CSS hex text. -parseHex :: A.Parser (Color RGB Double) -parseHex = - fmap toDouble - . ( \((r, g), b) -> - ColorRGB (fromIntegral r) (fromIntegral g) (fromIntegral b) :: Color RGB Word8 - ) - . (\(f, b) -> (f `divMod` (256 :: Int), b)) - . (`divMod` 256) - <$> (A.string "#" *> A.hexadecimal) - --- | Convert CSS hex to Colour -fromHex :: Text -> Either Text (Color RGB Double) -fromHex = first pack . A.parseOnly parseHex - --- | Convert CSS hex to Colour, unsafely. -unsafeFromHex :: Text -> Color RGB Double -unsafeFromHex t = fromRight (ColorRGB 0 0 0) $ A.parseOnly parseHex t - --- | Convert from 'Colour' to CSS hex (#xxxxxx) -toHex :: Colour -> Text -toHex c = - "#" - <> Text.justifyRight 2 '0' (hex' r) - <> Text.justifyRight 2 '0' (hex' g) - <> Text.justifyRight 2 '0' (hex' b) - where - (ColorRGBA r g b _) = fromIntegral . toWord8 <$> color' c - -hex' :: Int -> Text -hex' n - | n < 0 = "-" <> go (-n) - | otherwise = go n - where - go n' - | n' < 16 = hexDigit n' - | otherwise = go (n' `quot` 16) <> hexDigit (n' `rem` 16) - -hexDigit :: Int -> Text -hexDigit n - | n <= 9 = Text.singleton $! i2d n - | otherwise = Text.singleton $! toEnum (n + 87) - -i2d :: Int -> Char -i2d x = chr (ord '0' + x) - -- | Select a Colour from the palette -- -- >>> palette 0 @@ -313,6 +250,9 @@ grey g a = Colour g g g a transparent :: Colour transparent = Colour 0 0 0 0 +class ArrayAs f s a where + arrayAs :: (KnownNats s) => Array s a -> f a + -- | LCH colour representation -- -- oklab is a colour space being written into CSS specifications, that attempts to be ok at human-consistent colour representation. See: @@ -328,16 +268,10 @@ transparent = Colour 0 0 0 0 -- C: Chromacity, which ranges from 0 to around 0.32 or so. -- -- H: Hue, which ranges from 0 to 360 -newtype LCH a = LCH' {lchArray :: Array '[3] a} deriving (Eq, Show, IsList, Functor) - --- | LCH colour pattern -pattern LCH :: a -> a -> a -> LCH a -pattern LCH l c h <- - LCH' [l, c, h] - where - LCH l c h = LCH' [l, c, h] +data LCH a = LCH a a a deriving (Eq, Show, Functor) -{-# COMPLETE LCH #-} +instance ArrayAs LCH '[3] a where + arrayAs a = LCH (a ! [0]) (a ! [1]) (a ! [2]) -- | Lightness lens for LCH lLCH' :: Lens' (LCH Double) Double @@ -365,25 +299,19 @@ alpha' = lens (\(LCHA' _ a) -> a) (\(LCHA' lch _) a -> LCHA' lch a) -- | LCHA pattern pattern LCHA :: Double -> Double -> Double -> Double -> LCHA pattern LCHA l c h a <- - LCHA' (LCH' [l, c, h]) a + LCHA' (LCH l c h) a where - LCHA l c h a = LCHA' (LCH' [l, c, h]) a + LCHA l c h a = LCHA' (LCH l c h) a {-# COMPLETE LCHA #-} -- * RGB colour representation -- | A type to represent the RGB triple, useful as an intermediary between 'Colour' and 'LCHA' -newtype RGB3 a = RGB3' {rgb3Array :: Array '[3] a} deriving (Eq, Show, IsList, Functor) +data RGB3 a = RGB3 a a a deriving (Eq, Show, Functor) --- | The RGB3 pattern -pattern RGB3 :: a -> a -> a -> RGB3 a -pattern RGB3 r g b <- - RGB3' [r, g, b] - where - RGB3 r g b = RGB3' [r, g, b] - -{-# COMPLETE RGB3 #-} +instance ArrayAs RGB3 '[3] a where + arrayAs a = RGB3 (a ! [0]) (a ! [1]) (a ! [2]) -- | Lens for conversion between Double and Word8 RGB triples. rgbd' :: Iso' (RGB3 Double) (RGB3 Word8) @@ -396,16 +324,10 @@ rgb32colour' = iso (\(RGB3 r g b, a) -> Colour r g b a) (\(Colour r g b a) -> (R -- * LAB colour representation -- | LAB colour representation. a is green-red and b is blue-yellow -newtype LAB a = LAB' {labArray :: Array '[3] a} deriving (Eq, Show, IsList, Functor) - --- | LAB pattern -pattern LAB :: a -> a -> a -> LAB a -pattern LAB l a b <- - LAB' [l, a, b] - where - LAB l a b = LAB' [l, a, b] +data LAB a = LAB a a a deriving (Eq, Show, Functor) -{-# COMPLETE LAB #-} +instance ArrayAs LAB '[3] a where + arrayAs a = LAB (a ! [0]) (a ! [1]) (a ! [2]) -- * Colour conversions @@ -415,14 +337,14 @@ pattern LAB l a b <- -- -- >>> c0 = Colour 0.78 0.36 0.02 1.00 -- >>> view (re lcha2colour') c0 --- LCHA' {_lch = LCH' {lchArray = [0.5969891006896103, 0.15793931531669247, 49.191113810479784]}, _alpha = 1.0} +-- LCHA' {_lch = LCH 0.5969891006896103 0.15793931531669247 49.191113810479784, _alpha = 1.0} -- -- >>> view (re lcha2colour' % lcha2colour') c0 -- Colour 0.78 0.36 0.02 1.00 -- -- >>> c1 = Colour 0.49 0.14 0.16 1 -- >>> view (re lcha2colour') c1 --- LCHA' {_lch = LCH' {lchArray = [0.40115567099848914, 0.12279066817938503, 21.51476756026837]}, _alpha = 1.0} +-- LCHA' {_lch = LCH 0.40115567099848914 0.12279066817938503 21.51476756026837, _alpha = 1.0} -- -- >>> view (re lcha2colour' % lcha2colour') c1 -- Colour 0.49 0.14 0.16 1.00 @@ -457,50 +379,52 @@ lab2lch' = rgb2lab' :: Iso' (RGB3 Double) (LAB Double) rgb2lab' = iso - (\(RGB3' a) -> LAB' . xyz2lab_ . rgb2xyz_ $ a) - (\(LAB' a) -> RGB3' . xyz2rgb_ . lab2xyz_ $ a) + (\(RGB3 r g b) -> arrayAs . xyz2lab_ . rgb2xyz_ $ array [r, g, b]) + (\(LAB l a b) -> arrayAs . xyz2rgb_ . lab2xyz_ $ array [l, a, b]) -- * rgb to xyz -xyz2rgb_ :: Array '[3] Double -> Array '[3] Double -xyz2rgb_ a = fromList [r, g, b] +xyz2rgb_ :: F.Array '[3] Double -> F.Array '[3] Double +xyz2rgb_ a = array [r, g, b] where - (S.ColorSRGB r g b) = S.xyz2rgb (S.ColorXYZ (a `index` [0]) (a `index` [1]) (a `index` [2])) :: Color (S.SRGB 'S.NonLinear) Double + (S.ColorSRGB r g b) = S.xyz2rgb (S.ColorXYZ (a ! [0]) (a ! [1]) (a ! [2])) :: Color (S.SRGB 'S.NonLinear) Double -- >>> rgb2xyz_ [1,1,1] -- [0.9505, 1.0, 1.089] -rgb2xyz_ :: Array '[3] Double -> Array '[3] Double -rgb2xyz_ a = fromList [x, y, z] +rgb2xyz_ :: F.Array '[3] Double -> F.Array '[3] Double +rgb2xyz_ a = array [x, y, z] where - (S.ColorXYZ x y z) = S.rgb2xyz (S.ColorSRGB (a `index` [0]) (a `index` [1]) (a `index` [2])) :: Color (S.XYZ S.D65) Double + (S.ColorXYZ x y z) = S.rgb2xyz (S.ColorSRGB (a ! [0]) (a ! [1]) (a ! [2])) :: Color (S.XYZ S.D65) Double -- * xyz to lab -m1 :: Array '[3, 3] Double +m1 :: F.Array '[3, 3] Double m1 = - [ 0.8189330101, - 0.3618667424, - -0.1288597137, - 0.0329845436, - 0.9293118715, - 0.0361456387, - 0.0482003018, - 0.2643662691, - 0.6338517070 - ] - -m2 :: Array '[3, 3] Double + array + [ 0.8189330101, + 0.3618667424, + -0.1288597137, + 0.0329845436, + 0.9293118715, + 0.0361456387, + 0.0482003018, + 0.2643662691, + 0.6338517070 + ] + +m2 :: F.Array '[3, 3] Double m2 = - [ 0.2104542553, - 0.7936177850, - -0.0040720468, - 1.9779984951, - -2.4285922050, - 0.4505937099, - 0.0259040371, - 0.7827717662, - -0.8086757660 - ] + array + [ 0.2104542553, + 0.7936177850, + -0.0040720468, + 1.9779984951, + -2.4285922050, + 0.4505937099, + 0.0259040371, + 0.7827717662, + -0.8086757660 + ] cubicroot :: (Floating a, Ord a) => a -> a cubicroot x = bool ((-1) * (-x) ** (1 / 3.0)) (x ** (1 / 3.0)) (x >= 0) @@ -519,39 +443,41 @@ cubicroot x = bool ((-1) * (-x) ** (1 / 3.0)) (x ** (1 / 3.0)) (x >= 0) -- -- >>> xyz2lab_ [0,0,1] -- [0.15260258004008057, -1.4149965510120839, -0.4489272035597538] -xyz2lab_ :: Array '[3] Double -> Array '[3] Double +xyz2lab_ :: F.Array '[3] Double -> F.Array '[3] Double xyz2lab_ xyz = - dot sum (*) m2 (cubicroot <$> dot sum (*) m1 xyz) + F.dot sum (*) m2 (cubicroot <$> F.dot sum (*) m1 xyz) -m1' :: Array '[3, 3] Double +m1' :: F.Array '[3, 3] Double m1' = - [ 1.227013851103521026, - -0.5577999806518222383, - 0.28125614896646780758, - -0.040580178423280593977, - 1.1122568696168301049, - -0.071676678665601200577, - -0.076381284505706892869, - -0.42148197841801273055, - 1.5861632204407947575 - ] - -m2' :: Array '[3, 3] Double + array + [ 1.227013851103521026, + -0.5577999806518222383, + 0.28125614896646780758, + -0.040580178423280593977, + 1.1122568696168301049, + -0.071676678665601200577, + -0.076381284505706892869, + -0.42148197841801273055, + 1.5861632204407947575 + ] + +m2' :: F.Array '[3, 3] Double m2' = - [ 0.99999999845051981432, - 0.39633779217376785678, - 0.21580375806075880339, - 1.0000000088817607767, - -0.1055613423236563494, - -0.063854174771705903402, - 1.0000000546724109177, - -0.089484182094965759684, - -1.2914855378640917399 - ] - -lab2xyz_ :: Array '[3] Double -> Array '[3] Double + array + [ 0.99999999845051981432, + 0.39633779217376785678, + 0.21580375806075880339, + 1.0000000088817607767, + -0.1055613423236563494, + -0.063854174771705903402, + 1.0000000546724109177, + -0.089484182094965759684, + -1.2914855378640917399 + ] + +lab2xyz_ :: F.Array '[3] Double -> F.Array '[3] Double lab2xyz_ lab = - dot sum (*) m1' ((** 3.0) <$> dot sum (*) m2' lab) + F.dot sum (*) m1' ((** 3.0) <$> F.dot sum (*) m2' lab) -- * mixins diff --git a/src/Data/Path.hs b/src/Data/Path.hs index dc6cdfb..49ecffa 100644 --- a/src/Data/Path.hs +++ b/src/Data/Path.hs @@ -9,6 +9,7 @@ module Data.Path pointPath, movePath, scalePath, + projectPath, projectPaths, pathBoxes, pathBox, @@ -44,8 +45,6 @@ module Data.Path where import Chart.Data -import Control.Foldl qualified as L -import Control.Monad.State.Lazy import GHC.Generics import Geom2D.CubicBezier qualified as B import NumHask.Prelude @@ -107,16 +106,7 @@ scalePath x (ArcP i p) = ArcP i (fmap (x *) p) -- | Project a list of connected PathDatas from one Rect (XY plave) to a new one. projectPaths :: Rect Double -> Rect Double -> [PathData Double] -> [PathData Double] -projectPaths new old ps = - flip evalState zero $ - mapM - ( \p -> do - x <- get - let d = projectPath new old x p - put (pointPath d) - pure d - ) - ps +projectPaths new old ps = snd $ mapAccumL (\s a -> let d = projectPath new old s a in (pointPath d, d)) zero ps -- | Project a PathData from one Rect (XY plave) to a new one. projectPath :: @@ -493,7 +483,8 @@ pathBoxes :: [PathData Double] -> Maybe (Rect Double) pathBoxes [] = Nothing pathBoxes (x : xs) = Just $ - L.fold (L.Fold step begin snd) xs + snd $ + foldl' step begin xs where begin :: (Point Double, Rect Double) begin = (pointPath x, singleton (pointPath x))