├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── .tool-versions ├── LICENSE ├── README.md ├── docs.json ├── docs ├── Docs.elm ├── INTRO.md ├── MyMarkdown.elm ├── README.md ├── assets │ ├── elm-logo.svg │ └── syntax.css ├── elm.json ├── intro │ ├── arc1.svg │ ├── arc2.svg │ ├── bar1.svg │ ├── bar2.svg │ ├── bar3.svg │ ├── bar4.svg │ ├── force.svg │ ├── line1.svg │ ├── line2.svg │ └── line3.svg └── misc │ ├── Logo-600.png │ ├── Logo.png │ ├── Logo@2x.png │ ├── accent.png │ ├── blue-greens.png │ ├── blue-oranges.png │ ├── blue-purples.png │ ├── blues.png │ ├── brown-blue-greens.png │ ├── browns.png │ ├── carbon-palette1.png │ ├── carbon-palette2.png │ ├── carbonAlert.png │ ├── category10.png │ ├── category20a.png │ ├── category20b.png │ ├── category20c.png │ ├── colorblind.png │ ├── examples-600.png │ ├── examples.png │ ├── examples@2x.png │ ├── examples@3x.png │ ├── gold-greens.png │ ├── gold-oranges.png │ ├── gold-reds.png │ ├── green-blues.png │ ├── greens.png │ ├── greys.png │ ├── inferno.png │ ├── light-grey-reds.png │ ├── light-grey-teals.png │ ├── light-multi.png │ ├── light-oranges.png │ ├── logo-inline.png │ ├── logo-inline.svg │ ├── magma.png │ ├── orange-reds.png │ ├── oranges.png │ ├── paired.png │ ├── pastel1.png │ ├── pastel2.png │ ├── pink-yellow-greens.png │ ├── plasma.png │ ├── purple-blue-greens.png │ ├── purple-blues.png │ ├── purple-greens.png │ ├── purple-oranges.png │ ├── purple-reds.png │ ├── purples.png │ ├── rainbow.png │ ├── red-blues.png │ ├── red-greys.png │ ├── red-purples.png │ ├── red-yellow-blues.png │ ├── red-yellow-greens.png │ ├── reds.png │ ├── set1.png │ ├── set2.png │ ├── sinebow.png │ ├── spectral.png │ ├── stackOffsetDiverging.svg │ ├── stackOffsetExpand.svg │ ├── stackOffsetNone.svg │ ├── stackOffsetSilhouette.svg │ ├── stackOffsetWiggle.svg │ ├── tableau10.png │ ├── teal-blues.png │ ├── teals.png │ ├── turbo.png │ ├── viridis.png │ ├── warm-greys.png │ ├── yellow-green-blues.png │ ├── yellow-greens.png │ ├── yellow-orange-browns.png │ └── yellow-orange-reds.png ├── elm.json ├── examples ├── BackgroundGraph.elm ├── BarChart.elm ├── BarChartRace.elm ├── BoxPlot.elm ├── Centroid.elm ├── ColorMaps.elm ├── ColorSpaceInterpolations.elm ├── Concentric.elm ├── CornerRadius.elm ├── CrimeViz.elm ├── Curves.elm ├── CustomPieChart.elm ├── DetailChart.elm ├── ForceDirectedGraph.elm ├── ForceDirectedGraphWithZoom.elm ├── FunnelChart.elm ├── HistogramChart.elm ├── LayeredTree.elm ├── LineChart.elm ├── Mandelbrot.elm ├── MarginalScatterplot.elm ├── NorwegianCarSales.elm ├── PadAngle.elm ├── Peaks.elm ├── Petals.elm ├── PieChart.elm ├── PolarPlot.elm ├── PopulationMinnesota.elm ├── ScatterMatrix.elm ├── Scatterplot.elm ├── Slopechart.elm ├── StackedBarChart.elm ├── Sunburst.elm ├── TidyTree.elm ├── TodoAnimated.elm ├── Treemap.elm ├── WeatherRadial.elm ├── assets │ └── todo.css ├── data │ ├── category-brands.csv │ └── visit-sequences.csv └── elm.json ├── package-lock.json ├── package.json ├── review ├── elm.json └── src │ └── ReviewConfig.elm ├── src ├── Axis.elm ├── Brush.elm ├── Color │ └── Lab.elm ├── Events.elm ├── Force.elm ├── Force │ ├── Collision.elm │ ├── Jiggle.elm │ ├── ManyBody.elm │ └── QuadTree.elm ├── Hierarchy.elm ├── Hierarchy │ ├── Partition.elm │ ├── Tidy.elm │ └── Treemap.elm ├── Histogram.elm ├── Histogram │ └── Array.elm ├── Interpolation.elm ├── Scale.elm ├── Scale │ ├── Band.elm │ ├── Color.elm │ ├── Continuous.elm │ ├── Diverging.elm │ ├── Log.elm │ ├── Ordinal.elm │ ├── Quantile.elm │ ├── Quantize.elm │ ├── Sequential.elm │ ├── Threshold.elm │ └── Time.elm ├── Shape.elm ├── Shape │ ├── Bump.elm │ ├── Generators.elm │ ├── Pie.elm │ └── Stack.elm ├── Statistics.elm ├── Transition.elm ├── Zoom.elm └── Zoom │ ├── Interpolation.elm │ ├── Matrix.elm │ └── Transform.elm └── tests ├── .gitignore ├── AxisTests.elm ├── Color └── LabTests.elm ├── ForceTests.elm ├── Helper.elm ├── Hierarchy ├── TidyTests.elm └── TreemapTests.elm ├── HierarchyTests.elm ├── Histogram └── ArrayTests.elm ├── HistogramTests.elm ├── InterpolationTests.elm ├── Scale ├── BandTests.elm ├── ContinousTests.elm ├── DivergingTests.elm ├── LogTests.elm ├── OrdinalTests.elm ├── QuantileTests.elm ├── QuantizeTests.elm └── ThresholdTests.elm ├── Shape └── StackTests.elm ├── ShapeTests.elm ├── StatisticsTests.elm ├── ZoomTest.elm └── elm-verify-examples.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [gampleman] 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: elm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | reviewers: 9 | - gampleman 10 | ignore: 11 | - dependency-name: folkertdev/one-true-path-experiment 12 | versions: 13 | - 6.0.0 14 | - package-ecosystem: elm 15 | directory: "/examples" 16 | schedule: 17 | interval: monthly 18 | open-pull-requests-limit: 10 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: [push, pull_request] 6 | 7 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 8 | jobs: 9 | test: 10 | # The type of runner that the job will run on 11 | runs-on: ubuntu-latest 12 | 13 | # Steps represent a sequence of tasks that will be executed as part of the job 14 | steps: 15 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 16 | - uses: actions/checkout@v3 17 | 18 | - name: Setup Node.js environment 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: lts/* 22 | 23 | # Re-use node_modules between runs until package-lock.json changes. 24 | - name: Cache node_modules 25 | id: internal-cache-node_modules 26 | uses: actions/cache@v3 27 | with: 28 | path: node_modules 29 | key: internal-node_modules-ubuntu-latest.x-${{ hashFiles('package-lock.json') }} 30 | 31 | # Re-use ~/.elm between runs until elm.json, elm-tooling.json or 32 | # review/elm.json changes. The Elm compiler saves downloaded Elm packages 33 | # to ~/.elm, and elm-tooling saves downloaded tool executables there. 34 | - name: Cache ~/.elm 35 | uses: actions/cache@v3 36 | with: 37 | path: ~/.elm 38 | key: elm-${{ hashFiles('elm.json', 'review/elm.json') }} 39 | 40 | - name: Install npm dependencies 41 | if: steps.cache-node_modules.outputs.cache-hit != 'true' 42 | 43 | run: npm ci --omit dev 44 | 45 | - name: Run tests 46 | run: npm test 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | tests/Doc 3 | tests/VerifyExamples 4 | .DS_Store 5 | node_modules/ 6 | build/ 7 | *.swp 8 | .idea 9 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v19.5.0 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 19.5.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jakub Hampl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Elm-visualization](https://code.gampleman.eu/elm-visualization/misc/Logo-600.png) 2 | 3 | [Tutorial](https://github.com/gampleman/elm-visualization/blob/master/docs/INTRO.md) | [Docs](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/) | [Examples](https://elm-visualization.netlify.app/) | [GitHub](https://github.com/gampleman/elm-visualization) | [Changelog](https://github.com/gampleman/elm-visualization/releases) | `#visualization` on [Elm slack](https://elmlang.herokuapp.com) 4 | 5 | This project is designed to give you all the tools needed to build data visualizations. 6 | It is not a charting library in the sense that you have pre-bundled Excel-style 7 | charts, but it should contain all the tools to make building such charts relatively 8 | easy. The advantage is that you are free to design and build data visualizations 9 | that uniquely suite your needs. 10 | 11 | ## Learn by [example](https://elm-visualization.netlify.app/) 12 | 13 | [![Examples](https://code.gampleman.eu/elm-visualization/misc/examples-600.png)](https://elm-visualization.netlify.app/) 14 | 15 | or [read the introduction](https://github.com/gampleman/elm-visualization/blob/master/docs/INTRO.md). 16 | 17 | ## Getting started 18 | 19 | You will need to have [elm](https://elm-lang.org) installed. Then run: 20 | 21 | ```sh 22 | elm init 23 | elm install gampleman/elm-visualization 24 | ``` 25 | 26 | However, there are other packages that you will likely need to produce a visualization. Which depends somewhat on what you want to achieve, here are some common ones: 27 | 28 | - [avh4/elm-color](https://package.elm-lang.org/packages/avh4/elm-color/latest) for the `Color` type 29 | - [elm-community/typed-svg](https://package.elm-lang.org/packages/elm-community/typed-svg/latest) for rendering 30 | - [folkertdev/one-true-path-experiment](https://package.elm-lang.org/packages/folkertdev/one-true-path-experiment/latest) for the `Path` type 31 | - [gampleman/elm-rosetree](https://package.elm-lang.org/packages/gampleman/elm-rosetree/latest) for the `Tree` type 32 | 33 | You can use [this Ellie](https://ellie-app.com/p6X5hXxcdRCa1) to run the examples, since it has all the dependencies already installed into it. 34 | 35 | ## What's included? 36 | 37 | ### [Scales](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Scale/) 38 | 39 | Most of the time you have data that has properties that you want to display on the 40 | screen, however these properties typically aren't in pixels. Scales solve this 41 | fundamental problem by giving you convenient ways to transform raw data into positions, 42 | sizes, colors, labels and other ways to display data. 43 | 44 | ### [Axis](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Axis/) 45 | 46 | A component that allows you to visualize a Scale. Those little ticks that describe 47 | the dimensions of a plot. 48 | 49 | ### [Shapes](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Shape/) 50 | 51 | This module gives you ways to draw some fundamental shapes used in data visualization, including lines (as in line or area charts), 52 | as well as arcs (as in pie charts). 53 | 54 | ### [Force Layout](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Force/) 55 | 56 | Use a simulation of physical forces to do layout. Suitable for i.e. network graphs. 57 | 58 | ### [Hierarchy](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Hierarchy/) 59 | 60 | Layout algorithms for dealing with trees. 61 | 62 | ### [Interpolation](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Interpolation/) 63 | 64 | Smoothly transition between pairs of values. Useful for animation, or generating gradients of values. 65 | 66 | ### [Transition](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Transition/) 67 | 68 | Build complex animations using Interpolation. 69 | 70 | ### [Histogram](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Histogram/) 71 | 72 | Compute histograms of data. 73 | 74 | ### [Brush](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Brush/) 75 | 76 | Interactively select subregions of a dataset. 77 | 78 | ### [Zoom](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Zoom/) 79 | 80 | Build pan and zoom user interactions. 81 | 82 | ### [Statistics](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Statistics/) 83 | 84 | Process data to extract useful insights for visualizations. 85 | 86 | ## Acknowledgements 87 | 88 | Heavily inspired by parts of the [D3](https://github.com/d3/d3) library 89 | by [Mike Bostock](https://bost.ocks.org/mike/). However since Elm provides a 90 | great DOM abstraction already, selections are not part of this library. 91 | 92 | ## Contributing 93 | 94 | This library is still under active development, so please submit feature requests 95 | iff you are also willing to implement them. Bug reports are welcome. 96 | -------------------------------------------------------------------------------- /docs/MyMarkdown.elm: -------------------------------------------------------------------------------- 1 | module MyMarkdown exposing (strip, render) 2 | 3 | import Html exposing (Html) 4 | import Html.Attributes as Attr 5 | import Markdown.Block as Block exposing (..) 6 | import Markdown.Renderer exposing (Renderer) 7 | import Markdown.Parser 8 | import Markdown.Html 9 | 10 | 11 | render : String -> Html.Html msg 12 | render = 13 | Markdown.Parser.parse 14 | >> Result.withDefault [] 15 | >> Markdown.Renderer.render Markdown.Renderer.defaultHtmlRenderer 16 | >> Result.withDefault ([Html.text "Something went wrong"]) 17 | >> Html.div [] 18 | 19 | 20 | 21 | strip : String -> String 22 | strip = 23 | Markdown.Parser.parse 24 | >> Result.withDefault [] 25 | >> Markdown.Renderer.render renderer 26 | >> Result.withDefault [] 27 | >> String.join " " 28 | >> String.replace " " " " 29 | 30 | renderer : Renderer String 31 | renderer = 32 | { heading = \{ children } -> String.concat children 33 | , paragraph = String.concat 34 | , hardLineBreak = "\n" 35 | , blockQuote = String.concat 36 | , strong = String.concat 37 | , emphasis = String.concat 38 | , codeSpan = identity 39 | , link = \link content -> String.concat content 40 | , image = \imageInfo -> "" 41 | , text = identity 42 | , unorderedList = 43 | \items -> 44 | items 45 | |> List.map 46 | (\(Block.ListItem _ children) -> 47 | String.concat children 48 | ) 49 | |> String.join " " 50 | 51 | , orderedList = 52 | \startingIndex items -> 53 | 54 | (items 55 | |> List.indexedMap 56 | (\ idx itemBlocks -> 57 | String.fromInt (idx + startingIndex) ++ ") " ++ String.concat itemBlocks 58 | ) 59 | |> String.join " " 60 | ) 61 | , html = Markdown.Html.oneOf [] |> Markdown.Html.map (always String.concat) 62 | , codeBlock = \block -> block.body 63 | 64 | , thematicBreak = " " 65 | , table = always "" 66 | , tableHeader = always "" 67 | , tableBody = always "" 68 | , tableRow =always "" 69 | , tableHeaderCell = \_ _ -> "" 70 | , tableCell = \_ _ -> "" 71 | , strikethrough = String.concat 72 | } 73 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ### The examples website 2 | 3 | Stuff in this folder is used to generate the Examples website. We use [elm-example-publisher](https://github.com/gampleman/elm-example-publisher) to build the website. 4 | 5 | The Docs.elm file is the Elm program that actually generates the HTML. You can create a local version of the website by going to the root of the directory and running: 6 | 7 | ```sh 8 | # Install dependencies 9 | npm install 10 | 11 | # Build website 12 | npm run build-docs 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/assets/elm-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/assets/syntax.css: -------------------------------------------------------------------------------- 1 | .hljs { 2 | display: block; 3 | overflow-x: auto; 4 | padding: 0.5em; 5 | color: #383a42; 6 | background: #fafafa; 7 | } 8 | 9 | .hljs-comment, 10 | .diff .hljs-header, 11 | .hljs-doctype, 12 | .hljs-pi, 13 | .lisp .hljs-string, 14 | .hljs-javadoc { 15 | color: #93a1a1; 16 | } 17 | 18 | /* Solarized Green */ 19 | .hljs-keyword, 20 | .hljs-winutils, 21 | .method, 22 | .hljs-addition, 23 | .css .hljs-tag, 24 | .hljs-request, 25 | .hljs-status, 26 | .nginx .hljs-title { 27 | color: #859900; 28 | } 29 | 30 | /* Solarized Cyan */ 31 | .hljs-number, 32 | .hljs-command, 33 | .hljs-string, 34 | .hljs-tag .hljs-value, 35 | .hljs-rules .hljs-value, 36 | .hljs-phpdoc, 37 | .hljs-dartdoc, 38 | .tex .hljs-formula, 39 | .hljs-regexp, 40 | .hljs-hexcolor, 41 | .hljs-link_url { 42 | color: #2aa198; 43 | } 44 | 45 | /* Solarized Blue */ 46 | .hljs-title, 47 | .hljs-localvars, 48 | .hljs-chunk, 49 | .hljs-decorator, 50 | .hljs-built_in, 51 | .hljs-identifier, 52 | .vhdl .hljs-literal, 53 | .hljs-id, 54 | .css .hljs-function { 55 | color: #268bd2; 56 | } 57 | 58 | /* Solarized Yellow */ 59 | .hljs-attribute, 60 | .hljs-variable, 61 | .lisp .hljs-body, 62 | .smalltalk .hljs-number, 63 | .hljs-constant, 64 | .hljs-class .hljs-title, 65 | .hljs-parent, 66 | .hljs-type, 67 | .hljs-link_reference { 68 | color: #b58900; 69 | } 70 | 71 | /* Solarized Orange */ 72 | .hljs-preprocessor, 73 | .hljs-preprocessor .hljs-keyword, 74 | .hljs-pragma, 75 | .hljs-shebang, 76 | .hljs-symbol, 77 | .hljs-symbol .hljs-string, 78 | .diff .hljs-change, 79 | .hljs-special, 80 | .hljs-attr_selector, 81 | .hljs-subst, 82 | .hljs-cdata, 83 | .css .hljs-pseudo, 84 | .hljs-header { 85 | color: #cb4b16; 86 | } 87 | 88 | /* Solarized Red */ 89 | .hljs-deletion, 90 | .hljs-important { 91 | color: #dc322f; 92 | } 93 | 94 | /* Solarized Violet */ 95 | .hljs-link_label { 96 | color: #6c71c4; 97 | } 98 | 99 | .tex .hljs-formula { 100 | background: #eee8d5; 101 | } 102 | -------------------------------------------------------------------------------- /docs/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "." 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "dillonkearns/elm-markdown": "7.0.1", 10 | "elm/browser": "1.0.2", 11 | "elm/core": "1.0.5", 12 | "elm/html": "1.0.0", 13 | "elm/json": "1.1.3", 14 | "rtfeldman/elm-css": "16.1.0" 15 | }, 16 | "indirect": { 17 | "elm/parser": "1.1.0", 18 | "elm/regex": "1.0.0", 19 | "elm/time": "1.0.0", 20 | "elm/url": "1.0.0", 21 | "elm/virtual-dom": "1.0.2", 22 | "rtfeldman/elm-hex": "1.0.0" 23 | } 24 | }, 25 | "test-dependencies": { 26 | "direct": {}, 27 | "indirect": {} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/intro/arc1.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/intro/arc2.svg: -------------------------------------------------------------------------------- 1 | 🇨🇳123🇺🇸95🇬🇧45🇨🇿21🇨🇾4 2 | -------------------------------------------------------------------------------- /docs/intro/bar1.svg: -------------------------------------------------------------------------------- 1 | 2 | 020406080100120🇨🇳🇺🇸🇬🇧🇨🇿🇨🇾1239545214 3 | 4 | -------------------------------------------------------------------------------- /docs/intro/bar2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 95 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/intro/bar3.svg: -------------------------------------------------------------------------------- 1 | 2 | 1239545214 3 | 4 | -------------------------------------------------------------------------------- /docs/intro/bar4.svg: -------------------------------------------------------------------------------- 1 | 020406080100120🇨🇳🇺🇸🇬🇧🇨🇿🇨🇾1239545214 2 | -------------------------------------------------------------------------------- /docs/intro/force.svg: -------------------------------------------------------------------------------- 1 | MyrielNapoleonMlle.BaptistineMme.MagloireCountessdeLoGeborandChamptercierCravatteCount 2 | -------------------------------------------------------------------------------- /docs/misc/Logo-600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/Logo-600.png -------------------------------------------------------------------------------- /docs/misc/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/Logo.png -------------------------------------------------------------------------------- /docs/misc/Logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/Logo@2x.png -------------------------------------------------------------------------------- /docs/misc/accent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/accent.png -------------------------------------------------------------------------------- /docs/misc/blue-greens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/blue-greens.png -------------------------------------------------------------------------------- /docs/misc/blue-oranges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/blue-oranges.png -------------------------------------------------------------------------------- /docs/misc/blue-purples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/blue-purples.png -------------------------------------------------------------------------------- /docs/misc/blues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/blues.png -------------------------------------------------------------------------------- /docs/misc/brown-blue-greens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/brown-blue-greens.png -------------------------------------------------------------------------------- /docs/misc/browns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/browns.png -------------------------------------------------------------------------------- /docs/misc/carbon-palette1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/carbon-palette1.png -------------------------------------------------------------------------------- /docs/misc/carbon-palette2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/carbon-palette2.png -------------------------------------------------------------------------------- /docs/misc/carbonAlert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/carbonAlert.png -------------------------------------------------------------------------------- /docs/misc/category10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/category10.png -------------------------------------------------------------------------------- /docs/misc/category20a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/category20a.png -------------------------------------------------------------------------------- /docs/misc/category20b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/category20b.png -------------------------------------------------------------------------------- /docs/misc/category20c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/category20c.png -------------------------------------------------------------------------------- /docs/misc/colorblind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/colorblind.png -------------------------------------------------------------------------------- /docs/misc/examples-600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/examples-600.png -------------------------------------------------------------------------------- /docs/misc/examples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/examples.png -------------------------------------------------------------------------------- /docs/misc/examples@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/examples@2x.png -------------------------------------------------------------------------------- /docs/misc/examples@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/examples@3x.png -------------------------------------------------------------------------------- /docs/misc/gold-greens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/gold-greens.png -------------------------------------------------------------------------------- /docs/misc/gold-oranges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/gold-oranges.png -------------------------------------------------------------------------------- /docs/misc/gold-reds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/gold-reds.png -------------------------------------------------------------------------------- /docs/misc/green-blues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/green-blues.png -------------------------------------------------------------------------------- /docs/misc/greens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/greens.png -------------------------------------------------------------------------------- /docs/misc/greys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/greys.png -------------------------------------------------------------------------------- /docs/misc/inferno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/inferno.png -------------------------------------------------------------------------------- /docs/misc/light-grey-reds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/light-grey-reds.png -------------------------------------------------------------------------------- /docs/misc/light-grey-teals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/light-grey-teals.png -------------------------------------------------------------------------------- /docs/misc/light-multi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/light-multi.png -------------------------------------------------------------------------------- /docs/misc/light-oranges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/light-oranges.png -------------------------------------------------------------------------------- /docs/misc/logo-inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/logo-inline.png -------------------------------------------------------------------------------- /docs/misc/logo-inline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ELM-VISUALIZATIO N 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/misc/magma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/magma.png -------------------------------------------------------------------------------- /docs/misc/orange-reds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/orange-reds.png -------------------------------------------------------------------------------- /docs/misc/oranges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/oranges.png -------------------------------------------------------------------------------- /docs/misc/paired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/paired.png -------------------------------------------------------------------------------- /docs/misc/pastel1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/pastel1.png -------------------------------------------------------------------------------- /docs/misc/pastel2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/pastel2.png -------------------------------------------------------------------------------- /docs/misc/pink-yellow-greens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/pink-yellow-greens.png -------------------------------------------------------------------------------- /docs/misc/plasma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/plasma.png -------------------------------------------------------------------------------- /docs/misc/purple-blue-greens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/purple-blue-greens.png -------------------------------------------------------------------------------- /docs/misc/purple-blues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/purple-blues.png -------------------------------------------------------------------------------- /docs/misc/purple-greens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/purple-greens.png -------------------------------------------------------------------------------- /docs/misc/purple-oranges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/purple-oranges.png -------------------------------------------------------------------------------- /docs/misc/purple-reds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/purple-reds.png -------------------------------------------------------------------------------- /docs/misc/purples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/purples.png -------------------------------------------------------------------------------- /docs/misc/rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/rainbow.png -------------------------------------------------------------------------------- /docs/misc/red-blues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/red-blues.png -------------------------------------------------------------------------------- /docs/misc/red-greys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/red-greys.png -------------------------------------------------------------------------------- /docs/misc/red-purples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/red-purples.png -------------------------------------------------------------------------------- /docs/misc/red-yellow-blues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/red-yellow-blues.png -------------------------------------------------------------------------------- /docs/misc/red-yellow-greens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/red-yellow-greens.png -------------------------------------------------------------------------------- /docs/misc/reds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/reds.png -------------------------------------------------------------------------------- /docs/misc/set1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/set1.png -------------------------------------------------------------------------------- /docs/misc/set2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/set2.png -------------------------------------------------------------------------------- /docs/misc/sinebow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/sinebow.png -------------------------------------------------------------------------------- /docs/misc/spectral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/spectral.png -------------------------------------------------------------------------------- /docs/misc/tableau10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/tableau10.png -------------------------------------------------------------------------------- /docs/misc/teal-blues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/teal-blues.png -------------------------------------------------------------------------------- /docs/misc/teals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/teals.png -------------------------------------------------------------------------------- /docs/misc/turbo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/turbo.png -------------------------------------------------------------------------------- /docs/misc/viridis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/viridis.png -------------------------------------------------------------------------------- /docs/misc/warm-greys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/warm-greys.png -------------------------------------------------------------------------------- /docs/misc/yellow-green-blues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/yellow-green-blues.png -------------------------------------------------------------------------------- /docs/misc/yellow-greens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/yellow-greens.png -------------------------------------------------------------------------------- /docs/misc/yellow-orange-browns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/yellow-orange-browns.png -------------------------------------------------------------------------------- /docs/misc/yellow-orange-reds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gampleman/elm-visualization/f5897312fa320cdaaf34642aaf08accc902087af/docs/misc/yellow-orange-reds.png -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "gampleman/elm-visualization", 4 | "summary": "A data visualization package for Elm", 5 | "license": "MIT", 6 | "version": "2.4.2", 7 | "exposed-modules": [ 8 | "Scale", 9 | "Scale.Color", 10 | "Axis", 11 | "Shape", 12 | "Force", 13 | "Hierarchy", 14 | "Statistics", 15 | "Histogram", 16 | "Interpolation", 17 | "Transition", 18 | "Brush", 19 | "Zoom" 20 | ], 21 | "elm-version": "0.19.0 <= v < 0.20.0", 22 | "dependencies": { 23 | "avh4/elm-color": "1.0.0 <= v < 2.0.0", 24 | "elm/browser": "1.0.2 <= v < 2.0.0", 25 | "elm/core": "1.0.0 <= v < 2.0.0", 26 | "elm/html": "1.0.0 <= v < 2.0.0", 27 | "elm/json": "1.0.0 <= v < 2.0.0", 28 | "elm/svg": "1.0.1 <= v < 2.0.0", 29 | "elm/time": "1.0.0 <= v < 2.0.0", 30 | "elmcraft/core-extra": "1.0.0 <= v < 3.0.0", 31 | "folkertdev/one-true-path-experiment": "5.0.0 <= v < 7.0.0", 32 | "gampleman/elm-rosetree": "1.0.0 <= v < 2.0.0", 33 | "ianmackenzie/elm-geometry": "3.6.0 <= v < 4.0.0", 34 | "ianmackenzie/elm-units-prefixed": "2.0.0 <= v < 3.0.0", 35 | "justinmimbs/time-extra": "1.0.1 <= v < 2.0.0", 36 | "rtfeldman/elm-hex": "1.0.0 <= v < 2.0.0", 37 | "ryan-haskell/date-format": "1.0.0 <= v < 2.0.0" 38 | }, 39 | "test-dependencies": { 40 | "elm/regex": "1.0.0 <= v < 2.0.0", 41 | "elm-explorations/test": "2.0.0 <= v < 3.0.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/BarChart.elm: -------------------------------------------------------------------------------- 1 | module BarChart exposing (main) 2 | 3 | {-| This module shows how to build a simple bar chart. 4 | 5 | 1. It starts by setting out the basic variables to define a drawing area. 6 | 2. Then it creates scales that encode the x and y dimensions of the bars. 7 | 3. Based on these it sets up axes. 8 | 4. Finally it assembles everything into a chart. 9 | 10 | @category Basics 11 | 12 | -} 13 | 14 | import Axis 15 | import DateFormat 16 | import Scale exposing (BandScale, ContinuousScale, defaultBandConfig) 17 | import Time 18 | import TypedSvg exposing (g, rect, style, svg, text_) 19 | import TypedSvg.Attributes exposing (class, textAnchor, transform, viewBox) 20 | import TypedSvg.Attributes.InPx exposing (height, width, x, y) 21 | import TypedSvg.Core exposing (Svg, text) 22 | import TypedSvg.Types exposing (AnchorAlignment(..), Transform(..)) 23 | 24 | 25 | w : Float 26 | w = 27 | 900 28 | 29 | 30 | h : Float 31 | h = 32 | 450 33 | 34 | 35 | padding : Float 36 | padding = 37 | 30 38 | 39 | 40 | xScale : List ( Time.Posix, Float ) -> BandScale Time.Posix 41 | xScale model = 42 | List.map Tuple.first model 43 | |> Scale.band { defaultBandConfig | paddingInner = 0.1, paddingOuter = 0.2 } ( 0, w - 2 * padding ) 44 | 45 | 46 | yScale : ContinuousScale Float 47 | yScale = 48 | Scale.linear ( h - 2 * padding, 0 ) ( 0, 5 ) 49 | 50 | 51 | dateFormat : Time.Posix -> String 52 | dateFormat = 53 | DateFormat.format [ DateFormat.dayOfMonthFixed, DateFormat.text " ", DateFormat.monthNameAbbreviated ] Time.utc 54 | 55 | 56 | xAxis : List ( Time.Posix, Float ) -> Svg msg 57 | xAxis model = 58 | Axis.bottom [] (Scale.toRenderable dateFormat (xScale model)) 59 | 60 | 61 | yAxis : Svg msg 62 | yAxis = 63 | Axis.left [ Axis.tickCount 5 ] yScale 64 | 65 | 66 | column : BandScale Time.Posix -> ( Time.Posix, Float ) -> Svg msg 67 | column scale ( date, value ) = 68 | g [ class [ "column" ] ] 69 | [ rect 70 | [ x <| Scale.convert scale date 71 | , y <| Scale.convert yScale value 72 | , width <| Scale.bandwidth scale 73 | , height <| h - Scale.convert yScale value - 2 * padding 74 | ] 75 | [] 76 | , text_ 77 | [ x <| Scale.convert (Scale.toRenderable dateFormat scale) date 78 | , y <| Scale.convert yScale value - 5 79 | , textAnchor AnchorMiddle 80 | ] 81 | [ text <| String.fromFloat value ] 82 | ] 83 | 84 | 85 | view : List ( Time.Posix, Float ) -> Svg msg 86 | view model = 87 | svg [ viewBox 0 0 w h ] 88 | [ style [] [ text """ 89 | .column rect { fill: rgba(118, 214, 78, 0.8); } 90 | .column text { display: none; } 91 | .column:hover rect { fill: rgb(118, 214, 78); } 92 | .column:hover text { display: inline; } 93 | """ ] 94 | , g [ transform [ Translate (padding - 1) (h - padding) ] ] 95 | [ xAxis model ] 96 | , g [ transform [ Translate (padding - 1) padding ] ] 97 | [ yAxis ] 98 | , g [ transform [ Translate padding padding ], class [ "series" ] ] <| 99 | List.map (column (xScale model)) model 100 | ] 101 | 102 | 103 | main : Svg msg 104 | main = 105 | view timeSeries 106 | 107 | 108 | timeSeries : List ( Time.Posix, Float ) 109 | timeSeries = 110 | [ ( Time.millisToPosix 1448928000000, 2.5 ) 111 | , ( Time.millisToPosix 1451606400000, 2 ) 112 | , ( Time.millisToPosix 1452211200000, 3.5 ) 113 | , ( Time.millisToPosix 1452816000000, 2 ) 114 | , ( Time.millisToPosix 1453420800000, 3 ) 115 | , ( Time.millisToPosix 1454284800000, 1 ) 116 | , ( Time.millisToPosix 1456790400000, 1.2 ) 117 | ] 118 | -------------------------------------------------------------------------------- /examples/Centroid.elm: -------------------------------------------------------------------------------- 1 | module Centroid exposing (main) 2 | 3 | {-| The black dots show the midpoint computed by `centroid`. Note that this is 4 | not the geometric center of the arc, which may be outside the arc; this method 5 | is merely a convenience for positioning labels. 6 | 7 | @category Reference 8 | 9 | -} 10 | 11 | import Array exposing (Array) 12 | import Color exposing (Color) 13 | import Path 14 | import Shape exposing (Arc, defaultPieConfig) 15 | import TypedSvg exposing (circle, g, svg) 16 | import TypedSvg.Attributes exposing (fill, stroke, transform, viewBox) 17 | import TypedSvg.Attributes.InPx exposing (cx, cy, r) 18 | import TypedSvg.Core exposing (Svg) 19 | import TypedSvg.Types exposing (Paint(..), Transform(..)) 20 | 21 | 22 | w : Float 23 | w = 24 | 990 25 | 26 | 27 | h : Float 28 | h = 29 | 504 30 | 31 | 32 | rgba255 : Int -> Int -> Int -> Float -> Color 33 | rgba255 r g b a = 34 | Color.fromRgba { red = toFloat r / 255, green = toFloat g / 255, blue = toFloat b / 255, alpha = a } 35 | 36 | 37 | colors : Array Color 38 | colors = 39 | Array.fromList 40 | [ rgba255 31 119 180 0.5 41 | , rgba255 255 127 14 0.5 42 | , rgba255 44 159 44 0.5 43 | , rgba255 214 39 40 0.5 44 | , rgba255 148 103 189 0.5 45 | , rgba255 140 86 75 0.5 46 | , rgba255 227 119 194 0.5 47 | , rgba255 128 128 128 0.5 48 | , rgba255 188 189 34 0.5 49 | , rgba255 23 190 207 0.5 50 | ] 51 | 52 | 53 | radius : Float 54 | radius = 55 | min (w / 2) h / 2 - 10 56 | 57 | 58 | circular : List Arc -> Svg msg 59 | circular arcs = 60 | let 61 | makeSlice index datum = 62 | Path.element (Shape.arc datum) 63 | [ fill <| Paint <| Maybe.withDefault Color.black <| Array.get index colors 64 | , stroke <| Paint Color.black 65 | ] 66 | 67 | makeDot datum = 68 | let 69 | ( x, y ) = 70 | Shape.centroid datum 71 | in 72 | circle [ cx x, cy y, r 5 ] [] 73 | in 74 | g [ transform [ Translate radius radius ] ] 75 | [ g [] <| List.indexedMap makeSlice arcs 76 | , g [] <| List.map makeDot arcs 77 | ] 78 | 79 | 80 | annular : List Arc -> Svg msg 81 | annular arcs = 82 | let 83 | makeSlice index datum = 84 | Path.element (Shape.arc { datum | innerRadius = radius - 60 }) 85 | [ fill <| Paint <| Maybe.withDefault Color.black <| Array.get index colors 86 | , stroke <| Paint Color.black 87 | ] 88 | 89 | makeDot datum = 90 | let 91 | ( x, y ) = 92 | Shape.centroid { datum | innerRadius = radius - 60 } 93 | in 94 | circle [ cx x, cy y, r 5 ] [] 95 | in 96 | g [ transform [ Translate (3 * radius + 20) radius ] ] 97 | [ g [] <| List.indexedMap makeSlice arcs 98 | , g [] <| List.map makeDot arcs 99 | ] 100 | 101 | 102 | view : List Float -> Svg msg 103 | view model = 104 | let 105 | pieData = 106 | model |> Shape.pie { defaultPieConfig | outerRadius = radius } 107 | in 108 | svg [ viewBox 0 0 w h ] 109 | [ circular pieData 110 | , annular pieData 111 | ] 112 | 113 | 114 | data : List Float 115 | data = 116 | [ 1, 1, 2, 3, 5, 8, 13 ] 117 | 118 | 119 | main : Svg msg 120 | main = 121 | view data 122 | -------------------------------------------------------------------------------- /examples/ColorMaps.elm: -------------------------------------------------------------------------------- 1 | module ColorMaps exposing (main) 2 | 3 | {-| This module shows the color schemes available in the Scale.Color module 4 | 5 | @category Reference 6 | 7 | -} 8 | 9 | import Color exposing (Color) 10 | import Html exposing (Html) 11 | import Html.Attributes exposing (style, title) 12 | import Interpolation exposing (Interpolator) 13 | import Scale.Color 14 | 15 | 16 | pallete : String -> List ( String, List (Html msg) ) -> Html msg 17 | pallete title items = 18 | Html.section [ style "display" "flex", style "align-items" "flex-start", style "margin-bottom" "30px", style "padding-right" "20px" ] 19 | [ Html.h3 [ style "position" "sticky", style "top" "10px", style "width" "250px" ] [ Html.text title ] 20 | , items 21 | |> List.map 22 | (\( name, colors ) -> 23 | Html.div [] 24 | [ Html.h5 [ style "margin-bottom" "4px", style "margin-top" "10px", style "font-weight" "normal" ] [ Html.text name ] 25 | , Html.div [ style "display" "flex" ] colors 26 | ] 27 | ) 28 | |> Html.div [ style "flex-grow" "1" ] 29 | ] 30 | 31 | 32 | interpolatorPallete : String -> List ( String, Interpolator Color ) -> Html msg 33 | interpolatorPallete title items = 34 | pallete title (List.map (Tuple.mapSecond interpolation) items) 35 | 36 | 37 | collectionPallete : String -> List ( String, List Color ) -> Html msg 38 | collectionPallete title items = 39 | pallete title (List.map (Tuple.mapSecond (List.map swatch)) items) 40 | 41 | 42 | swatch : Color -> Html msg 43 | swatch color = 44 | Html.div [ style "background-color" (Color.toCssString color), title (Color.toCssString color), style "height" "30px", style "flex-grow" "1" ] [] 45 | 46 | 47 | interpolation : Interpolator Color -> List (Html msg) 48 | interpolation interpolator = 49 | List.range 1 61 50 | |> List.map (\v -> toFloat v / 60) 51 | |> List.map interpolator 52 | |> List.map swatch 53 | 54 | 55 | view : Html msg 56 | view = 57 | Html.div [ style "height" "100vh", style "overflow-y" "auto", style "font-family" "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif" ] 58 | [ collectionPallete "Categorical" 59 | [ ( "category10", Scale.Color.category10 ) 60 | , ( "tableau10", Scale.Color.tableau10 ) 61 | , ( "paired", Scale.Color.paired ) 62 | , ( "set1", Scale.Color.set1 ) 63 | , ( "set2", Scale.Color.set2 ) 64 | , ( "accent", Scale.Color.accent ) 65 | , ( "pastel1", Scale.Color.pastel1 ) 66 | , ( "pastel2", Scale.Color.pastel2 ) 67 | , ( "colorblind", Scale.Color.colorblind ) 68 | ] 69 | , interpolatorPallete "Sequential Single-Hue" 70 | [ ( "blues", Scale.Color.bluesInterpolator ) 71 | , ( "greens", Scale.Color.greensInterpolator ) 72 | , ( "greys", Scale.Color.greysInterpolator ) 73 | , ( "oranges", Scale.Color.orangesInterpolator ) 74 | , ( "light-oranges", Scale.Color.lightOrangeInterpolator ) 75 | , ( "purples", Scale.Color.purplesInterpolator ) 76 | , ( "reds", Scale.Color.redsInterpolator ) 77 | , ( "browns", Scale.Color.brownsInterpolator ) 78 | , ( "teals", Scale.Color.tealInterpolator ) 79 | , ( "warm greys", Scale.Color.warmGreysInterpolator ) 80 | ] 81 | , interpolatorPallete "Sequential Multi-Hue" 82 | [ ( "blue-greens", Scale.Color.blueGreenInterpolator ) 83 | , ( "blue-purples", Scale.Color.bluePurpleInterpolator ) 84 | , ( "green-blues", Scale.Color.greenBlueInterpolator ) 85 | , ( "orange-reds", Scale.Color.orangeRedInterpolator ) 86 | , ( "purple-blues", Scale.Color.purpleBlueInterpolator ) 87 | , ( "purple-blue-greens", Scale.Color.purpleBlueGreenInterpolator ) 88 | , ( "purple-reds", Scale.Color.purpleRedInterpolator ) 89 | , ( "red-purples", Scale.Color.redPurpleInterpolator ) 90 | , ( "yellow-greens", Scale.Color.yellowGreenInterpolator ) 91 | , ( "yellow-orange-browns", Scale.Color.yellowOrangeBrownInterpolator ) 92 | , ( "yellow-orange-reds", Scale.Color.yellowOrangeRedInterpolator ) 93 | , ( "teal-bluess", Scale.Color.tealBluesInterpolator ) 94 | , ( "gold-greens", Scale.Color.goldGreensInterpolator ) 95 | , ( "gold-oranges", Scale.Color.goldOrangeInterpolator ) 96 | , ( "gold-reds", Scale.Color.goldRedInterpolator ) 97 | , ( "light-grey-reds", Scale.Color.lightGreyRedInterpolator ) 98 | , ( "light-grey-teals", Scale.Color.lightGreyTealInterpolator ) 99 | , ( "light-multi", Scale.Color.lightMultiInterpolator ) 100 | ] 101 | , interpolatorPallete "Diverging" 102 | [ ( "carbon palette1", Scale.Color.carbonDiverging1Interpolator ) 103 | , ( "carbon palette2", Scale.Color.carbonDiverging2Interpolator ) 104 | , ( "blue-oranges", Scale.Color.blueOrangeInterpolator ) 105 | , ( "brown-blue-greens", Scale.Color.brownBlueGreenInterpolator ) 106 | , ( "purple-greens", Scale.Color.purpleGreenInterpolator ) 107 | , ( "purple-oranges", Scale.Color.purpleOrangeInterpolator ) 108 | , ( "red-blues", Scale.Color.redBlueInterpolator ) 109 | , ( "red-greys", Scale.Color.redGreyInterpolator ) 110 | , ( "yellow-green-blues", Scale.Color.yellowGreenBlueInterpolator ) 111 | , ( "red-yellow-blues", Scale.Color.redYellowBlueInterpolator ) 112 | , ( "red-yellow-greens", Scale.Color.redYellowGreenInterpolator ) 113 | , ( "pink-yellow-greens", Scale.Color.pinkYellowGreenInterpolator ) 114 | , ( "spectral", Scale.Color.spectralInterpolator ) 115 | ] 116 | , interpolatorPallete "Cyclic" 117 | [ ( "rainbow", Scale.Color.rainbowInterpolator ) 118 | , ( "sinebow", Scale.Color.sinebowInterpolator ) 119 | , ( "turbo", Scale.Color.turboInterpolator ) 120 | ] 121 | ] 122 | 123 | 124 | main : Html msg 125 | main = 126 | view 127 | -------------------------------------------------------------------------------- /examples/ColorSpaceInterpolations.elm: -------------------------------------------------------------------------------- 1 | module ColorSpaceInterpolations exposing (Model, main) 2 | 3 | {-| This module shows how to build some simple colour space palettes. 4 | 5 | @category Reference 6 | 7 | -} 8 | 9 | import Color exposing (Color) 10 | import Example 11 | import Html exposing (Html, div) 12 | import Html.Attributes exposing (href, style) 13 | import Interpolation exposing (Interpolator) 14 | 15 | 16 | type alias Model = 17 | { startColor : Color 18 | , endColor : Color 19 | , count : Int 20 | } 21 | 22 | 23 | palette : Model -> (Color -> Color -> Interpolator Color) -> Html msg 24 | palette model colorSpaceInterpolator = 25 | div [ style "display" "flex", style "flex-grow" "1" ] 26 | (Interpolation.samples model.count 27 | (colorSpaceInterpolator model.startColor model.endColor) 28 | |> List.map 29 | (\color -> 30 | Html.div [ style "background-color" (Color.toCssString color), style "flex-grow" "1" ] [] 31 | ) 32 | ) 33 | 34 | 35 | view : Model -> Html msg 36 | view model = 37 | Html.div [ style "display" "flex", style "min-height" "100vh", style "padding-right" "10px", style "flex-grow" "1" ] 38 | [ Html.div 39 | [ style "flex-grow" "1", style "margin-right" "20px", style "display" "flex", style "flex-direction" "column" ] 40 | [ title "rgb" "https://en.wikipedia.org/wiki/RGB_color_model" 41 | , palette model Interpolation.rgb 42 | , title "hsl" "https://en.wikipedia.org/wiki/HSL_and_HSV" 43 | , palette model Interpolation.hsl 44 | , title "hslLong" "https://en.wikipedia.org/wiki/HSL_and_HSV" 45 | , palette model Interpolation.hslLong 46 | , title "lab" "https://en.wikipedia.org/wiki/CIELAB_color_space" 47 | , palette model Interpolation.lab 48 | , title "hcl" "https://en.wikipedia.org/wiki/HCL_color_space" 49 | , palette model Interpolation.hcl 50 | , title "hclLong" "https://en.wikipedia.org/wiki/HCL_color_space" 51 | , palette model Interpolation.hclLong 52 | ] 53 | ] 54 | 55 | 56 | title : String -> String -> Html msg 57 | title label url = 58 | Html.h3 [ style "margin-bottom" "5px", style "margin-left" "5px", style "font" "16px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif" ] 59 | [ Html.a [ href url ] [ Html.text label ] 60 | ] 61 | 62 | 63 | main : Example.Program Model 64 | main = 65 | Example.configuration 66 | { startColor = Color.rgb255 0 255 0 67 | , endColor = Color.rgb255 255 2 0 68 | , count = 50 69 | } 70 | [ Example.colorPicker "Start Color" .startColor (\v m -> { m | startColor = v }) 71 | , Example.colorPicker "End Color" .endColor (\v m -> { m | endColor = v }) 72 | , Example.intSlider "Number of Colors" { min = 3, max = 100 } .count (\v m -> { m | count = v }) 73 | ] 74 | |> Example.withTitle "Color Space Interpolations" 75 | |> Example.application view 76 | -------------------------------------------------------------------------------- /examples/Concentric.elm: -------------------------------------------------------------------------------- 1 | module Concentric exposing (main) 2 | 3 | {-| Having fun with arcs and animations. 4 | 5 | @category Art 6 | 7 | -} 8 | 9 | import Browser 10 | import Browser.Events 11 | import Color exposing (Color) 12 | import Path 13 | import Random 14 | import Scale exposing (defaultBandConfig) 15 | import Scale.Color 16 | import Shape exposing (defaultPieConfig) 17 | import TypedSvg exposing (g, rect, svg) 18 | import TypedSvg.Attributes exposing (fill, transform, viewBox) 19 | import TypedSvg.Attributes.InPx exposing (height, width) 20 | import TypedSvg.Core exposing (Svg, text) 21 | import TypedSvg.Types exposing (Paint(..), Transform(..)) 22 | 23 | 24 | type alias Model = 25 | { data : List (List Float), frame : Int } 26 | 27 | 28 | type Msg 29 | = Generated (List (List Float)) 30 | | Tick Int 31 | 32 | 33 | w : Float 34 | w = 35 | 900 36 | 37 | 38 | h : Float 39 | h = 40 | 450 41 | 42 | 43 | arcWidth : Float 44 | arcWidth = 45 | 20 46 | 47 | 48 | radius : Float 49 | radius = 50 | (max w h / 2) + 50 51 | 52 | 53 | scale = 54 | Scale.band { defaultBandConfig | paddingInner = 0.3 } ( arcWidth, radius ) (List.range 0 (floor (radius / arcWidth))) 55 | 56 | 57 | colorScale = 58 | Scale.sequential Scale.Color.plasmaInterpolator ( 0, 1 ) 59 | 60 | 61 | getColor : Int -> Int -> Color 62 | getColor ring value = 63 | let 64 | seed = 65 | Random.initialSeed ((ring + 1) * (value + 1)) 66 | 67 | gen = 68 | Random.map (Scale.convert colorScale) (Random.float 0.2 0.7) 69 | 70 | ( val, _ ) = 71 | Random.step gen seed 72 | in 73 | val 74 | 75 | 76 | tau = 77 | pi * 2 78 | 79 | 80 | view : Model -> Svg msg 81 | view model = 82 | svg [ viewBox 0 0 w h ] 83 | [ rect [ width w, height h, fill (Paint (Color.rgb255 50 50 50)) ] [] 84 | , model.data 85 | |> List.indexedMap 86 | (\index data -> 87 | if model.frame > index * 100 then 88 | data 89 | |> Shape.pie 90 | { defaultPieConfig 91 | | innerRadius = Scale.convert scale index 92 | , outerRadius = Scale.convert scale index + Scale.bandwidth scale 93 | , padAngle = 0.03 94 | , cornerRadius = arcWidth 95 | , sortingFn = \_ _ -> EQ 96 | , startAngle = toFloat index 97 | , endAngle = toFloat index + tau 98 | } 99 | |> List.indexedMap 100 | (\subIndex datum -> 101 | Path.element (Shape.arc datum) 102 | [ fill (Paint (getColor index subIndex)) 103 | , transform [ Rotate (toFloat model.frame / 100 * alternaring index) 0 0 ] 104 | ] 105 | ) 106 | |> g [] 107 | 108 | else 109 | text "" 110 | ) 111 | |> g 112 | [ transform [ Translate (w / 2) (h / 2) ] 113 | ] 114 | ] 115 | 116 | 117 | alternaring v = 118 | toFloat ((modBy 2 v * 2) - 1) 119 | 120 | 121 | dataGenerator = 122 | Random.list (floor (radius / arcWidth)) 123 | (Random.int 2 10 124 | |> Random.andThen (\len -> Random.list len (Random.float 0.1 5)) 125 | ) 126 | 127 | 128 | main = 129 | Browser.element 130 | { init = init 131 | , view = view 132 | , update = update 133 | , subscriptions = subscriptions 134 | } 135 | 136 | 137 | init : () -> ( Model, Cmd Msg ) 138 | init () = 139 | ( { data = [] 140 | , frame = 0 141 | } 142 | , Random.generate Generated dataGenerator 143 | ) 144 | 145 | 146 | update : Msg -> Model -> ( Model, Cmd Msg ) 147 | update msg model = 148 | case msg of 149 | Generated data -> 150 | ( { model | data = data }, Cmd.none ) 151 | 152 | Tick i -> 153 | ( { model | frame = model.frame + i }, Cmd.none ) 154 | 155 | 156 | subscriptions : Model -> Sub Msg 157 | subscriptions _ = 158 | Browser.Events.onAnimationFrameDelta (round >> Tick) 159 | -------------------------------------------------------------------------------- /examples/CornerRadius.elm: -------------------------------------------------------------------------------- 1 | module CornerRadius exposing (main) 2 | 3 | {-| A demonstration of cornerRadius for arcs 4 | 5 | @category Reference 6 | 7 | -} 8 | 9 | import Array exposing (Array) 10 | import Color exposing (Color) 11 | import Path exposing (Path) 12 | import Shape exposing (Arc, defaultPieConfig) 13 | import TypedSvg exposing (g, svg) 14 | import TypedSvg.Attributes exposing (fill, stroke, transform, viewBox) 15 | import TypedSvg.Core exposing (Svg) 16 | import TypedSvg.Types exposing (Paint(..), Transform(..)) 17 | 18 | 19 | w : Float 20 | w = 21 | 990 22 | 23 | 24 | h : Float 25 | h = 26 | 504 27 | 28 | 29 | cornerRadius : Float 30 | cornerRadius = 31 | 12 32 | 33 | 34 | rgba255 : Int -> Int -> Int -> Float -> Color 35 | rgba255 r g b a = 36 | Color.fromRgba { red = toFloat r / 255, green = toFloat g / 255, blue = toFloat b / 255, alpha = a } 37 | 38 | 39 | colors : Array Color 40 | colors = 41 | Array.fromList 42 | [ rgba255 31 119 180 0.5 43 | , rgba255 255 127 14 0.5 44 | , rgba255 44 159 44 0.5 45 | , rgba255 214 39 40 0.5 46 | , rgba255 148 103 189 0.5 47 | , rgba255 140 86 75 0.5 48 | , rgba255 227 119 194 0.5 49 | , rgba255 128 128 128 0.5 50 | , rgba255 188 189 34 0.5 51 | , rgba255 23 190 207 0.5 52 | ] 53 | 54 | 55 | mainRadius : Float 56 | mainRadius = 57 | min (w / 2) h / 2 - 10 58 | 59 | 60 | circle : Path 61 | circle = 62 | Shape.arc 63 | { innerRadius = 0 64 | , outerRadius = cornerRadius 65 | , cornerRadius = 0 66 | , startAngle = 0 67 | , endAngle = 2 * pi 68 | , padAngle = 0 69 | , padRadius = 0 70 | } 71 | 72 | 73 | corner : Float -> Float -> Float -> Svg msg 74 | corner angle radius sign = 75 | Path.element 76 | circle 77 | [ transform 78 | [ Translate 79 | (sign * cornerRadius * cos angle + sqrt (radius ^ 2 - cornerRadius ^ 2) * sin angle) 80 | (sign * cornerRadius * sin angle - sqrt (radius ^ 2 - cornerRadius ^ 2) * cos angle) 81 | ] 82 | , stroke <| Paint Color.black 83 | , fill PaintNone 84 | ] 85 | 86 | 87 | circular : List Arc -> Svg msg 88 | circular arcs = 89 | let 90 | makeSlice index datum = 91 | Path.element (Shape.arc datum) 92 | [ fill <| Paint <| Maybe.withDefault Color.black <| Array.get index colors 93 | , stroke <| Paint Color.white 94 | ] 95 | 96 | makeCorners : { a | startAngle : Float, endAngle : Float, outerRadius : Float } -> List (Svg msg) 97 | makeCorners { startAngle, endAngle, outerRadius } = 98 | [ corner startAngle (outerRadius - cornerRadius) 1 99 | , corner endAngle (outerRadius - cornerRadius) -1 100 | ] 101 | in 102 | g [ transform [ Translate mainRadius mainRadius ] ] 103 | [ g [] <| List.indexedMap makeSlice arcs 104 | , g [] <| List.concatMap makeCorners arcs 105 | ] 106 | 107 | 108 | annular : List Arc -> Svg msg 109 | annular arcs = 110 | let 111 | makeSlice index datum = 112 | Path.element (Shape.arc { datum | innerRadius = mainRadius - 60 }) 113 | [ fill <| Paint <| Maybe.withDefault Color.black <| Array.get index colors 114 | , stroke <| Paint Color.white 115 | ] 116 | 117 | makeCorners { startAngle, endAngle, outerRadius } = 118 | [ corner startAngle (outerRadius - cornerRadius) 1 119 | , corner endAngle (outerRadius - cornerRadius) -1 120 | , corner endAngle (mainRadius - 60 + cornerRadius) -1 121 | , corner startAngle (mainRadius - 60 + cornerRadius) 1 122 | ] 123 | in 124 | g [ transform [ Translate (3 * mainRadius + 20) mainRadius ] ] 125 | [ g [] <| List.indexedMap makeSlice arcs 126 | , g [] <| List.concatMap makeCorners arcs 127 | ] 128 | 129 | 130 | view : List Float -> Svg msg 131 | view model = 132 | let 133 | pieData = 134 | model |> Shape.pie { defaultPieConfig | outerRadius = mainRadius, cornerRadius = cornerRadius } 135 | in 136 | svg [ viewBox 0 0 w h ] 137 | [ circular pieData 138 | , annular pieData 139 | ] 140 | 141 | 142 | data : List Float 143 | data = 144 | [ 1, 1, 2, 3, 5, 8, 13 ] 145 | 146 | 147 | main : Svg msg 148 | main = 149 | view data 150 | -------------------------------------------------------------------------------- /examples/Curves.elm: -------------------------------------------------------------------------------- 1 | module Curves exposing (main) 2 | 3 | {-| This example demonstrates the effect of curve type on the shape connecting the same set of points. 4 | 5 | @screenshot linear 6 | @screenshot basis 7 | @screenshot basisclosed 8 | @screenshot basisopen 9 | @screenshot bundle 10 | @screenshot cardinal 11 | @screenshot cardinalclosed 12 | @screenshot cardinalopen 13 | @screenshot catmullrom 14 | @screenshot catmullromclosed 15 | @screenshot catmullromopen 16 | @screenshot monotoneinx 17 | @screenshot step 18 | @screenshot natural 19 | @screenshot bumpx 20 | @screenshot bumpy 21 | 22 | @category Reference 23 | 24 | -} 25 | 26 | import Color exposing (Color) 27 | import Example 28 | import Path exposing (Path) 29 | import Scale exposing (ContinuousScale) 30 | import Scale.Color 31 | import Shape 32 | import SubPath exposing (SubPath) 33 | import TypedSvg exposing (g, line, rect, svg, text_) 34 | import TypedSvg.Attributes as Explicit exposing (fill, fontFamily, stroke, transform, viewBox) 35 | import TypedSvg.Attributes.InPx exposing (height, strokeWidth, width, x, x1, x2, y, y1, y2) 36 | import TypedSvg.Core exposing (Svg, text) 37 | import TypedSvg.Types exposing (Paint(..), Transform(..), percent) 38 | 39 | 40 | w : Float 41 | w = 42 | 990 43 | 44 | 45 | h : Float 46 | h = 47 | 450 48 | 49 | 50 | padding : Float 51 | padding = 52 | 50 53 | 54 | 55 | points : List ( Float, Float ) 56 | points = 57 | [ ( 0.1, 0.1 ) 58 | , ( 0.2, 0.6 ) 59 | , ( 0.35, 0.3 ) 60 | , ( 0.45, 0.3 ) 61 | , ( 0.6, 0.2 ) 62 | , ( 0.9, 0.8 ) 63 | , ( 1.2, 0.6 ) 64 | , ( 1.5, 0.9 ) 65 | , ( 1.7, 0.2 ) 66 | , ( 1.9, 0.1 ) 67 | ] 68 | 69 | 70 | xScale : ContinuousScale Float 71 | xScale = 72 | Scale.linear ( padding, w - padding ) ( 0, 2 ) 73 | 74 | 75 | yScale : ContinuousScale Float 76 | yScale = 77 | Scale.linear ( h - padding, padding ) ( 0, 1 ) 78 | 79 | 80 | preparedPoints : List ( Float, Float ) 81 | preparedPoints = 82 | List.map (\( x, y ) -> ( Scale.convert xScale x, Scale.convert yScale y )) points 83 | 84 | 85 | xGridLine : Int -> Float -> Svg msg 86 | xGridLine index tick = 87 | line 88 | [ y1 0 89 | , Explicit.y2 (percent 100) 90 | , x1 (Scale.convert xScale tick) 91 | , x2 (Scale.convert xScale tick) 92 | , stroke <| Paint Color.white 93 | , strokeWidth (Basics.max (toFloat (modBy 2 index)) 0.5) 94 | ] 95 | [] 96 | 97 | 98 | yGridLine : Int -> Float -> Svg msg 99 | yGridLine index tick = 100 | line 101 | [ x1 0 102 | , Explicit.x2 (percent 100) 103 | , y1 (Scale.convert yScale tick) 104 | , y2 (Scale.convert yScale tick) 105 | , stroke <| Paint Color.white 106 | , strokeWidth (Basics.max (toFloat (modBy 2 index)) 0.5) 107 | ] 108 | [] 109 | 110 | 111 | type alias Curve = 112 | List ( Float, Float ) -> SubPath 113 | 114 | 115 | drawCurve : ( String, Path, Color ) -> Svg msg 116 | drawCurve ( _, path, color ) = 117 | Path.element path [ stroke (Paint color), fill PaintNone, strokeWidth 2 ] 118 | 119 | 120 | drawLegend : Int -> ( String, Path, Color ) -> Svg msg 121 | drawLegend index ( name, _, color ) = 122 | text_ [ fill (Paint color), fontFamily [ "monospace" ], x padding, y (toFloat index * 20 + padding) ] [ text name ] 123 | 124 | 125 | view : List ( String, Path, Color ) -> Svg msg 126 | view model = 127 | svg [ viewBox 0 0 w h ] 128 | [ rect [ width w, height h, fill <| Paint <| Color.rgb255 223 223 223 ] [] 129 | , g [] <| List.indexedMap yGridLine <| Scale.ticks yScale 10 130 | , g [] <| List.indexedMap xGridLine <| Scale.ticks xScale 20 131 | , g [] <| 132 | List.map drawCurve model 133 | , g [] <| List.map (\( dx, dy ) -> Path.element circle [ fill (Paint Color.white), stroke (Paint Color.black), transform [ Translate dx dy ] ]) preparedPoints 134 | , g [] <| List.indexedMap drawLegend <| model 135 | ] 136 | 137 | 138 | circle : Path 139 | circle = 140 | Shape.arc 141 | { innerRadius = 0 142 | , outerRadius = 3 143 | , cornerRadius = 0 144 | , startAngle = 0 145 | , endAngle = 2 * pi 146 | , padAngle = 0 147 | , padRadius = 0 148 | } 149 | 150 | 151 | basic : String -> Curve -> List ( String, Path, Color ) 152 | basic prefix curveFn = 153 | [ ( prefix, [ curveFn preparedPoints ], Color.black ) ] 154 | 155 | 156 | parametrized : String -> (Float -> Curve) -> List ( String, Path, Color ) 157 | parametrized prefix curveFn = 158 | let 159 | scale = 160 | Scale.sequential Scale.Color.magmaInterpolator ( 0, 1 ) 161 | 162 | stops = 163 | [ 0, 0.25, 0.5, 0.75, 1 ] 164 | in 165 | stops 166 | |> List.map (\s -> ( prefix ++ " " ++ String.fromFloat s, [ curveFn s preparedPoints ], Scale.convert scale s )) 167 | 168 | 169 | main : Example.Program (List ( String, Path, Color )) 170 | main = 171 | [ ( "Linear", basic "linearCurve" Shape.linearCurve ) 172 | , ( "Basis", basic "basisCurve" Shape.basisCurve ) 173 | , ( "BasisClosed", basic "basisCurveClosed" Shape.basisCurveClosed ) 174 | , ( "BasisOpen", basic "basisCurveOpen" Shape.basisCurveOpen ) 175 | , ( "Bundle", parametrized "bundleCurve" Shape.bundleCurve ) 176 | , ( "Cardinal", parametrized "cardinalCurve" Shape.cardinalCurve ) 177 | , ( "CardinalClosed", parametrized "cardinalCurveClosed" Shape.cardinalCurveClosed ) 178 | , ( "CardinalOpen", parametrized "cardinalCurveOpen" Shape.cardinalCurveOpen ) 179 | , ( "CatmullRom", parametrized "catmullRomCurve" Shape.catmullRomCurve ) 180 | , ( "CatmullRomClosed", parametrized "catmullRomCurveClosed" Shape.catmullRomCurveClosed ) 181 | , ( "CatmullRomOpen", parametrized "catmullRomCurveOpen" Shape.catmullRomCurveOpen ) 182 | , ( "MonotoneInX", basic "monotoneInXCurve" Shape.monotoneInXCurve ) 183 | , ( "Step", parametrized "stepCurve" Shape.stepCurve ) 184 | , ( "Natural", basic "naturalCurve" Shape.naturalCurve ) 185 | , ( "BumpX", basic "bumpXCurve" Shape.bumpXCurve ) 186 | , ( "BumpY", basic "bumpYCurve" Shape.bumpYCurve ) 187 | ] 188 | |> Example.tabbed "Curve type:" 189 | |> Example.application view 190 | -------------------------------------------------------------------------------- /examples/CustomPieChart.elm: -------------------------------------------------------------------------------- 1 | module CustomPieChart exposing (ChartConfig, main) 2 | 3 | {-| An interactive example showing the effect of various options on pie generators. 4 | 5 | @category Reference 6 | 7 | -} 8 | 9 | import Array exposing (Array) 10 | import Color exposing (Color) 11 | import Example 12 | import Path 13 | import Shape exposing (defaultPieConfig) 14 | import TypedSvg exposing (g, svg, text_) 15 | import TypedSvg.Attributes exposing (dy, fill, stroke, textAnchor, transform) 16 | import TypedSvg.Attributes.InPx exposing (height, width) 17 | import TypedSvg.Core exposing (Svg, text) 18 | import TypedSvg.Types exposing (AnchorAlignment(..), Paint(..), Transform(..), em) 19 | 20 | 21 | w : Float 22 | w = 23 | 990 24 | 25 | 26 | h : Float 27 | h = 28 | 504 29 | 30 | 31 | colors : Array Color 32 | colors = 33 | Array.fromList 34 | [ Color.rgb255 152 171 198 35 | , Color.rgb255 138 137 166 36 | , Color.rgb255 123 104 136 37 | , Color.rgb255 107 72 107 38 | , Color.rgb255 159 92 85 39 | , Color.rgb255 208 116 60 40 | , Color.rgb255 255 96 0 41 | ] 42 | 43 | 44 | radius : Float 45 | radius = 46 | min w h / 2 47 | 48 | 49 | type alias ChartConfig = 50 | { outerRadius : Float 51 | , innerRadius : Float 52 | , padAngle : Float 53 | , cornerRadius : Float 54 | , labelPosition : Float 55 | } 56 | 57 | 58 | view : ChartConfig -> Svg msg 59 | view config = 60 | let 61 | pieData = 62 | data 63 | |> List.map Tuple.second 64 | |> Shape.pie 65 | { defaultPieConfig 66 | | innerRadius = config.innerRadius 67 | , outerRadius = config.outerRadius 68 | , padAngle = config.padAngle 69 | , cornerRadius = config.cornerRadius 70 | , sortingFn = \_ _ -> EQ 71 | } 72 | 73 | makeSlice index datum = 74 | Path.element (Shape.arc datum) [ fill <| Paint <| Maybe.withDefault Color.black <| Array.get index colors, stroke <| Paint Color.white ] 75 | 76 | makeLabel slice ( label, _ ) = 77 | let 78 | ( x, y ) = 79 | Shape.centroid { slice | innerRadius = config.labelPosition, outerRadius = config.labelPosition } 80 | in 81 | text_ 82 | [ transform [ Translate x y ] 83 | , dy (em 0.35) 84 | , textAnchor AnchorMiddle 85 | ] 86 | [ text label ] 87 | in 88 | svg [ width (radius * 2), height (radius * 2) ] 89 | [ g [ transform [ Translate radius radius ] ] 90 | [ g [] <| List.indexedMap makeSlice pieData 91 | , g [] <| List.map2 makeLabel pieData data 92 | ] 93 | ] 94 | 95 | 96 | data : List ( String, Float ) 97 | data = 98 | [ ( "<5", 2704659 ) 99 | , ( "5-13", 4499890 ) 100 | , ( "14-17", 2159981 ) 101 | , ( "18-24", 3853788 ) 102 | , ( "25-44", 14106543 ) 103 | , ( "45-64", 8819342 ) 104 | , ( "≥65", 612463 ) 105 | ] 106 | 107 | 108 | main : Example.Program ChartConfig 109 | main = 110 | Example.configuration 111 | { outerRadius = 210 112 | , innerRadius = 200 113 | , padAngle = 0.02 114 | , cornerRadius = 20 115 | , labelPosition = 230 116 | } 117 | [ Example.floatSlider "Outer Radius" { min = 0, max = radius } .outerRadius (\v m -> { m | outerRadius = v }) 118 | , Example.floatSlider "Inner Radius" { min = 0, max = radius } .innerRadius (\v m -> { m | innerRadius = v }) 119 | , Example.floatSlider "Pad Angle" { min = 0, max = 0.8 } .padAngle (\v m -> { m | padAngle = v }) 120 | , Example.floatSlider "Corner Radius" { min = 0, max = 20 } .cornerRadius (\v m -> { m | cornerRadius = v }) 121 | , Example.floatSlider "Label Position" { min = 0, max = radius } .labelPosition (\v m -> { m | labelPosition = v }) 122 | ] 123 | |> Example.withTitle "Pie Configuration" 124 | |> Example.withCustomCss ".example-layout-horizontal { justify-content: space-around; }" 125 | |> Example.application view 126 | -------------------------------------------------------------------------------- /examples/HistogramChart.elm: -------------------------------------------------------------------------------- 1 | module HistogramChart exposing (main) 2 | 3 | {-| Renders a histogram of a randomly generated data set. 4 | 5 | This is very similar to the [Bar Chart](../BarChart), but computes the bars from data. 6 | Note that the x scale here is linear rather than a band scale, since x axis is not categorical. 7 | 8 | @category Basics 9 | 10 | -} 11 | 12 | import Axis 13 | import Color 14 | import Histogram exposing (Bin) 15 | import Random exposing (Generator, Seed) 16 | import Scale exposing (ContinuousScale) 17 | import TypedSvg exposing (g, rect, svg) 18 | import TypedSvg.Attributes exposing (class, fill, transform, viewBox) 19 | import TypedSvg.Attributes.InPx exposing (height, width, x, y) 20 | import TypedSvg.Core exposing (Svg) 21 | import TypedSvg.Types exposing (Paint(..), Transform(..)) 22 | 23 | 24 | {-| We use addition here to approximate normal distribution. 25 | -} 26 | generator : Generator (List Float) 27 | generator = 28 | Random.list 500 <| Random.map2 (+) (Random.float 0 10) (Random.float 0 10) 29 | 30 | 31 | seed : Seed 32 | seed = 33 | -- chosen by fair dice roll 34 | Random.initialSeed 227852860 35 | 36 | 37 | data : List Float 38 | data = 39 | Tuple.first <| Random.step generator seed 40 | 41 | 42 | histogram : List Float -> List (Bin Float Float) 43 | histogram model = 44 | Histogram.float 45 | |> Histogram.withDomain ( 0, 20 ) 46 | |> Histogram.compute model 47 | 48 | 49 | w : Float 50 | w = 51 | 900 52 | 53 | 54 | h : Float 55 | h = 56 | 450 57 | 58 | 59 | padding : Float 60 | padding = 61 | 30 62 | 63 | 64 | xScale : ContinuousScale Float 65 | xScale = 66 | Scale.linear ( 0, w - 2 * padding ) ( 0, 20 ) 67 | 68 | 69 | yScaleFromBins : List (Bin Float Float) -> ContinuousScale Float 70 | yScaleFromBins bins = 71 | List.map .length bins 72 | |> List.maximum 73 | |> Maybe.withDefault 0 74 | |> toFloat 75 | |> Tuple.pair 0 76 | |> Scale.linear ( h - 2 * padding, 0 ) 77 | 78 | 79 | xAxis : Svg msg 80 | xAxis = 81 | Axis.bottom [] xScale 82 | 83 | 84 | yAxis : List (Bin Float Float) -> Svg msg 85 | yAxis bins = 86 | Axis.left [ Axis.tickCount 5 ] (yScaleFromBins bins) 87 | 88 | 89 | column : ContinuousScale Float -> Bin Float Float -> Svg msg 90 | column yScale { length, x0, x1 } = 91 | rect 92 | [ x <| Scale.convert xScale x0 93 | , y <| Scale.convert yScale (toFloat length) 94 | , width <| Scale.convert xScale x1 - Scale.convert xScale x0 95 | , height <| h - Scale.convert yScale (toFloat length) - 2 * padding 96 | , fill <| Paint <| Color.rgb255 46 118 149 97 | ] 98 | [] 99 | 100 | 101 | view : List Float -> Svg msg 102 | view model = 103 | let 104 | bins = 105 | histogram model 106 | in 107 | svg [ viewBox 0 0 w h ] 108 | [ g [ transform [ Translate (padding - 1) (h - padding) ] ] 109 | [ xAxis ] 110 | , g [ transform [ Translate (padding - 1) padding ] ] 111 | [ yAxis bins ] 112 | , g [ transform [ Translate padding padding ], class [ "series" ] ] <| 113 | List.map (column (yScaleFromBins bins)) bins 114 | ] 115 | 116 | 117 | main : Svg msg 118 | main = 119 | view data 120 | -------------------------------------------------------------------------------- /examples/LayeredTree.elm: -------------------------------------------------------------------------------- 1 | module LayeredTree exposing (main) 2 | 3 | {-| Shows the behavior of the `Hierarchy.layered` attribute: when present, each level of depth of the tree is rendered at the same y offset. When this is absent, the tree is free to use up space much more efficiently rendereing a potentially significantly smaller diagram. 4 | 5 | @category Reference 6 | 7 | -} 8 | 9 | import Color 10 | import Hierarchy 11 | import Path 12 | import Shape 13 | import Tree exposing (Tree) 14 | import TypedSvg exposing (g, rect, svg) 15 | import TypedSvg.Attributes exposing (dy, fill, pointerEvents, stroke, style, transform, viewBox) 16 | import TypedSvg.Attributes.InPx exposing (fontSize, height, rx, width, x, y) 17 | import TypedSvg.Core exposing (Svg) 18 | import TypedSvg.Types exposing (Paint(..), Transform(..), em) 19 | 20 | 21 | w : Float 22 | w = 23 | 990 24 | 25 | 26 | h : Float 27 | h = 28 | 504 29 | 30 | 31 | padding : Float 32 | padding = 33 | 30 34 | 35 | 36 | layout : Bool -> Tree { x : Float, y : Float, width : Float, height : Float, node : { height : Float, width : Float } } 37 | layout layered = 38 | Hierarchy.tidy 39 | [ Hierarchy.nodeSize 40 | (\{ width, height } -> 41 | ( width, height ) 42 | ) 43 | , Hierarchy.parentChildMargin 20 44 | , Hierarchy.peerMargin 20 45 | , if layered then 46 | Hierarchy.layered 47 | 48 | else 49 | Hierarchy.none 50 | ] 51 | tree 52 | 53 | 54 | main = 55 | svg [ viewBox 0 0 w h ] 56 | [ g [] 57 | [ TypedSvg.text_ [ x padding, y padding, dy (em 1), fontSize 18, TypedSvg.Attributes.fontFamily [ "sans-serif" ] ] [ TypedSvg.Core.text "Non-layered" ] 58 | , view (layout False) 59 | ] 60 | , g [ transform [ Translate (w / 2) 0 ] ] 61 | [ TypedSvg.text_ [ x padding, y padding, dy (em 1), fontSize 18, TypedSvg.Attributes.fontFamily [ "sans-serif" ] ] [ TypedSvg.Core.text "Layered" ] 62 | , view (layout True) 63 | ] 64 | ] 65 | 66 | 67 | view : Tree { x : Float, y : Float, width : Float, height : Float, node : { height : Float, width : Float } } -> Svg msg 68 | view layedOut = 69 | g [ transform [ Translate (padding + w / 4) padding ] ] 70 | [ layedOut 71 | |> Tree.links 72 | |> List.map 73 | (\( from, to ) -> 74 | Shape.bumpYCurve 75 | [ ( from.x + from.width / 2, from.y + from.height ) 76 | , ( to.x + to.width / 2, to.y ) 77 | ] 78 | ) 79 | |> (\p -> 80 | Path.element p 81 | [ fill PaintNone 82 | , stroke (Paint (Color.rgb 0.3 0.3 0.3)) 83 | , style "vector-effect: non-scaling-stroke" 84 | , pointerEvents "none" 85 | ] 86 | ) 87 | , layedOut 88 | |> Tree.toList 89 | |> List.map 90 | (\item -> 91 | rect [ rx 5, width item.width, height item.height, x item.x, y item.y, fill (Paint (Color.rgba 0.1 0.1 0.8 0.3)), stroke (Paint (Color.rgba 0.1 0.1 0.8 1)) ] [] 92 | ) 93 | |> g [] 94 | ] 95 | 96 | 97 | tree : Tree { width : Float, height : Float } 98 | tree = 99 | Tree.tree { width = 100, height = 50 } 100 | [ Tree.tree { width = 60, height = 140 } 101 | [ Tree.singleton { width = 60, height = 160 } 102 | , Tree.tree { width = 100, height = 100 } 103 | [ Tree.singleton { width = 40, height = 40 } 104 | , Tree.singleton { width = 40, height = 40 } 105 | ] 106 | , Tree.singleton { width = 120, height = 160 } 107 | ] 108 | , Tree.tree { width = 20, height = 20 } 109 | [ Tree.singleton { width = 100, height = 100 } 110 | ] 111 | ] 112 | -------------------------------------------------------------------------------- /examples/LineChart.elm: -------------------------------------------------------------------------------- 1 | module LineChart exposing (main) 2 | 3 | {-| This module shows how to build a simple line and area chart using some of 4 | the primitives provided in this library. 5 | 6 | Specifically we define scales, use these to create shapes, then transform those into SVG. 7 | 8 | @category Basics 9 | 10 | -} 11 | 12 | import Axis 13 | import Color 14 | import Path exposing (Path) 15 | import Scale exposing (ContinuousScale) 16 | import Shape 17 | import Time 18 | import TypedSvg exposing (g, svg) 19 | import TypedSvg.Attributes exposing (class, fill, stroke, transform, viewBox) 20 | import TypedSvg.Attributes.InPx exposing (strokeWidth) 21 | import TypedSvg.Core exposing (Svg) 22 | import TypedSvg.Types exposing (Paint(..), Transform(..)) 23 | 24 | 25 | w : Float 26 | w = 27 | 900 28 | 29 | 30 | h : Float 31 | h = 32 | 450 33 | 34 | 35 | padding : Float 36 | padding = 37 | 30 38 | 39 | 40 | xScale : ContinuousScale Time.Posix 41 | xScale = 42 | Scale.time Time.utc ( 0, w - 2 * padding ) ( Time.millisToPosix 1448928000000, Time.millisToPosix 1456790400000 ) 43 | 44 | 45 | yScale : ContinuousScale Float 46 | yScale = 47 | Scale.linear ( h - 2 * padding, 0 ) ( 0, 5 ) 48 | 49 | 50 | xAxis : List ( Time.Posix, Float ) -> Svg msg 51 | xAxis model = 52 | Axis.bottom [ Axis.tickCount (List.length model) ] xScale 53 | 54 | 55 | yAxis : Svg msg 56 | yAxis = 57 | Axis.left [ Axis.tickCount 5 ] yScale 58 | 59 | 60 | transformToLineData : ( Time.Posix, Float ) -> Maybe ( Float, Float ) 61 | transformToLineData ( x, y ) = 62 | Just ( Scale.convert xScale x, Scale.convert yScale y ) 63 | 64 | 65 | tranfromToAreaData : ( Time.Posix, Float ) -> Maybe ( ( Float, Float ), ( Float, Float ) ) 66 | tranfromToAreaData ( x, y ) = 67 | Just 68 | ( ( Scale.convert xScale x, Tuple.first (Scale.rangeExtent yScale) ) 69 | , ( Scale.convert xScale x, Scale.convert yScale y ) 70 | ) 71 | 72 | 73 | line : List ( Time.Posix, Float ) -> Path 74 | line model = 75 | List.map transformToLineData model 76 | |> Shape.line Shape.monotoneInXCurve 77 | 78 | 79 | area : List ( Time.Posix, Float ) -> Path 80 | area model = 81 | List.map tranfromToAreaData model 82 | |> Shape.area Shape.monotoneInXCurve 83 | 84 | 85 | view : List ( Time.Posix, Float ) -> Svg msg 86 | view model = 87 | svg [ viewBox 0 0 w h ] 88 | [ g [ transform [ Translate (padding - 1) (h - padding) ] ] 89 | [ xAxis model ] 90 | , g [ transform [ Translate (padding - 1) padding ] ] 91 | [ yAxis ] 92 | , g [ transform [ Translate padding padding ], class [ "series" ] ] 93 | [ Path.element (area model) [ strokeWidth 3, fill <| Paint <| Color.rgba 1 0 0 0.54 ] 94 | , Path.element (line model) [ stroke <| Paint <| Color.rgb 1 0 0, strokeWidth 3, fill PaintNone ] 95 | ] 96 | ] 97 | 98 | 99 | 100 | -- From here onwards this is simply example boilerplate. 101 | -- In a real app you would load the data from a server and parse it, perhaps in 102 | -- a separate module. 103 | 104 | 105 | main : Svg msg 106 | main = 107 | view timeSeries 108 | 109 | 110 | timeSeries : List ( Time.Posix, Float ) 111 | timeSeries = 112 | [ ( Time.millisToPosix 1448928000000, 2.5 ) 113 | , ( Time.millisToPosix 1451606400000, 2 ) 114 | , ( Time.millisToPosix 1452211200000, 3.5 ) 115 | , ( Time.millisToPosix 1452816000000, 2 ) 116 | , ( Time.millisToPosix 1453420800000, 3 ) 117 | , ( Time.millisToPosix 1454284800000, 1 ) 118 | , ( Time.millisToPosix 1456790400000, 1.2 ) 119 | ] 120 | -------------------------------------------------------------------------------- /examples/Mandelbrot.elm: -------------------------------------------------------------------------------- 1 | module Mandelbrot exposing (main) 2 | 3 | {-| Try scrolling, double clicking, or pinch-and-zooming! 4 | 5 | This example demonstrates how one can use the Zoom module for other rendering technologies, in this case for a WebGL scene. 6 | 7 | @category Advanced 8 | 9 | -} 10 | 11 | import Browser 12 | import Html exposing (Html) 13 | import Html.Attributes exposing (height, style, width) 14 | import Math.Matrix4 as Mat4 exposing (Mat4) 15 | import Math.Vector2 exposing (Vec2, vec2) 16 | import WebGL exposing (Mesh, Shader) 17 | import Zoom exposing (Zoom) 18 | 19 | 20 | w : Float 21 | w = 22 | 990 23 | 24 | 25 | h : Float 26 | h = 27 | 504 28 | 29 | 30 | main : Program () Zoom Zoom.OnZoom 31 | main = 32 | Browser.element 33 | { init = 34 | \() -> 35 | ( Zoom.init { width = w, height = h } 36 | |> Zoom.translateExtent ( ( 0, 0 ), ( w, h ) ) 37 | |> Zoom.scaleExtent 1 10000 38 | , Cmd.none 39 | ) 40 | , view = view 41 | , update = \msg model -> ( Zoom.update msg model, Cmd.none ) 42 | , subscriptions = \model -> Zoom.subscriptions model identity 43 | } 44 | 45 | 46 | view : Zoom -> Html Zoom.OnZoom 47 | view zoom = 48 | WebGL.toHtml 49 | ([ width (round (w * 2)) 50 | , height (round (h * 2)) 51 | , style "display" "block" 52 | , style "width" (String.fromFloat w ++ "px") 53 | , style "height" (String.fromFloat h ++ "px") 54 | ] 55 | ++ Zoom.events zoom identity 56 | ) 57 | [ WebGL.entity 58 | vertexShader 59 | fragmentShader 60 | mesh 61 | { zoom = zoomToMat4 zoom } 62 | ] 63 | 64 | 65 | zoomToMat4 : Zoom -> Mat4 66 | zoomToMat4 zoom = 67 | let 68 | z = 69 | Zoom.asRecord zoom 70 | in 71 | Mat4.makeTranslate3 ((z.translate.x + (w / 2) * z.scale) / (w / 2) - 1) (-(z.translate.y + (h / 2) * z.scale) / (h / 2) + 1) 0 72 | |> Mat4.scale3 z.scale z.scale 1 73 | 74 | 75 | type alias Vertex = 76 | { position : Vec2 77 | } 78 | 79 | 80 | mesh : Mesh Vertex 81 | mesh = 82 | WebGL.triangles 83 | [ ( Vertex (vec2 0 0) 84 | , Vertex (vec2 1 0) 85 | , Vertex (vec2 0 1) 86 | ) 87 | , ( Vertex (vec2 0 1) 88 | , Vertex (vec2 1 0) 89 | , Vertex (vec2 1 1) 90 | ) 91 | ] 92 | 93 | 94 | type alias Uniforms = 95 | { zoom : Mat4 } 96 | 97 | 98 | vertexShader : Shader Vertex Uniforms { v_pos : Vec2 } 99 | vertexShader = 100 | [glsl| 101 | precision highp float; 102 | attribute vec2 position; 103 | uniform mat4 zoom; 104 | varying vec2 v_pos; 105 | 106 | void main() { 107 | v_pos = vec2(position.x * 2.0 - 1.75 , position.y * 2.0 - 1.0); 108 | gl_Position = zoom * vec4(position * 2.0 - 1.0, 0, 1); 109 | } 110 | |] 111 | 112 | 113 | fragmentShader : Shader {} Uniforms { v_pos : Vec2 } 114 | fragmentShader = 115 | [glsl| 116 | precision highp float; 117 | varying vec2 v_pos; 118 | const float PI = 3.14159265359; 119 | 120 | vec4 sinebow(float iterations) { 121 | float t = 0.5 - (iterations / 100.0); 122 | return vec4(pow(sin(PI * t), 2.0), pow(sin(PI * (t + 1.0/ 3.0)), 2.0), pow(sin(PI * (t + 2.0/ 3.0)), 2.0), 1.0); 123 | } 124 | 125 | void main() { 126 | vec2 q = v_pos; 127 | vec4 color = vec4(0,0,0,1); 128 | vec2 z = vec2(0.0, 0.0); 129 | float iterations = 0.0; 130 | for(int i = 0; i < 100; i++) { 131 | iterations++; 132 | z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + q; 133 | if (length(z) > 10.0) break; 134 | } 135 | if (iterations < 100.0) { 136 | float log_zn = log(z.x*z.x + z.y*z.y) / 2.0; 137 | float nu = log(log_zn / log(2.0)) / log(2.0); 138 | iterations = iterations + 1.0 - nu; 139 | vec4 color1 = sinebow(floor(iterations)); 140 | vec4 color2 = sinebow(floor(iterations) + 1.0); 141 | color = mix(color1, color2, mod(iterations, 1.0)); 142 | } else { 143 | color = vec4(0,0,0,1); 144 | } 145 | gl_FragColor = color; 146 | } 147 | |] 148 | -------------------------------------------------------------------------------- /examples/PadAngle.elm: -------------------------------------------------------------------------------- 1 | module PadAngle exposing (main) 2 | 3 | {-| A demonstration of padAngle for arcs 4 | @category Reference 5 | -} 6 | 7 | import Array exposing (Array) 8 | import Color exposing (Color) 9 | import Path 10 | import Shape exposing (Arc, defaultPieConfig) 11 | import TypedSvg exposing (g, svg) 12 | import TypedSvg.Attributes exposing (fill, stroke, transform, viewBox) 13 | import TypedSvg.Core exposing (Svg) 14 | import TypedSvg.Types exposing (Paint(..), Transform(..)) 15 | 16 | 17 | w : Float 18 | w = 19 | 990 20 | 21 | 22 | h : Float 23 | h = 24 | 504 25 | 26 | 27 | rgba255 : Int -> Int -> Int -> Float -> Color 28 | rgba255 r g b a = 29 | Color.fromRgba { red = toFloat r / 255, green = toFloat g / 255, blue = toFloat b / 255, alpha = a } 30 | 31 | 32 | colors : Array Color 33 | colors = 34 | Array.fromList 35 | [ rgba255 31 119 180 0.5 36 | , rgba255 255 127 14 0.5 37 | , rgba255 44 159 44 0.5 38 | , rgba255 214 39 40 0.5 39 | , rgba255 148 103 189 0.5 40 | , rgba255 140 86 75 0.5 41 | , rgba255 227 119 194 0.5 42 | , rgba255 128 128 128 0.5 43 | , rgba255 188 189 34 0.5 44 | , rgba255 23 190 207 0.5 45 | ] 46 | 47 | 48 | radius : Float 49 | radius = 50 | min (w / 2) h / 2 - 10 51 | 52 | 53 | circular : List Arc -> Svg msg 54 | circular arcs = 55 | let 56 | makeSlice index datum = 57 | Path.element (Shape.arc datum) 58 | [ fill <| Paint <| Maybe.withDefault Color.black <| Array.get index colors 59 | , stroke <| Paint Color.black 60 | ] 61 | in 62 | g [ transform [ Translate radius radius ] ] 63 | [ g [] <| List.indexedMap makeSlice arcs 64 | ] 65 | 66 | 67 | annular : List Arc -> Svg msg 68 | annular arcs = 69 | let 70 | makeSlice index datum = 71 | Path.element (Shape.arc { datum | innerRadius = radius - 60 }) 72 | [ fill <| Paint <| Maybe.withDefault Color.black <| Array.get index colors 73 | , stroke <| Paint Color.black 74 | ] 75 | in 76 | g [ transform [ Translate (3 * radius + 20) radius ] ] 77 | [ g [] <| List.indexedMap makeSlice arcs 78 | ] 79 | 80 | 81 | view : List Float -> Svg msg 82 | view model = 83 | let 84 | pieData = 85 | model |> Shape.pie { defaultPieConfig | outerRadius = radius, padAngle = 0.03 } 86 | in 87 | svg [ viewBox 0 0 w h ] 88 | [ circular pieData 89 | , annular pieData 90 | ] 91 | 92 | 93 | data : List Float 94 | data = 95 | [ 1, 1, 2, 3, 5, 8, 13 ] 96 | 97 | 98 | main : Svg msg 99 | main = 100 | view data 101 | -------------------------------------------------------------------------------- /examples/Petals.elm: -------------------------------------------------------------------------------- 1 | module Petals exposing (main) 2 | 3 | {-| Based on the arrangement of petals in a sunflower. Demonstrates the initial layout of Force.entity. 4 | 5 | @category Art 6 | 7 | -} 8 | 9 | import Color exposing (Color) 10 | import Force 11 | import Scale 12 | import Scale.Color 13 | import TypedSvg exposing (circle, svg) 14 | import TypedSvg.Attributes exposing (fill, viewBox) 15 | import TypedSvg.Attributes.InPx exposing (cx, cy, r) 16 | import TypedSvg.Core exposing (Svg) 17 | import TypedSvg.Types exposing (Paint(..)) 18 | 19 | 20 | color : Int -> Color 21 | color = 22 | Scale.convert (Scale.sequential Scale.Color.viridisInterpolator ( 0, 360 )) << toFloat 23 | 24 | 25 | makePetal : Int -> Svg msg 26 | makePetal i = 27 | let 28 | { x, y } = 29 | Force.entity i () 30 | 31 | angle = 32 | modBy 360 (floor (toFloat i * (3 - sqrt 5) * pi * 180 - sqrt (toFloat i) * 4)) 33 | in 34 | circle [ cx x, cy y, r 5, fill <| Paint <| color angle ] [] 35 | 36 | 37 | view : List Int -> Svg msg 38 | view model = 39 | svg [ viewBox -1000 -500 2000 2000 ] <| 40 | List.map makePetal model 41 | 42 | 43 | main : Svg msg 44 | main = 45 | view <| List.range 1 10000 46 | -------------------------------------------------------------------------------- /examples/PieChart.elm: -------------------------------------------------------------------------------- 1 | module PieChart exposing (main) 2 | 3 | {-| An example showing how to render a basic pie chart. 4 | 5 | @category Basics 6 | 7 | -} 8 | 9 | import Array exposing (Array) 10 | import Color exposing (Color) 11 | import Path 12 | import Shape exposing (defaultPieConfig) 13 | import TypedSvg exposing (g, svg, text_) 14 | import TypedSvg.Attributes exposing (dy, fill, stroke, textAnchor, transform, viewBox) 15 | import TypedSvg.Core exposing (Svg, text) 16 | import TypedSvg.Types exposing (AnchorAlignment(..), Paint(..), Transform(..), em) 17 | 18 | 19 | w : Float 20 | w = 21 | 990 22 | 23 | 24 | h : Float 25 | h = 26 | 504 27 | 28 | 29 | colors : Array Color 30 | colors = 31 | Array.fromList 32 | [ Color.rgb255 152 171 198 33 | , Color.rgb255 138 137 166 34 | , Color.rgb255 123 104 136 35 | , Color.rgb255 107 72 107 36 | , Color.rgb255 159 92 85 37 | , Color.rgb255 208 116 60 38 | , Color.rgb255 255 96 0 39 | ] 40 | 41 | 42 | radius : Float 43 | radius = 44 | min w h / 2 45 | 46 | 47 | pieSlice : Int -> Shape.Arc -> Svg msg 48 | pieSlice index datum = 49 | Path.element (Shape.arc datum) [ fill <| Paint <| Maybe.withDefault Color.black <| Array.get index colors, stroke <| Paint Color.white ] 50 | 51 | 52 | pieLabel : Shape.Arc -> ( String, Float ) -> Svg msg 53 | pieLabel slice ( label, _ ) = 54 | let 55 | ( x, y ) = 56 | Shape.centroid { slice | innerRadius = radius - 40, outerRadius = radius - 40 } 57 | in 58 | text_ 59 | [ transform [ Translate x y ] 60 | , dy (em 0.35) 61 | , textAnchor AnchorMiddle 62 | ] 63 | [ text label ] 64 | 65 | 66 | view : List ( String, Float ) -> Svg msg 67 | view model = 68 | let 69 | pieData = 70 | model |> List.map Tuple.second |> Shape.pie { defaultPieConfig | outerRadius = radius } 71 | in 72 | svg [ viewBox 0 0 w h ] 73 | [ g [ transform [ Translate (w / 2) (h / 2) ] ] 74 | [ g [] <| List.indexedMap pieSlice pieData 75 | , g [] <| List.map2 pieLabel pieData model 76 | ] 77 | ] 78 | 79 | 80 | data : List ( String, Float ) 81 | data = 82 | [ ( "/notifications", 2704659 ) 83 | , ( "/about", 4499890 ) 84 | , ( "/product", 2159981 ) 85 | , ( "/blog", 3853788 ) 86 | , ( "/shop", 14106543 ) 87 | , ( "/profile", 8819342 ) 88 | , ( "/", 612463 ) 89 | ] 90 | 91 | 92 | main : Svg msg 93 | main = 94 | view data 95 | -------------------------------------------------------------------------------- /examples/PolarPlot.elm: -------------------------------------------------------------------------------- 1 | module PolarPlot exposing (main) 2 | 3 | {-| A polar plot of `sin(2x)cos(2x)`. 4 | 5 | @category Advanced 6 | 7 | -} 8 | 9 | import Path 10 | import Scale exposing (ContinuousScale) 11 | import Shape 12 | import Statistics 13 | import TypedSvg exposing (circle, g, line, style, svg, text_) 14 | import TypedSvg.Attributes exposing (class, dy, textAnchor, transform, viewBox) 15 | import TypedSvg.Attributes.InPx exposing (fontSize, r, x, x2, y) 16 | import TypedSvg.Core exposing (Svg, text) 17 | import TypedSvg.Types exposing (AnchorAlignment(..), Transform(..), em) 18 | 19 | 20 | w : Float 21 | w = 22 | 900 23 | 24 | 25 | h : Float 26 | h = 27 | 450 28 | 29 | 30 | padding : Float 31 | padding = 32 | 30 33 | 34 | 35 | mainRadius : Float 36 | mainRadius = 37 | Basics.min w h / 2 - padding 38 | 39 | 40 | radiusScale : ContinuousScale Float 41 | radiusScale = 42 | Scale.linear ( 0, mainRadius ) ( 0, 0.5 ) 43 | 44 | 45 | fn : Float -> Float 46 | fn t = 47 | sin (2 * t) * cos (2 * t) 48 | 49 | 50 | data : List (Maybe ( Float, Float )) 51 | data = 52 | Statistics.range 0 (2 * pi) 0.01 53 | |> List.map (\t -> Just ( -t + pi / 2, Scale.convert radiusScale (fn t) )) 54 | 55 | 56 | spoke : Float -> Svg msg 57 | spoke angle = 58 | g [ transform [ Rotate -angle 0 0 ] ] 59 | [ line [ x2 mainRadius ] [] 60 | , text_ 61 | [ x (mainRadius + 6) 62 | , dy (em 0.35) 63 | , textAnchor 64 | (if angle < 270 && angle > 90 then 65 | AnchorEnd 66 | 67 | else 68 | AnchorInherit 69 | ) 70 | , transform 71 | (if angle < 270 && angle > 90 then 72 | [ Rotate 180 (mainRadius + 6) 0 ] 73 | 74 | else 75 | [] 76 | ) 77 | ] 78 | [ text (String.fromFloat angle ++ "°") ] 79 | ] 80 | 81 | 82 | radialAxis : Float -> Svg msg 83 | radialAxis radius = 84 | g [] 85 | [ circle [ r <| Scale.convert radiusScale radius ] [] 86 | , text_ [ y <| Scale.convert radiusScale radius * -1 - 4, transform [ Rotate 15 0 0 ], textAnchor AnchorMiddle ] 87 | [ radius |> Scale.tickFormat radiusScale 5 |> text ] 88 | ] 89 | 90 | 91 | css : String 92 | css = 93 | """ 94 | .frame { 95 | fill: none; 96 | stroke: #000; 97 | } 98 | 99 | .axis text { 100 | font: 10px sans-serif; 101 | } 102 | 103 | .axis line, 104 | .axis circle { 105 | fill: none; 106 | stroke: #777; 107 | stroke-dasharray: 1,4; 108 | } 109 | 110 | .axis :last-of-type circle { 111 | stroke: #333; 112 | stroke-dasharray: none; 113 | } 114 | 115 | .line { 116 | fill: none; 117 | stroke: red; 118 | stroke-width: 1.5px; 119 | } 120 | 121 | .label { 122 | font-family: sans-serif; 123 | } 124 | """ 125 | 126 | 127 | main : Svg msg 128 | main = 129 | svg [ viewBox 0 0 w h ] 130 | [ style [] [ text css ] 131 | , g [ class [ "label" ], transform [ Translate (padding * 2) (h / 2) ] ] 132 | [ text_ [ fontSize 20 ] [ text "sin(2x)cos(2x)" ] 133 | , text_ [ fontSize 12, y 20 ] [ text "A polar plot." ] 134 | ] 135 | , g [ transform [ Translate (w / 2 + mainRadius) (h / 2) ] ] 136 | [ Scale.ticks radiusScale 5 137 | |> List.drop 1 138 | |> List.map radialAxis 139 | |> g [ class [ "r", "axis" ] ] 140 | , Statistics.range 0 360 30 141 | |> List.map spoke 142 | |> g [ class [ "a", "axis" ] ] 143 | , Path.element (Shape.lineRadial Shape.linearCurve data) [ class [ "line" ] ] 144 | ] 145 | ] 146 | -------------------------------------------------------------------------------- /examples/Scatterplot.elm: -------------------------------------------------------------------------------- 1 | module Scatterplot exposing (main) 2 | 3 | {-| This module shows how to build a very basic scatterplot. 4 | 5 | @category Basics 6 | 7 | -} 8 | 9 | import Axis 10 | import List 11 | import Scale exposing (ContinuousScale) 12 | import TypedSvg exposing (circle, defs, g, linearGradient, stop, svg) 13 | import TypedSvg.Attributes exposing (class, fill, id, offset, opacity, stopColor, stroke, transform, viewBox) 14 | import TypedSvg.Attributes.InPx exposing (cx, cy, r, strokeWidth) 15 | import TypedSvg.Core exposing (Svg) 16 | import TypedSvg.Types exposing (Length(..), Opacity(..), Paint(..), Transform(..)) 17 | 18 | 19 | w : Float 20 | w = 21 | 900 22 | 23 | 24 | h : Float 25 | h = 26 | 450 27 | 28 | 29 | padding : Float 30 | padding = 31 | 30 32 | 33 | 34 | xScale : ContinuousScale Float 35 | xScale = 36 | Scale.linear ( 0, w - 2 * padding ) ( 0, 4.5 ) 37 | 38 | 39 | yScale : ContinuousScale Float 40 | yScale = 41 | Scale.linear ( h - 2 * padding, 0 ) ( 0, 6 ) 42 | 43 | 44 | xAxis : Svg msg 45 | xAxis = 46 | Axis.bottom [] xScale 47 | 48 | 49 | yAxis : Svg msg 50 | yAxis = 51 | Axis.left [ Axis.tickCount 5 ] yScale 52 | 53 | 54 | circles : List ( Float, Float ) -> List (Svg msg) 55 | circles model = 56 | List.map pointCircle model 57 | 58 | 59 | pointCircle : ( Float, Float ) -> Svg msg 60 | pointCircle ( dataX, dataY ) = 61 | g [ class [ "data-point" ] ] 62 | [ circle 63 | [ cx (Scale.convert xScale dataX) 64 | , cy (Scale.convert yScale dataY) 65 | , r 3 66 | , fill <| Reference "linGradientRed" 67 | , strokeWidth 0 68 | , stroke <| PaintNone 69 | , opacity <| Opacity 0.85 70 | ] 71 | [] 72 | ] 73 | 74 | 75 | myDefs : List (Svg msg) 76 | myDefs = 77 | [ linearGradient 78 | [ id "linGradientRed" 79 | , TypedSvg.Attributes.x1 <| Percent 0.0 80 | , TypedSvg.Attributes.y1 <| Percent 0.0 81 | , TypedSvg.Attributes.x2 <| Percent 0.0 82 | , TypedSvg.Attributes.y2 <| Percent 100.0 83 | ] 84 | [ stop [ offset "0%", stopColor "#e52d27" ] [] 85 | , stop [ offset "100%", stopColor "#b31217" ] [] 86 | ] 87 | , linearGradient 88 | [ id "linGradientGray" 89 | , TypedSvg.Attributes.x1 <| Percent 0.0 90 | , TypedSvg.Attributes.y1 <| Percent 0.0 91 | , TypedSvg.Attributes.x2 <| Percent 0.0 92 | , TypedSvg.Attributes.y2 <| Percent 100.0 93 | ] 94 | [ stop [ offset "0%", stopColor "#5b6467" ] [] 95 | , stop [ offset "74%", stopColor "#8b939a" ] [] 96 | ] 97 | ] 98 | 99 | 100 | view : List ( Float, Float ) -> Svg msg 101 | view model = 102 | svg [ viewBox 0 0 w h ] 103 | -- 104 | [ defs [] myDefs 105 | , g [ transform [ Translate (padding - 1) (h - padding) ] ] 106 | [ xAxis ] 107 | , g [ transform [ Translate (padding - 1) padding ] ] 108 | [ yAxis ] 109 | , g [ transform [ Translate padding padding ], class [ "series" ] ] 110 | (circles model) 111 | ] 112 | 113 | 114 | main : Svg msg 115 | main = 116 | view dataPoints 117 | 118 | 119 | dataPoints : List ( Float, Float ) 120 | dataPoints = 121 | [ ( 2.07, 3.82 ), ( 2.48, 4.52 ), ( 3.82, 5.25 ), ( 1.6, 3.31 ), ( 1.0, 1.8 ), ( 1.35, 2.6 ), ( 2.41, 4.31 ), ( 2.64, 4.5 ), ( 2.22, 3.79 ), ( 3.29, 4.69 ), ( 1.03, 2.29 ), ( 3.19, 5.08 ), ( 3.81, 5.5 ), ( 2.83, 4.62 ), ( 3.72, 5.11 ), ( 3.29, 5.02 ), ( 0.67, 1.07 ), ( 3.96, 5.62 ), ( 2.66, 4.69 ), ( 2.18, 3.89 ), ( 1.64, 3.32 ), ( 2.19, 3.66 ), ( 2.12, 3.84 ), ( 0.56, 0.76 ), ( 1.22, 2.37 ), ( 1.5, 2.98 ), ( 3.36, 4.78 ), ( 0.99, 2.14 ), ( 2.88, 4.48 ), ( 0.7, 1.19 ), ( 0.6, 0.58 ), ( 1.74, 3.41 ), ( 2.43, 4.33 ), ( 2.29, 4.2 ), ( 3.66, 5.11 ), ( 3.54, 4.87 ), ( 3.58, 4.96 ), ( 2.54, 4.28 ), ( 3.04, 4.43 ), ( 0.64, 1.0 ), ( 3.91, 5.51 ), ( 1.66, 3.16 ), ( 3.52, 5.08 ), ( 2.24, 3.78 ), ( 3.33, 5.17 ), ( 2.0, 3.63 ), ( 1.42, 2.88 ), ( 3.14, 4.64 ), ( 3.54, 4.92 ), ( 1.36, 2.64 ), ( 2.15, 4.06 ), ( 3.21, 5.13 ), ( 2.55, 4.27 ), ( 3.02, 4.74 ), ( 3.09, 4.69 ), ( 2.59, 4.4 ), ( 0.61, 0.67 ), ( 3.01, 4.74 ), ( 3.83, 5.4 ), ( 0.99, 2.23 ), ( 0.59, 0.65 ), ( 2.62, 4.24 ), ( 0.89, 1.29 ), ( 2.19, 4.01 ), ( 4.0, 5.61 ), ( 2.37, 4.28 ), ( 0.98, 1.99 ), ( 1.11, 2.2 ), ( 3.96, 5.29 ), ( 1.94, 3.54 ), ( 2.44, 4.29 ), ( 2.9, 4.76 ), ( 2.46, 4.74 ), ( 2.37, 4.18 ), ( 2.31, 4.08 ), ( 1.36, 2.63 ), ( 0.8, 1.23 ), ( 2.53, 4.32 ), ( 0.98, 1.88 ), ( 2.64, 4.27 ), ( 2.41, 4.25 ), ( 0.64, 0.94 ), ( 3.93, 5.41 ), ( 1.55, 2.67 ), ( 3.26, 4.71 ), ( 1.06, 2.05 ), ( 2.78, 4.6 ), ( 2.39, 4.08 ), ( 0.78, 1.57 ), ( 3.7, 5.26 ), ( 1.01, 1.79 ), ( 2.65, 4.54 ), ( 2.82, 4.7 ), ( 1.07, 2.24 ), ( 2.64, 4.29 ), ( 2.09, 4.02 ), ( 1.64, 2.98 ), ( 3.19, 4.81 ), ( 2.33, 4.25 ), ( 0.97, 1.88 ), ( 3.0, 4.55 ), ( 1.56, 3.26 ), ( 3.01, 4.84 ), ( 2.12, 3.9 ), ( 2.71, 4.29 ), ( 3.88, 4.9 ), ( 2.79, 4.54 ), ( 2.86, 4.62 ), ( 3.57, 5.27 ), ( 2.37, 4.15 ), ( 1.13, 2.38 ), ( 1.38, 2.6 ), ( 1.09, 2.28 ), ( 3.39, 4.65 ), ( 1.08, 2.27 ), ( 1.83, 3.3 ), ( 3.21, 4.72 ), ( 2.77, 4.49 ), ( 3.45, 5.36 ), ( 0.76, 1.63 ), ( 2.55, 4.49 ), ( 3.97, 5.19 ), ( 3.54, 5.17 ), ( 2.31, 4.24 ), ( 3.79, 5.32 ), ( 0.6, 0.8 ), ( 2.66, 4.12 ), ( 1.19, 2.77 ), ( 1.67, 3.23 ), ( 1.69, 3.55 ), ( 0.83, 1.49 ), ( 0.56, 0.82 ), ( 2.62, 4.32 ), ( 3.17, 5.2 ), ( 1.04, 1.92 ), ( 3.37, 4.97 ), ( 2.36, 4.01 ), ( 3.24, 4.89 ), ( 1.04, 2.49 ), ( 2.84, 4.71 ), ( 2.23, 3.95 ), ( 2.14, 3.87 ), ( 2.47, 4.32 ), ( 3.99, 5.37 ), ( 3.88, 5.3 ), ( 2.81, 4.57 ), ( 1.43, 2.82 ), ( 2.0, 3.82 ), ( 3.08, 4.99 ), ( 1.77, 3.41 ), ( 3.63, 5.22 ), ( 2.23, 4.03 ), ( 0.97, 1.94 ), ( 2.49, 4.33 ), ( 1.11, 2.62 ), ( 1.23, 2.64 ), ( 3.42, 4.77 ), ( 1.26, 2.36 ), ( 2.3, 4.32 ), ( 2.13, 3.72 ), ( 1.34, 2.56 ), ( 2.43, 4.34 ), ( 1.62, 3.3 ), ( 0.74, 1.34 ), ( 0.64, 0.87 ), ( 2.3, 3.97 ), ( 3.55, 5.15 ), ( 3.76, 5.28 ), ( 0.97, 2.0 ), ( 2.65, 4.23 ), ( 1.3, 2.89 ), ( 2.56, 4.11 ), ( 2.67, 4.51 ), ( 2.31, 3.69 ), ( 2.12, 3.87 ), ( 1.05, 2.16 ), ( 2.43, 4.29 ), ( 1.95, 4.0 ), ( 1.27, 2.45 ), ( 1.81, 3.36 ), ( 1.6, 3.26 ), ( 2.54, 4.5 ), ( 0.64, 0.84 ), ( 2.79, 4.68 ), ( 1.41, 3.31 ), ( 2.3, 4.2 ), ( 3.35, 5.15 ), ( 2.37, 4.12 ), ( 3.6, 5.05 ), ( 3.07, 4.51 ), ( 2.11, 4.02 ), ( 2.84, 4.73 ), ( 0.88, 1.94 ), ( 1.33, 2.91 ), ( 3.18, 5.11 ), ( 0.72, 0.97 ), ( 0.59, 0.9 ), ( 1.27, 2.68 ), ( 0.76, 1.66 ), ( 2.09, 3.66 ) ] 122 | -------------------------------------------------------------------------------- /examples/StackedBarChart.elm: -------------------------------------------------------------------------------- 1 | module StackedBarChart exposing (main) 2 | 3 | {-| @category Advanced 4 | -} 5 | 6 | import Axis 7 | import Color exposing (Color) 8 | import List.Extra as List 9 | import Scale exposing (BandScale, ContinuousScale, defaultBandConfig) 10 | import Scale.Color 11 | import Shape exposing (StackConfig, StackResult) 12 | import TypedSvg exposing (g, rect, svg) 13 | import TypedSvg.Attributes exposing (class, fill, transform, viewBox) 14 | import TypedSvg.Attributes.InPx exposing (height, width, x, y) 15 | import TypedSvg.Core exposing (Svg) 16 | import TypedSvg.Types exposing (Paint(..), Transform(..)) 17 | 18 | 19 | main : Svg msg 20 | main = 21 | view (Shape.stack config) 22 | 23 | 24 | type alias Year = 25 | Int 26 | 27 | 28 | series : List { label : String, accessor : CrimeRate -> Int } 29 | series = 30 | [ { label = "Assault" 31 | , accessor = .assault 32 | } 33 | , { label = "Rape" 34 | , accessor = .rape 35 | } 36 | , { label = "Robbery" 37 | , accessor = .robbery 38 | } 39 | , { label = "Murder" 40 | , accessor = .murder 41 | } 42 | ] 43 | 44 | 45 | samples : List ( String, List Float ) 46 | samples = 47 | List.map (\{ label, accessor } -> ( label, List.map (toFloat << accessor) crimeRates )) series 48 | 49 | 50 | w : Float 51 | w = 52 | 990 53 | 54 | 55 | h : Float 56 | h = 57 | 504 58 | 59 | 60 | padding : { bottom : Float, left : Float, right : Float, top : Float } 61 | padding = 62 | { top = 30 63 | , left = 60 64 | , right = 30 65 | , bottom = 60 66 | } 67 | 68 | 69 | config : StackConfig String 70 | config = 71 | { data = samples 72 | , offset = Shape.stackOffsetNone 73 | , order = 74 | -- stylistic choice: largest (by sum of values) category at the bottom 75 | List.sortBy (Tuple.second >> List.sum >> negate) 76 | } 77 | 78 | 79 | reverseViridis : Float -> Color 80 | reverseViridis progression = 81 | -- stylistic choice: the larger boxes look better in brighter colors, so invert the interpolator 82 | Scale.Color.viridisInterpolator (1 - progression) 83 | 84 | 85 | colors : Int -> List Color 86 | colors size = 87 | let 88 | colorScale = 89 | Scale.sequential reverseViridis ( 0, toFloat size - 1 ) 90 | |> Scale.convert 91 | in 92 | List.range 0 (size - 1) 93 | |> List.map (colorScale << toFloat) 94 | 95 | 96 | column : BandScale Year -> ( Year, List ( Float, Float ) ) -> Svg msg 97 | column xScale ( year, values ) = 98 | let 99 | block color ( upperY, lowerY ) = 100 | rect 101 | [ x <| Scale.convert xScale year 102 | , y <| lowerY 103 | , width <| Scale.bandwidth xScale 104 | , height <| (abs <| upperY - lowerY) 105 | , fill (Paint color) 106 | ] 107 | [] 108 | in 109 | g [ class [ "column" ] ] (List.map2 block (colors (List.length values)) values) 110 | 111 | 112 | view : StackResult String -> Svg msg 113 | view { values, extent } = 114 | let 115 | -- transpose back to get the values per year 116 | yearValues = 117 | List.transpose values 118 | 119 | years = 120 | List.map .year crimeRates 121 | 122 | xScale : BandScale Year 123 | xScale = 124 | Scale.band { defaultBandConfig | paddingInner = 0.1, paddingOuter = 0.2 } ( 0, w - (padding.left + padding.right) ) years 125 | 126 | yScale : ContinuousScale Float 127 | yScale = 128 | Scale.linear ( h - (padding.top + padding.bottom), 0 ) extent 129 | |> Scale.nice 4 130 | 131 | scaledValues = 132 | List.map (List.map (\( y1, y2 ) -> ( Scale.convert yScale y1, Scale.convert yScale y2 ))) yearValues 133 | in 134 | svg [ viewBox 0 0 w h ] 135 | [ g [ transform [ Translate (padding.left - 1) (h - padding.bottom) ] ] 136 | [ Axis.bottom [ Axis.tickCount 10 ] (Scale.toRenderable String.fromInt xScale) ] 137 | , g [ transform [ Translate (padding.left - 1) padding.top ] ] 138 | [ Axis.left [] yScale ] 139 | , g [ transform [ Translate padding.left padding.top ], class [ "series" ] ] <| 140 | List.map (column xScale) (List.map2 (\a b -> ( a, b )) years scaledValues) 141 | ] 142 | 143 | 144 | type alias CrimeRate = 145 | { year : Int 146 | , population : Int 147 | , murder : Int 148 | , rape : Int 149 | , robbery : Int 150 | , assault : Int 151 | , burglary : Int 152 | , larceny : Int 153 | , motorTheft : Int 154 | } 155 | 156 | 157 | crimeRates : List CrimeRate 158 | crimeRates = 159 | [ CrimeRate 1994 260327021 23326 102216 618949 1113179 2712774 7879812 1539287 160 | , CrimeRate 1995 262803276 21606 97470 580509 1099207 2593784 7997710 1472441 161 | , CrimeRate 1996 265228572 19645 96252 535594 1037049 2506400 7904685 1394238 162 | , CrimeRate 1997 267783607 18208 96153 498534 1023201 2460526 7743760 1354189 163 | , CrimeRate 1998 270248003 16974 93144 447186 976583 2332735 7376311 1242781 164 | , CrimeRate 1999 272690813 15522 89411 409371 911740 2100739 6955520 1152075 165 | , CrimeRate 2000 281421906 15586 90178 408016 911706 2050992 6971590 1160002 166 | , CrimeRate 2001 285317559 16037 90863 423557 909023 2116531 7092267 1228391 167 | , CrimeRate 2002 287973924 16229 95235 420806 891407 2151252 7057379 1246646 168 | , CrimeRate 2003 290788976 16528 93883 414235 859030 2154834 7026802 1261226 169 | , CrimeRate 2004 293656842 16148 95089 401470 847381 2144446 6937089 1237851 170 | , CrimeRate 2005 296507061 16740 94347 417438 862220 2155448 6783447 1235859 171 | , CrimeRate 2006 299398484 17309 94472 449246 874096 2194993 6626363 1198245 172 | , CrimeRate 2007 301621157 17128 92160 447324 866358 2190198 6591542 1100472 173 | , CrimeRate 2008 304059724 16465 90750 443563 843683 2228887 6586206 959059 174 | , CrimeRate 2009 307006550 15399 89241 408742 812514 2203313 6338095 795652 175 | , CrimeRate 2010 309330219 14722 85593 369089 781844 2168459 6204601 739565 176 | , CrimeRate 2011 311587816 14661 84175 354746 752423 2185140 6151095 716508 177 | , CrimeRate 2012 313873685 14856 85141 355051 762009 2109932 6168874 723186 178 | , CrimeRate 2013 316128839 14196 79770 345031 724149 1928465 6004453 699594 179 | ] 180 | -------------------------------------------------------------------------------- /examples/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | ".", 5 | "../src" 6 | ], 7 | "elm-version": "0.19.1", 8 | "dependencies": { 9 | "direct": { 10 | "BrianHicks/elm-csv": "3.0.3", 11 | "RalfNorthman/elm-lttb": "1.0.3", 12 | "avh4/elm-color": "1.0.0", 13 | "elm/browser": "1.0.2", 14 | "elm/core": "1.0.5", 15 | "elm/html": "1.0.0", 16 | "elm/http": "2.0.0", 17 | "elm/json": "1.1.3", 18 | "elm/random": "1.0.0", 19 | "elm/regex": "1.0.0", 20 | "elm/svg": "1.0.1", 21 | "elm/time": "1.0.0", 22 | "elm/url": "1.0.0", 23 | "elm-community/graph": "6.0.0", 24 | "elm-community/intdict": "3.0.0", 25 | "elm-community/typed-svg": "7.0.0", 26 | "elm-explorations/linear-algebra": "1.0.3", 27 | "elm-explorations/webgl": "1.1.3", 28 | "elmcraft/core-extra": "2.0.0", 29 | "folkertdev/one-true-path-experiment": "6.0.0", 30 | "gampleman/elm-examples-helper": "2.0.0", 31 | "gampleman/elm-rosetree": "1.1.0", 32 | "ianmackenzie/elm-geometry": "3.11.0", 33 | "ianmackenzie/elm-units-prefixed": "2.8.0", 34 | "justinmimbs/time-extra": "1.1.1", 35 | "mpizenberg/elm-pointer-events": "5.0.0", 36 | "rtfeldman/elm-hex": "1.0.0", 37 | "rtfeldman/elm-iso8601-date-strings": "1.1.4", 38 | "ryan-haskell/date-format": "1.0.0" 39 | }, 40 | "indirect": { 41 | "avh4/elm-fifo": "1.0.4", 42 | "elm/bytes": "1.0.8", 43 | "elm/file": "1.0.5", 44 | "elm/parser": "1.1.0", 45 | "elm/virtual-dom": "1.0.3", 46 | "elm-community/list-extra": "8.7.0", 47 | "folkertdev/elm-deque": "3.0.1", 48 | "folkertdev/svg-path-lowlevel": "4.0.1", 49 | "ianmackenzie/elm-1d-parameter": "1.0.1", 50 | "ianmackenzie/elm-float-extra": "1.1.0", 51 | "ianmackenzie/elm-interval": "3.1.0", 52 | "ianmackenzie/elm-triangular-mesh": "1.1.0", 53 | "ianmackenzie/elm-units": "2.10.0", 54 | "ianmackenzie/elm-units-interval": "3.2.0", 55 | "justinmimbs/date": "4.0.1" 56 | } 57 | }, 58 | "test-dependencies": { 59 | "direct": {}, 60 | "indirect": {} 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elm-visualization", 3 | "version": "1.0.0", 4 | "description": "[Docs](http://package.elm-lang.org/packages/gampleman/elm-visualization/latest/) | [Examples](http://elm-visualization.netlify.app/) | [GitHub](https://github.com/gampleman/elm-visualization) | [Changelog](https://github.com/gampleman/elm-visualization/releases) | `#visualization` on [Elm slack](https://elmlang.herokuapp.com)", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs", 8 | "example": "examples", 9 | "test": "tests" 10 | }, 11 | "scripts": { 12 | "build-docs": "elm-example-publisher --ellie --ellie-dep gampleman/elm-visualization@2.3.0 --base-url https://elm-visualization.netlify.app/", 13 | "test": "npm-run-all --print-name --sequential test:make test:format test:examples test:run test:review test:review:examples", 14 | "test:make": "elm make --docs=docs.json", 15 | "test:format": "elm-format src/ tests/ examples/ review/ --validate", 16 | "test:examples": "elm-verify-examples && elm-format --yes tests/VerifyExamples > /dev/null", 17 | "test:run": "elm-test", 18 | "test:review": "elm-review", 19 | "test:review:examples": "elm-review --elmjson examples/elm.json --config review --ignore-dirs ../tests,../src", 20 | "preview-docs": "elm-doc-preview", 21 | "elm-bump": "npm-run-all --print-name --sequential test bump-version 'test:review -- --fix-all-without-prompt'", 22 | "bump-version": "(yes | elm bump)", 23 | "format": "elm-format src/ tests/ examples/ review/ --yes" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/gampleman/elm-visualization.git" 28 | }, 29 | "author": "", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/gampleman/elm-visualization/issues" 33 | }, 34 | "homepage": "https://github.com/gampleman/elm-visualization#readme", 35 | "devDependencies": { 36 | "elm-example-publisher": "1.1.0" 37 | }, 38 | "dependencies": { 39 | "elm": "0.19.1-5", 40 | "elm-doc-preview": "^5.0.5", 41 | "elm-format": "^0.8.2", 42 | "elm-review": "^2.10.2", 43 | "elm-test": "0.19.1-revision12", 44 | "elm-verify-examples": "^5.2.0", 45 | "npm-run-all": "^4.1.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /review/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/core": "1.0.5", 10 | "elm/json": "1.1.3", 11 | "elm/project-metadata-utils": "1.0.2", 12 | "jfmengels/elm-review": "2.13.1", 13 | "jfmengels/elm-review-code-style": "1.1.4", 14 | "jfmengels/elm-review-common": "1.3.3", 15 | "jfmengels/elm-review-debug": "1.0.8", 16 | "jfmengels/elm-review-documentation": "2.0.3", 17 | "jfmengels/elm-review-performance": "1.0.2", 18 | "jfmengels/elm-review-simplify": "2.0.32", 19 | "jfmengels/elm-review-unused": "1.1.30", 20 | "stil4m/elm-syntax": "7.2.9" 21 | }, 22 | "indirect": { 23 | "elm/bytes": "1.0.8", 24 | "elm/html": "1.0.0", 25 | "elm/parser": "1.1.0", 26 | "elm/random": "1.0.0", 27 | "elm/regex": "1.0.0", 28 | "elm/time": "1.0.0", 29 | "elm/virtual-dom": "1.0.3", 30 | "elm-community/list-extra": "8.7.0", 31 | "elm-explorations/test": "2.1.1", 32 | "miniBill/elm-unicode": "1.0.3", 33 | "pzp1997/assoc-list": "1.0.0", 34 | "rtfeldman/elm-hex": "1.0.0", 35 | "stil4m/structured-writer": "1.0.3" 36 | } 37 | }, 38 | "test-dependencies": { 39 | "direct": {}, 40 | "indirect": {} 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /review/src/ReviewConfig.elm: -------------------------------------------------------------------------------- 1 | module ReviewConfig exposing (config) 2 | 3 | {-| Do not rename the ReviewConfig module or the config function, because 4 | `elm-review` will look for these. 5 | 6 | To add packages that contain rules, add them to this review project using 7 | 8 | `elm install author/packagename` 9 | 10 | when inside the directory containing this file. 11 | 12 | -} 13 | 14 | import Docs.NoMissing exposing (exposedModules, onlyExposed) 15 | import Docs.ReviewAtDocs 16 | import Docs.ReviewLinksAndSections 17 | import Docs.UpToDateReadmeLinks 18 | import NoConfusingPrefixOperator 19 | import NoDebug.Log 20 | import NoDebug.TodoOrToString 21 | import NoExposingEverything 22 | import NoImportingEverything 23 | import NoMissingTypeAnnotation 24 | import NoMissingTypeAnnotationInLetIn 25 | import NoMissingTypeExpose 26 | import NoPrematureLetComputation 27 | import NoSimpleLetBody 28 | import NoUnoptimizedRecursion 29 | import NoUnused.CustomTypeConstructorArgs 30 | import NoUnused.CustomTypeConstructors 31 | import NoUnused.Dependencies 32 | import NoUnused.Exports 33 | import NoUnused.Parameters 34 | import NoUnused.Patterns 35 | import NoUnused.Variables 36 | import Review.Rule as Rule exposing (Rule) 37 | import Simplify 38 | 39 | 40 | config : List Rule 41 | config = 42 | [ {- Docs.NoMissing.rule 43 | { document = onlyExposed 44 | , from = exposedModules 45 | } 46 | , 47 | -} 48 | -- Docs.ReviewLinksAndSections.rule 49 | Docs.ReviewAtDocs.rule 50 | , Docs.UpToDateReadmeLinks.rule 51 | , NoConfusingPrefixOperator.rule 52 | , NoDebug.Log.rule 53 | , NoDebug.TodoOrToString.rule 54 | |> Rule.ignoreErrorsForDirectories [ "tests/" ] 55 | , NoExposingEverything.rule 56 | , NoImportingEverything.rule [] 57 | 58 | -- , NoMissingTypeAnnotation.rule 59 | , NoMissingTypeExpose.rule 60 | , NoSimpleLetBody.rule 61 | , NoPrematureLetComputation.rule 62 | , NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optOutWithComment "IGNORE TCO") 63 | -- We should indeed make quadtree tail recursive, but that is a MAJOR refactoring 64 | -- in performance critical code... 65 | |> Rule.ignoreErrorsForDirectories [ "tests/", "src/Force/" ] 66 | |> Rule.ignoreErrorsForFiles [ "src/Hierarchy/Tidy.elm" ] 67 | , NoUnused.CustomTypeConstructors.rule [] 68 | , NoUnused.CustomTypeConstructorArgs.rule 69 | 70 | -- , NoUnused.Dependencies.rule 71 | , NoUnused.Exports.rule 72 | , NoUnused.Parameters.rule 73 | , NoUnused.Patterns.rule 74 | , NoUnused.Variables.rule 75 | , Simplify.rule Simplify.defaults 76 | |> Rule.ignoreErrorsForFiles [ "tests/StatisticsTests.elm" ] 77 | ] 78 | |> List.map (Rule.ignoreErrorsForDirectories [ "tests/VerifyExamples" ]) 79 | -------------------------------------------------------------------------------- /src/Color/Lab.elm: -------------------------------------------------------------------------------- 1 | module Color.Lab exposing 2 | ( fromHcl 3 | , fromLab 4 | , toHcl 5 | , toLab 6 | ) 7 | 8 | import Color exposing (Color, toRgba) 9 | 10 | 11 | {-| Implements conversions to and from the CIELAB color space. 12 | 13 | For more information about this, see . 14 | 15 | For an explanation of the math in this module, see . 16 | 17 | -} 18 | 19 | 20 | 21 | -- Constants 22 | 23 | 24 | nan : Float 25 | nan = 26 | 0 / 0 27 | 28 | 29 | xn : Float 30 | xn = 31 | 0.96422 32 | 33 | 34 | zn : Float 35 | zn = 36 | 0.82521 37 | 38 | 39 | t0 : Float 40 | t0 = 41 | 4 / 29 42 | 43 | 44 | t1 : Float 45 | t1 = 46 | 6 / 29 47 | 48 | 49 | t2 : Float 50 | t2 = 51 | 3 * t1 ^ 2 52 | 53 | 54 | t3 : Float 55 | t3 = 56 | t1 ^ 3 57 | 58 | 59 | 60 | -- TO 61 | 62 | 63 | to : Float -> Float -> Float -> { l : Float, a : Float, b : Float } 64 | to sr sg sb = 65 | let 66 | ( lr, lg, lb ) = 67 | ( srgb2lrgb sr, srgb2lrgb sg, srgb2lrgb sb ) 68 | 69 | y = 70 | xyz2lab (0.2225045 * lr + 0.7168786 * lg + 0.0606169 * lb) 71 | 72 | ( x, z ) = 73 | if lr == lg && lg == lb then 74 | ( y, y ) 75 | 76 | else 77 | ( xyz2lab ((0.4360747 * lr + 0.3850649 * lg + 0.1430804 * lb) / xn) 78 | , xyz2lab ((0.0139322 * lr + 0.0971045 * lg + 0.7141733 * lb) / zn) 79 | ) 80 | in 81 | { l = 116 * y - 16, a = 500 * (x - y), b = 200 * (y - z) } 82 | 83 | 84 | {-| Converts (a coordinate) from the sRGB color space to the linear light RGB, undoing the gamma encoding of sRGB space. 85 | -} 86 | srgb2lrgb : Float -> Float 87 | srgb2lrgb v = 88 | if v <= 0.04045 then 89 | v / 12.92 90 | 91 | else 92 | ((v + 0.055) / 1.055) ^ 2.4 93 | 94 | 95 | {-| Converts (a coordinate) from the XYZ color space to the LAB color space. 96 | -} 97 | xyz2lab : Float -> Float 98 | xyz2lab t = 99 | if t > t3 then 100 | t ^ (1 / 3) 101 | 102 | else 103 | t / t2 + t0 104 | 105 | 106 | 107 | -- FROM 108 | 109 | 110 | from : Float -> Float -> Float -> { red : Float, green : Float, blue : Float } 111 | from l a b = 112 | let 113 | fl = 114 | (l + 16) / 116 115 | 116 | fa = 117 | if isNaN a then 118 | 0 119 | 120 | else 121 | a / 500 122 | 123 | fb = 124 | if isNaN b then 125 | 0 126 | 127 | else 128 | b / 200 129 | 130 | x = 131 | xn * lab2xyz (fl + fa) 132 | 133 | y = 134 | lab2xyz fl 135 | 136 | z = 137 | zn * lab2xyz (fl - fb) 138 | in 139 | { red = lrgb2srgb (3.1338561 * x - 1.6168667 * y - 0.4906146 * z) 140 | , green = lrgb2srgb (-0.9787684 * x + 1.9161415 * y + 0.033454 * z) 141 | , blue = lrgb2srgb (0.0719453 * x - 0.2289914 * y + 1.4052427 * z) 142 | } 143 | 144 | 145 | {-| Converts (a coordinate) from the LAB color space to the XYZ color space. 146 | -} 147 | lab2xyz : Float -> Float 148 | lab2xyz t = 149 | if t > t1 then 150 | t ^ 3 151 | 152 | else 153 | t2 * (t - t0) 154 | 155 | 156 | lrgb2srgb : Float -> Float 157 | lrgb2srgb v = 158 | if v <= 0.0031308 then 159 | 12.92 * v 160 | 161 | else 162 | 1.055 * (v ^ (1 / 2.4)) - 0.055 163 | 164 | 165 | {-| Extract the L\*a\*b\* and alpha components in the [CIELAB color space](https://en.wikipedia.org/wiki/CIELAB_color_space). 166 | -} 167 | toLab : Color -> { l : Float, a : Float, b : Float, alpha : Float } 168 | toLab color = 169 | let 170 | { red, green, blue, alpha } = 171 | toRgba color 172 | 173 | result = 174 | to red green blue 175 | in 176 | { l = result.l, a = result.a, b = result.b, alpha = alpha } 177 | 178 | 179 | {-| Specify a color using the [CIELAB color space](https://en.wikipedia.org/wiki/CIELAB_color_space). 180 | The value of l is typically in the range [0, 100], while a and b are typically in [-160, +160]. 181 | -} 182 | fromLab : { l : Float, a : Float, b : Float, alpha : Float } -> Color 183 | fromLab { l, a, b, alpha } = 184 | let 185 | result = 186 | from l a b 187 | in 188 | Color.rgba result.red result.green result.blue alpha 189 | 190 | 191 | {-| Extract the hue, chroma, luminance and alpha components in the [CIE Lch(ab)](https://en.wikipedia.org/wiki/HCL_color_space) color space. 192 | -} 193 | toHcl : Color -> { hue : Float, chroma : Float, luminance : Float, alpha : Float } 194 | toHcl color = 195 | let 196 | { l, a, b, alpha } = 197 | toLab color 198 | in 199 | if a == 0 && b == 0 then 200 | { hue = nan 201 | , chroma = 202 | if 0 < l && l < 100 then 203 | nan 204 | 205 | else 206 | 0 207 | , luminance = l 208 | , alpha = alpha 209 | } 210 | 211 | else 212 | let 213 | h = 214 | atan2 b a * 180 / pi 215 | in 216 | { hue = 217 | if h < 0 then 218 | h + 360 219 | 220 | else 221 | h 222 | , chroma = sqrt (a ^ 2 + b ^ 2) 223 | , luminance = l 224 | , alpha = alpha 225 | } 226 | 227 | 228 | {-| Constructs a color in the [CIE Lch(ab)](https://en.wikipedia.org/wiki/HCL_color_space) color space. 229 | This is especially useful for generating and manipulating colors, as this is a perceptually uniform color space. 230 | The value of l is typically in the range [0, 100], c is typically in [0, 230], and h is typically in [0, 360). 231 | -} 232 | fromHcl : { hue : Float, chroma : Float, luminance : Float, alpha : Float } -> Color 233 | fromHcl p = 234 | if isNaN p.hue then 235 | fromLab { l = p.luminance, a = 0, b = 0, alpha = p.alpha } 236 | 237 | else 238 | let 239 | h = 240 | p.hue * pi / 180 241 | in 242 | fromLab { l = p.luminance, a = cos h * p.chroma, b = sin h * p.chroma, alpha = p.alpha } 243 | -------------------------------------------------------------------------------- /src/Events.elm: -------------------------------------------------------------------------------- 1 | module Events exposing (Rect, Touch, decodeMousePosition, decodeSVGTransformMatrix, decodeTouches, normalizePointerPosition) 2 | 3 | import Json.Decode as D exposing (Decoder) 4 | import Zoom.Matrix as Matrix exposing (Matrix2x3) 5 | 6 | 7 | type alias Touch = 8 | { position : ( Float, Float ) 9 | , identifier : Int 10 | } 11 | 12 | 13 | type alias Rect = 14 | { x : Float 15 | , y : Float 16 | , width : Float 17 | , height : Float 18 | } 19 | 20 | 21 | normalizePointerPosition : ( Float, Float ) -> Maybe Matrix2x3 -> ( Float, Float ) 22 | normalizePointerPosition position maybeMatrix = 23 | case maybeMatrix of 24 | Just matrix -> 25 | Matrix.transform position matrix 26 | 27 | Nothing -> 28 | position 29 | 30 | 31 | decodeMousePosition : Decoder ( Float, Float ) 32 | decodeMousePosition = 33 | D.map3 34 | (\maybeMatrix x y -> 35 | normalizePointerPosition ( x, y ) maybeMatrix 36 | ) 37 | decodeSVGTransformMatrix 38 | (D.oneOf [ D.field "offsetX" D.float, D.field "clientX" D.float ]) 39 | (D.oneOf [ D.field "offsetY" D.float, D.field "clientY" D.float ]) 40 | 41 | 42 | decodeSVGTransformMatrix : Decoder (Maybe Matrix2x3) 43 | decodeSVGTransformMatrix = 44 | D.oneOf 45 | [ D.map3 46 | (\viewBox width height -> 47 | Just ( ( viewBox.width / width, 0, 0 ), ( 0, viewBox.height / height, 0 ) ) 48 | ) 49 | (D.at [ "currentTarget", "viewBox", "baseVal" ] decodeRect) 50 | (D.at [ "currentTarget", "width", "baseVal", "value" ] D.float) 51 | (D.at [ "currentTarget", "height", "baseVal", "value" ] D.float) 52 | , D.succeed Nothing 53 | ] 54 | 55 | 56 | 57 | {- FFS we need this shenigan to decode these bizzaro datastructures -} 58 | 59 | 60 | listLike : Decoder a -> Decoder (List a) 61 | listLike itemDecoder = 62 | let 63 | decodeN n = 64 | List.range 0 (n - 1) 65 | |> List.map decodeOne 66 | |> List.foldr (D.map2 (::)) (D.succeed []) 67 | 68 | decodeOne n = 69 | D.field (String.fromInt n) itemDecoder 70 | in 71 | D.field "length" D.int 72 | |> D.andThen decodeN 73 | 74 | 75 | decodeTouches : Decoder (List Touch) 76 | decodeTouches = 77 | D.andThen 78 | (\maybeMatrix -> 79 | D.map3 80 | (\x y identifier -> 81 | { position = normalizePointerPosition ( x, y ) maybeMatrix, identifier = identifier } 82 | ) 83 | (D.field "clientX" D.float) 84 | (D.field "clientY" D.float) 85 | (D.field "identifier" D.int) 86 | |> listLike 87 | |> D.field "changedTouches" 88 | ) 89 | decodeSVGTransformMatrix 90 | 91 | 92 | decodeRect : Decoder Rect 93 | decodeRect = 94 | D.map4 Rect 95 | (D.field "x" D.float) 96 | (D.field "y" D.float) 97 | (D.field "width" D.float) 98 | (D.field "height" D.float) 99 | -------------------------------------------------------------------------------- /src/Force/Jiggle.elm: -------------------------------------------------------------------------------- 1 | module Force.Jiggle exposing (jiggle, jiggleVector) 2 | 3 | {-| prevents a value being exactly zero 4 | -} 5 | 6 | import Vector2d exposing (Vector2d) 7 | 8 | 9 | jiggle : Float -> Float 10 | jiggle v = 11 | if v == 0 then 12 | 1.0e-6 13 | 14 | else 15 | v 16 | 17 | 18 | jiggleVector : Vector2d units coordinates -> Vector2d units coordinates 19 | jiggleVector vec = 20 | let 21 | { x, y } = 22 | Vector2d.unwrap vec 23 | in 24 | Vector2d.unsafe { x = jiggle x, y = jiggle y } 25 | -------------------------------------------------------------------------------- /src/Hierarchy/Partition.elm: -------------------------------------------------------------------------------- 1 | module Hierarchy.Partition exposing (layout) 2 | 3 | import Hierarchy.Treemap as Treemap 4 | import Tree exposing (Tree) 5 | 6 | 7 | layout : 8 | { padding : a -> Float, value : a -> Float, size : ( Float, Float ) } 9 | -> Tree a 10 | -> Tree { x : Float, y : Float, width : Float, height : Float, value : Float, node : a } 11 | layout opts t = 12 | let 13 | ( dx, dy ) = 14 | opts.size 15 | 16 | n = 17 | toFloat (Tree.depth t) + 1 18 | in 19 | t 20 | |> Tree.map 21 | (\node -> 22 | { bbox = 23 | { x0 = opts.padding node 24 | , y0 = opts.padding node 25 | , x1 = dx 26 | , y1 = dy / n 27 | } 28 | , value = opts.value node 29 | , node = node 30 | } 31 | ) 32 | |> Tree.depthFirstTraversal 33 | (\s a l c -> 34 | let 35 | depth = 36 | List.length a 37 | 38 | children = 39 | List.map2 40 | (\bb -> 41 | Tree.updateLabel (\cn -> { cn | bbox = bb }) 42 | ) 43 | (Treemap.dice depth { x0 = l.bbox.x0, y0 = dy * (toFloat depth + 1) / n, x1 = l.bbox.x1, y1 = dy * (toFloat depth + 2) / n } l.value (List.map (\child -> .value (Tree.label child)) c)) 44 | c 45 | 46 | p = 47 | opts.padding l.node 48 | 49 | bbox0 = 50 | { x0 = l.bbox.x0 51 | , y0 = l.bbox.y0 52 | , x1 = l.bbox.x1 - p 53 | , y1 = l.bbox.y1 - p 54 | } 55 | 56 | bbox1 = 57 | if bbox0.x1 < bbox0.x0 then 58 | { bbox0 | x0 = (bbox0.x0 + bbox0.x1) / 2, x1 = (bbox0.x0 + bbox0.x1) / 2 } 59 | 60 | else 61 | bbox0 62 | 63 | bbox2 = 64 | if bbox0.y1 < bbox0.y0 then 65 | { bbox1 | y0 = (bbox0.y0 + bbox0.y1) / 2, y1 = (bbox0.y0 + bbox0.y1) / 2 } 66 | 67 | else 68 | bbox1 69 | in 70 | ( s 71 | , { x = bbox2.x0 72 | , y = bbox2.y0 73 | , width = bbox2.x1 - bbox2.x0 74 | , height = bbox2.y1 - bbox2.y0 75 | , value = l.value 76 | , node = l.node 77 | } 78 | , children 79 | ) 80 | ) 81 | (\s _ l c -> ( s, Tree.tree l c )) 82 | () 83 | |> Tuple.second 84 | -------------------------------------------------------------------------------- /src/Histogram/Array.elm: -------------------------------------------------------------------------------- 1 | module Histogram.Array exposing (bisectRight) 2 | 3 | import Array exposing (Array) 4 | import Bitwise 5 | 6 | 7 | bisectRight : comparable -> Array comparable -> Maybe ( Int, Int ) -> Int 8 | bisectRight item array extent = 9 | case Array.get 0 array of 10 | Nothing -> 11 | 0 12 | 13 | Just default -> 14 | let 15 | -- we start by doing a bounds check, so this shouldn't fail 16 | get index = 17 | Array.get index array |> Maybe.withDefault default 18 | 19 | helper lo hi = 20 | if lo < hi then 21 | let 22 | mid = 23 | Bitwise.shiftRightZfBy 1 (lo + hi) 24 | in 25 | if get mid > item then 26 | helper lo mid 27 | 28 | else 29 | helper (mid + 1) hi 30 | 31 | else 32 | lo 33 | in 34 | case extent of 35 | Just ( lo, hi ) -> 36 | helper (max 0 lo) (min hi <| Array.length array) 37 | 38 | Nothing -> 39 | helper 0 <| Array.length array 40 | -------------------------------------------------------------------------------- /src/Scale/Band.elm: -------------------------------------------------------------------------------- 1 | module Scale.Band exposing (bandwidth, convert) 2 | 3 | 4 | type alias Config = 5 | { paddingInner : Float 6 | , paddingOuter : Float 7 | , align : Float 8 | } 9 | 10 | 11 | normalizeConfig : Config -> Config 12 | normalizeConfig { paddingInner, paddingOuter, align } = 13 | { paddingInner = clamp 0 1 paddingInner 14 | , paddingOuter = clamp 0 1 paddingOuter 15 | , align = clamp 0 1 align 16 | } 17 | 18 | 19 | bandwidth : Config -> List a -> ( Float, Float ) -> Float 20 | bandwidth cfg domain ( d0, d1 ) = 21 | let 22 | { paddingInner, paddingOuter } = 23 | normalizeConfig cfg 24 | 25 | ( start, stop ) = 26 | if d0 < d1 then 27 | ( d0, d1 ) 28 | 29 | else 30 | ( d1, d0 ) 31 | 32 | n = 33 | toFloat <| List.length domain 34 | 35 | step = 36 | (stop - start) / max 1 (n - paddingInner + paddingOuter * 2) 37 | in 38 | step * (1 - paddingInner) 39 | 40 | 41 | computePositions : Config -> Float -> ( Float, Float ) -> ( Float, Float ) 42 | computePositions cfg n ( start, stop ) = 43 | let 44 | { paddingInner, paddingOuter, align } = 45 | normalizeConfig cfg 46 | 47 | step = 48 | (stop - start) / max 1 (n - paddingInner + paddingOuter * 2) 49 | 50 | start2 = 51 | start + (stop - start - step * (n - paddingInner)) * align 52 | in 53 | ( start2, step ) 54 | 55 | 56 | convert : Config -> List a -> ( Float, Float ) -> a -> Float 57 | convert cfg domain ( start, stop ) value = 58 | case indexOf value domain of 59 | Just index -> 60 | let 61 | n = 62 | toFloat <| List.length domain 63 | in 64 | if start < stop then 65 | let 66 | ( start2, step ) = 67 | computePositions cfg n ( start, stop ) 68 | in 69 | start2 + step * index 70 | 71 | else 72 | let 73 | ( stop2, step ) = 74 | computePositions cfg n ( stop, start ) 75 | in 76 | stop2 + step * (n - index - 1) 77 | 78 | Nothing -> 79 | 0 / 0 80 | 81 | 82 | indexOf : a -> List a -> Maybe number 83 | indexOf = 84 | indexOfHelp 0 85 | 86 | 87 | indexOfHelp : number -> a -> List a -> Maybe number 88 | indexOfHelp index value list = 89 | case list of 90 | [] -> 91 | Nothing 92 | 93 | x :: xs -> 94 | if value == x then 95 | Just index 96 | 97 | else 98 | indexOfHelp (index + 1) value xs 99 | -------------------------------------------------------------------------------- /src/Scale/Diverging.elm: -------------------------------------------------------------------------------- 1 | module Scale.Diverging exposing (log, power, scale, symlog) 2 | 3 | import Scale.Continuous 4 | 5 | 6 | convertWithTransform : (Float -> Float) -> ( Float, Float, Float ) -> (Float -> a) -> Float -> a 7 | convertWithTransform transform ( x0, x1, x2 ) interpolator x = 8 | let 9 | t0 = 10 | transform x0 11 | 12 | t1 = 13 | transform x1 14 | 15 | xt = 16 | transform x 17 | 18 | s = 19 | if t1 < t0 then 20 | -1 21 | 22 | else 23 | 1 24 | 25 | factor = 26 | if s * xt < s * t1 then 27 | if t0 == t1 then 28 | 0 29 | 30 | else 31 | 0.5 / (t1 - t0) 32 | 33 | else 34 | let 35 | t2 = 36 | transform x2 37 | in 38 | if t1 == t2 then 39 | 0 40 | 41 | else 42 | 0.5 / (t2 - t1) 43 | in 44 | interpolator (0.5 + (xt - t1) * factor) 45 | 46 | 47 | 48 | -- this is just `scaleWithTransform identity` 49 | 50 | 51 | scaleWithTransform transform interpolator domain = 52 | { domain = domain 53 | , range = interpolator 54 | , convert = convertWithTransform transform 55 | } 56 | 57 | 58 | scale = 59 | scaleWithTransform identity 60 | 61 | 62 | log _ interpolator (( x0, _, _ ) as domain) = 63 | let 64 | transform x = 65 | if x0 < 0 then 66 | -(logBase e -x) 67 | 68 | else 69 | logBase e x 70 | in 71 | scaleWithTransform transform interpolator domain 72 | 73 | 74 | symlog = 75 | Scale.Continuous.transformSymlog >> scaleWithTransform 76 | 77 | 78 | power = 79 | Scale.Continuous.transformPow >> scaleWithTransform 80 | -------------------------------------------------------------------------------- /src/Scale/Ordinal.elm: -------------------------------------------------------------------------------- 1 | module Scale.Ordinal exposing (convert) 2 | 3 | 4 | convert : List a -> List b -> a -> Maybe b 5 | convert domain range val = 6 | case range of 7 | [] -> 8 | Nothing 9 | 10 | _ -> 11 | convertHelp domain range [] val 12 | 13 | 14 | convertHelp : List a -> List b -> List b -> a -> Maybe b 15 | convertHelp d r used needle = 16 | case ( d, r ) of 17 | ( [], _ ) -> 18 | Nothing 19 | 20 | ( _ :: _, [] ) -> 21 | convertHelp d (List.reverse used) [] needle 22 | 23 | ( x :: xs, y :: ys ) -> 24 | if x == needle then 25 | Just y 26 | 27 | else 28 | convertHelp xs ys (y :: used) needle 29 | -------------------------------------------------------------------------------- /src/Scale/Quantile.elm: -------------------------------------------------------------------------------- 1 | module Scale.Quantile exposing (scale) 2 | 3 | import Array exposing (Array) 4 | import Histogram.Array exposing (bisectRight) 5 | import Statistics 6 | 7 | 8 | scale ( r, range ) domain = 9 | let 10 | domain_ = 11 | List.sort domain 12 | 13 | range_ = 14 | Array.fromList (r :: range) 15 | 16 | n = 17 | max 0 (List.length range) 18 | 19 | quantiles = 20 | Array.initialize n (\i -> Statistics.quantile (toFloat (i + 1) / toFloat (n + 1)) domain_ |> Maybe.withDefault 0) 21 | in 22 | { convert = convert r quantiles 23 | , invertExtent = invertExtent quantiles 24 | , domain = domain_ 25 | , range = range_ 26 | , quantiles = quantiles |> Array.toList 27 | } 28 | 29 | 30 | convert : a -> Array comparable -> c -> Array a -> comparable -> a 31 | convert default thresholds _ range x = 32 | Array.get (bisectRight x thresholds Nothing) range |> Maybe.withDefault default 33 | 34 | 35 | invertExtent : Array Float -> List Float -> Array a -> a -> Maybe ( Float, Float ) 36 | invertExtent thresholds domain range y = 37 | Maybe.andThen 38 | (\idx -> 39 | Maybe.map2 Tuple.pair 40 | (if idx <= 0 then 41 | List.head domain 42 | 43 | else 44 | Array.get (idx - 1) thresholds 45 | ) 46 | (if idx >= Array.length thresholds then 47 | last domain 48 | 49 | else 50 | Array.get idx thresholds 51 | ) 52 | ) 53 | (indexOf y range) 54 | 55 | 56 | last : List a -> Maybe a 57 | last xs = 58 | case xs of 59 | [ x ] -> 60 | Just x 61 | 62 | [] -> 63 | Nothing 64 | 65 | _ :: ys -> 66 | last ys 67 | 68 | 69 | indexOf : a -> Array a -> Maybe Int 70 | indexOf a = 71 | Array.foldl 72 | (\item idxSoFar -> 73 | case ( item == a, idxSoFar ) of 74 | ( False, Err idx ) -> 75 | Err (idx + 1) 76 | 77 | ( True, Err idx ) -> 78 | Ok idx 79 | 80 | ( _, Ok idx ) -> 81 | Ok idx 82 | ) 83 | (Err 0) 84 | >> Result.toMaybe 85 | -------------------------------------------------------------------------------- /src/Scale/Quantize.elm: -------------------------------------------------------------------------------- 1 | module Scale.Quantize exposing (convert, invertExtent, nice, rangeExtent, tickFormat, ticks) 2 | 3 | import Scale.Continuous as Linear 4 | import Statistics 5 | 6 | 7 | rangeExtent : ( Float, Float ) -> ( a, List a ) -> ( a, a ) 8 | rangeExtent ( mi, ma ) range = 9 | ( convert ( mi, ma ) range mi, convert ( mi, ma ) range ma ) 10 | 11 | 12 | computeDomain : ( Float, Float ) -> List a -> List Float 13 | computeDomain ( mi, ma ) tail = 14 | let 15 | l = 16 | List.length tail 17 | 18 | step = 19 | (ma - mi) / toFloat (l + 1) 20 | in 21 | Maybe.withDefault [ 0 ] <| List.tail <| Statistics.range mi ma step 22 | 23 | 24 | convert : ( Float, Float ) -> ( a, List a ) -> Float -> a 25 | convert domain ( head, tail ) val = 26 | let 27 | last h t = 28 | case t of 29 | [] -> 30 | h 31 | 32 | x :: xs -> 33 | last x xs 34 | 35 | helper dom range = 36 | case dom of 37 | [] -> 38 | case range of 39 | [] -> 40 | last head tail 41 | 42 | r :: _ -> 43 | r 44 | 45 | d :: ds -> 46 | case range of 47 | [] -> 48 | head 49 | 50 | r :: rs -> 51 | if val > d then 52 | helper ds rs 53 | 54 | else 55 | r 56 | in 57 | helper (computeDomain domain tail) (head :: tail) 58 | 59 | 60 | invertExtent : ( Float, Float ) -> ( a, List a ) -> a -> Maybe ( Float, Float ) 61 | invertExtent ( mi, ma ) ( head, tail ) val = 62 | let 63 | domain = 64 | computeDomain ( mi, ma ) tail 65 | 66 | helper dmn range = 67 | case range of 68 | [] -> 69 | Nothing 70 | 71 | x :: xs -> 72 | if x == val then 73 | case dmn of 74 | a :: b :: _ -> 75 | Just ( a, b ) 76 | 77 | _ -> 78 | Nothing 79 | 80 | else 81 | case dmn of 82 | [] -> 83 | Nothing 84 | 85 | _ :: ds -> 86 | helper ds xs 87 | in 88 | helper (mi :: domain ++ [ ma ]) (head :: tail) 89 | 90 | 91 | ticks : ( Float, Float ) -> ( a, List a ) -> Int -> List Float 92 | ticks ( start, end ) _ count = 93 | Statistics.ticks start end count 94 | 95 | 96 | tickFormat : ( Float, Float ) -> Int -> Float -> String 97 | tickFormat = 98 | Linear.tickFormat 99 | 100 | 101 | nice : ( Float, Float ) -> Int -> ( Float, Float ) 102 | nice = 103 | Linear.nice 104 | -------------------------------------------------------------------------------- /src/Scale/Sequential.elm: -------------------------------------------------------------------------------- 1 | module Scale.Sequential exposing (log, power, scale, symlog) 2 | 3 | import Scale.Continuous 4 | 5 | 6 | convert : ( Float, Float ) -> (Float -> a) -> Float -> a 7 | convert ( x0, x1 ) interpolator x = 8 | interpolator ((x - x0) / (x1 - x0)) 9 | 10 | 11 | convertWithTransform : (Float -> Float) -> ( Float, Float ) -> (Float -> a) -> Float -> a 12 | convertWithTransform transform ( x0, x1 ) interpolator x = 13 | let 14 | t0 = 15 | transform x0 16 | 17 | t1 = 18 | transform x1 19 | in 20 | interpolator ((transform x - t0) / (t1 - t0)) 21 | 22 | 23 | 24 | -- this is just `scaleWithTransform identity` 25 | 26 | 27 | scale interpolator domain = 28 | { domain = domain 29 | , range = interpolator 30 | , convert = convert 31 | } 32 | 33 | 34 | scaleWithTransform transform interpolator domain = 35 | { domain = domain 36 | , range = interpolator 37 | , convert = convertWithTransform transform 38 | } 39 | 40 | 41 | log _ interpolator domain = 42 | let 43 | transform x = 44 | if Tuple.first domain < 0 then 45 | -(logBase e -x) 46 | 47 | else 48 | logBase e x 49 | in 50 | scaleWithTransform transform interpolator domain 51 | 52 | 53 | symlog = 54 | Scale.Continuous.transformSymlog >> scaleWithTransform 55 | 56 | 57 | power = 58 | Scale.Continuous.transformPow >> scaleWithTransform 59 | -------------------------------------------------------------------------------- /src/Scale/Threshold.elm: -------------------------------------------------------------------------------- 1 | module Scale.Threshold exposing (scale) 2 | 3 | import Array exposing (Array) 4 | import Histogram.Array exposing (bisectRight) 5 | 6 | 7 | deinterleave : List ( a, b ) -> List a -> List b -> ( Array a, Array b ) 8 | deinterleave domrange domain range = 9 | case domrange of 10 | ( d, r ) :: xs -> 11 | deinterleave xs (d :: domain) (r :: range) 12 | 13 | [] -> 14 | ( List.reverse domain |> Array.fromList, List.reverse range |> Array.fromList ) 15 | 16 | 17 | scale ( r0, domrange ) = 18 | let 19 | ( domain_, range_ ) = 20 | deinterleave domrange [] [ r0 ] 21 | in 22 | { convert = convert r0 23 | , domain = domain_ 24 | , range = range_ 25 | } 26 | 27 | 28 | convert : a -> Array comparable -> Array a -> comparable -> a 29 | convert default thresholds range x = 30 | Array.get (bisectRight x thresholds Nothing) range |> Maybe.withDefault default 31 | -------------------------------------------------------------------------------- /src/Scale/Time.elm: -------------------------------------------------------------------------------- 1 | module Scale.Time exposing (scale) 2 | 3 | import DateFormat 4 | import Interpolation 5 | import Scale.Continuous as Continuous 6 | import Time 7 | import Time.Extra exposing (Interval(..)) 8 | 9 | 10 | scale zone range_ domain_ = 11 | { domain = domain_ 12 | , range = range_ 13 | , convert = Continuous.convertTransform (Time.posixToMillis >> toFloat) Interpolation.float 14 | , invert = Continuous.invertTransform (Time.posixToMillis >> toFloat) (round >> Time.millisToPosix) 15 | , ticks = ticks zone 16 | , tickFormat = tickFormat zone 17 | , nice = nice zone 18 | , rangeExtent = \_ r -> r 19 | } 20 | 21 | 22 | toTime : ( Time.Posix, Time.Posix ) -> ( Float, Float ) 23 | toTime ( a, b ) = 24 | ( Time.posixToMillis a |> toFloat, Time.posixToMillis b |> toFloat ) 25 | 26 | 27 | ticks : Time.Zone -> ( Time.Posix, Time.Posix ) -> Int -> List Time.Posix 28 | ticks zone domain count = 29 | let 30 | ( start, end ) = 31 | toTime domain 32 | 33 | target = 34 | abs (start - end) / toFloat count 35 | 36 | ( interval, step ) = 37 | findInterval target tickIntervals 38 | in 39 | Time.Extra.range interval (round step) zone (Tuple.first domain) (Tuple.second domain) 40 | 41 | 42 | tickIntervals : List ( Interval, number ) 43 | tickIntervals = 44 | [ ( Second, 1 ) 45 | , ( Second, 5 ) 46 | , ( Second, 15 ) 47 | , ( Second, 30 ) 48 | , ( Minute, 1 ) 49 | , ( Minute, 5 ) 50 | , ( Minute, 15 ) 51 | , ( Minute, 30 ) 52 | , ( Hour, 1 ) 53 | , ( Hour, 3 ) 54 | , ( Hour, 6 ) 55 | , ( Hour, 12 ) 56 | , ( Day, 1 ) 57 | , ( Day, 2 ) 58 | , ( Week, 1 ) 59 | , ( Month, 1 ) 60 | , ( Month, 3 ) 61 | , ( Year, 1 ) 62 | ] 63 | 64 | 65 | timeLength : Interval -> number 66 | timeLength interval = 67 | case interval of 68 | Millisecond -> 69 | 1 70 | 71 | Second -> 72 | 1000 73 | 74 | Minute -> 75 | 60 * 1000 76 | 77 | Hour -> 78 | 60 * 60 * 1000 79 | 80 | Day -> 81 | 24 * 60 * 60 * 1000 82 | 83 | Month -> 84 | 30 * 24 * 60 * 60 * 1000 85 | 86 | Year -> 87 | 365 * 30 * 24 * 60 * 60 * 1000 88 | 89 | Quarter -> 90 | 4 * 30 * 24 * 60 * 60 * 1000 91 | 92 | Week -> 93 | 7 * 24 * 60 * 60 * 1000 94 | 95 | _ -> 96 | 0 97 | 98 | 99 | findInterval : Float -> List ( Interval, Float ) -> ( Interval, Float ) 100 | findInterval target intervals = 101 | case intervals of 102 | [] -> 103 | ( Year, 1 ) 104 | 105 | ( interval, step ) :: ( interval_, step_ ) :: xs -> 106 | let 107 | ratio = 108 | target / (step * timeLength interval) 109 | 110 | ratio_ = 111 | (step_ * timeLength interval_) / target 112 | in 113 | if ratio < ratio_ then 114 | ( interval, step ) 115 | 116 | else 117 | findInterval target (( interval_, step_ ) :: xs) 118 | 119 | x :: _ -> 120 | x 121 | 122 | 123 | tickFormat : Time.Zone -> ( Time.Posix, Time.Posix ) -> Int -> Time.Posix -> String 124 | tickFormat zone _ _ date = 125 | let 126 | time = 127 | Time.posixToMillis date 128 | 129 | significant interval = 130 | Time.posixToMillis (Time.Extra.floor interval zone date) < time 131 | 132 | format = 133 | if significant Second then 134 | [ DateFormat.text ".", DateFormat.millisecondFixed ] 135 | 136 | else if significant Minute then 137 | [ DateFormat.text ":", DateFormat.secondFixed ] 138 | 139 | else if significant Hour then 140 | [ DateFormat.hourFixed, DateFormat.text ":", DateFormat.minuteFixed ] 141 | 142 | else if significant Day then 143 | [ DateFormat.hourFixed, DateFormat.text " ", DateFormat.amPmLowercase ] 144 | 145 | else if significant Month then 146 | [ DateFormat.dayOfMonthFixed, DateFormat.text " ", DateFormat.monthNameAbbreviated ] 147 | 148 | else if significant Year then 149 | [ DateFormat.monthNameFull ] 150 | 151 | else 152 | [ DateFormat.yearNumber ] 153 | in 154 | DateFormat.format format zone date 155 | 156 | 157 | nice : Time.Zone -> ( Time.Posix, Time.Posix ) -> Int -> ( Time.Posix, Time.Posix ) 158 | nice zone domain count = 159 | let 160 | ( start, end ) = 161 | toTime domain 162 | 163 | target = 164 | abs (start - end) / toFloat count 165 | 166 | ( interval, _ ) = 167 | findInterval target tickIntervals 168 | in 169 | ( Time.Extra.floor interval zone (Tuple.first domain), Time.Extra.ceiling interval zone (Tuple.second domain) ) 170 | -------------------------------------------------------------------------------- /src/Shape/Bump.elm: -------------------------------------------------------------------------------- 1 | module Shape.Bump exposing (bumpXCurve, bumpYCurve) 2 | 3 | import LowLevel.Command exposing (cubicCurveTo, moveTo) 4 | import SubPath exposing (SubPath) 5 | 6 | 7 | bumpXCurve : List ( Float, Float ) -> SubPath 8 | bumpXCurve points = 9 | case points of 10 | [] -> 11 | SubPath.empty 12 | 13 | first :: rest -> 14 | SubPath.with (moveTo first) 15 | [ List.foldl 16 | (\( x, y ) ( ( x0, y0 ), soFar ) -> 17 | let 18 | x1 = 19 | (x0 + x) / 2 20 | in 21 | ( ( x, y ), ( ( x1, y0 ), ( x1, y ), ( x, y ) ) :: soFar ) 22 | ) 23 | ( first, [] ) 24 | rest 25 | |> Tuple.second 26 | |> List.reverse 27 | |> cubicCurveTo 28 | ] 29 | 30 | 31 | bumpYCurve : List ( Float, Float ) -> SubPath 32 | bumpYCurve points = 33 | case points of 34 | [] -> 35 | SubPath.empty 36 | 37 | first :: rest -> 38 | SubPath.with (moveTo first) 39 | [ List.foldl 40 | (\( x, y ) ( ( x0, y0 ), soFar ) -> 41 | let 42 | y1 = 43 | (y0 + y) / 2 44 | in 45 | ( ( x, y ), ( ( x0, y1 ), ( x, y1 ), ( x, y ) ) :: soFar ) 46 | ) 47 | ( first, [] ) 48 | rest 49 | |> Tuple.second 50 | |> List.reverse 51 | |> cubicCurveTo 52 | ] 53 | 54 | 55 | 56 | {- 57 | Do we need this? Can be published later... 58 | 59 | 60 | pointRadial : Float -> Float -> ( Float, Float ) 61 | pointRadial x y = 62 | let 63 | x1 = 64 | x - pi / 2 65 | in 66 | ( y * cos x1, y * sin x1 ) 67 | 68 | 69 | bumpRadialCurve : List ( Float, Float ) -> SubPath 70 | bumpRadialCurve points = 71 | case points of 72 | [] -> 73 | SubPath.empty 74 | 75 | ( fx, fy ) :: rest -> 76 | SubPath.with (moveTo (pointRadial fx fy)) 77 | [ List.foldl 78 | (\( x, y ) ( ( x0, y0 ), soFar ) -> 79 | let 80 | y1 = 81 | (y0 + y) / 2 82 | in 83 | ( ( x, y ), ( pointRadial x0 y1, pointRadial x y1, pointRadial x y ) :: soFar ) 84 | ) 85 | ( ( fx, fy ), [] ) 86 | rest 87 | |> Tuple.second 88 | |> List.reverse 89 | |> cubicCurveTo 90 | ] 91 | -} 92 | -------------------------------------------------------------------------------- /src/Shape/Generators.elm: -------------------------------------------------------------------------------- 1 | module Shape.Generators exposing (area, line) 2 | 3 | import Path exposing (Path) 4 | import SubPath exposing (SubPath) 5 | 6 | 7 | line : 8 | (List a -> SubPath) 9 | -> List (Maybe a) 10 | -> Path 11 | line curve data = 12 | let 13 | makeCurves datum ( prev, list ) = 14 | case ( prev, datum, list ) of 15 | ( _, Nothing, l ) -> 16 | ( False, l ) 17 | 18 | ( False, Just point, l ) -> 19 | ( True, [ point ] :: l ) 20 | 21 | ( True, Just p1, ps :: l ) -> 22 | ( True, (p1 :: ps) :: l ) 23 | 24 | ( True, Just p1, l ) -> 25 | ( True, [ p1 ] :: l ) 26 | in 27 | List.map curve <| Tuple.second <| List.foldr makeCurves ( False, [] ) data 28 | 29 | 30 | area : 31 | (List ( Float, Float ) -> SubPath) 32 | -> List (Maybe ( ( Float, Float ), ( Float, Float ) )) 33 | -> Path 34 | area curve data = 35 | let 36 | makeCurves acc datum ( prev, list ) = 37 | case ( prev, datum, list ) of 38 | ( _, Nothing, l ) -> 39 | ( False, l ) 40 | 41 | ( False, Just point, l ) -> 42 | ( True, [ acc point ] :: l ) 43 | 44 | ( True, Just p1, ps :: l ) -> 45 | ( True, (acc p1 :: ps) :: l ) 46 | 47 | ( True, Just p1, l ) -> 48 | ( True, [ acc p1 ] :: l ) 49 | 50 | topLineData = 51 | List.foldr (makeCurves Tuple.first) ( False, [] ) data 52 | |> Tuple.second 53 | 54 | bottomLineData = 55 | List.foldr (makeCurves Tuple.second) ( False, [] ) data 56 | |> Tuple.second 57 | |> List.map List.reverse 58 | 59 | makeShape topline bottomline = 60 | curve topline |> SubPath.connect (curve bottomline) 61 | in 62 | List.map2 makeShape topLineData bottomLineData 63 | -------------------------------------------------------------------------------- /src/Zoom/Interpolation.elm: -------------------------------------------------------------------------------- 1 | module Zoom.Interpolation exposing (interpolate) 2 | 3 | {-| This is again one of those crazy ports from D3 modules... 4 | -} 5 | 6 | 7 | type alias View = 8 | { cx : Float 9 | , cy : Float 10 | , size : Float 11 | } 12 | 13 | 14 | exp : Float -> Float 15 | exp x = 16 | e ^ x 17 | 18 | 19 | log : Float -> Float 20 | log = 21 | logBase e 22 | 23 | 24 | epsilon2 : Float 25 | epsilon2 = 26 | 1.0e-12 27 | 28 | 29 | rho : Float 30 | rho = 31 | sqrt 2 32 | 33 | 34 | cosh : Float -> Float 35 | cosh x = 36 | let 37 | x_ = 38 | exp x 39 | in 40 | (x_ + 1 / x_) / 2 41 | 42 | 43 | sinh : Float -> Float 44 | sinh x = 45 | let 46 | x_ = 47 | exp x 48 | in 49 | (x_ - 1 / x_) / 2 50 | 51 | 52 | tanh : Float -> Float 53 | tanh x = 54 | let 55 | x_ = 56 | exp (2 * x) 57 | in 58 | (x_ - 1) / (x_ + 1) 59 | 60 | 61 | interpolate : View -> View -> ( Float, Float -> View ) 62 | interpolate a b = 63 | let 64 | dx = 65 | b.cx - a.cx 66 | 67 | dy = 68 | b.cy - a.cy 69 | 70 | d2 = 71 | dx ^ 2 + dy ^ 2 72 | in 73 | -- special case for a.cxy ≅ b.cxy 74 | if d2 < epsilon2 then 75 | let 76 | s = 77 | log (b.size / a.size) 78 | / rho 79 | in 80 | ( abs s * 1000 81 | , \t -> 82 | { cx = a.cx + t * dx 83 | , cy = a.cy + t * dy 84 | , size = a.size * exp (rho * t * s) 85 | } 86 | ) 87 | 88 | else 89 | -- general case 90 | let 91 | d1 = 92 | sqrt d2 93 | 94 | b0 = 95 | (b.size ^ 2 - (a.size ^ 2) + rho ^ 4 * d2) / (2 * a.size * rho ^ 2 * d1) 96 | 97 | b1 = 98 | (b.size ^ 2 - (a.size ^ 2) - rho ^ 4 * d2) / (2 * b.size * rho ^ 2 * d1) 99 | 100 | r0 = 101 | log (sqrt (b0 ^ 2 + 1) - b0) 102 | 103 | r1 = 104 | log (sqrt (b1 ^ 2 + 1) - b1) 105 | 106 | s_ = 107 | (r1 - r0) 108 | / rho 109 | in 110 | ( s_ * 1000 111 | , \t -> 112 | let 113 | s = 114 | t * s_ 115 | 116 | coshr0 = 117 | cosh r0 118 | 119 | u = 120 | a.size / (rho ^ 2 * d1) * (coshr0 * tanh (rho * s + r0) - sinh r0) 121 | in 122 | { cx = a.cx + u * dx 123 | , cy = a.cy + u * dy 124 | , size = a.size * coshr0 / cosh (rho * s + r0) 125 | } 126 | ) 127 | -------------------------------------------------------------------------------- /src/Zoom/Matrix.elm: -------------------------------------------------------------------------------- 1 | module Zoom.Matrix exposing (Matrix2x3, transform) 2 | 3 | 4 | type alias Matrix2x3 = 5 | ( ( Float, Float, Float ), ( Float, Float, Float ) ) 6 | 7 | 8 | transform : ( Float, Float ) -> Matrix2x3 -> ( Float, Float ) 9 | transform ( x, y ) ( ( a11, a12, a13 ), ( a21, a22, a23 ) ) = 10 | ( a11 * x + a12 * y + a13, a21 * x + a22 * y + a23 ) 11 | -------------------------------------------------------------------------------- /src/Zoom/Transform.elm: -------------------------------------------------------------------------------- 1 | module Zoom.Transform exposing (Transform, identity, invert, scale, toString, translate) 2 | 3 | 4 | type alias Transform = 5 | { k : Float 6 | , x : Float 7 | , y : Float 8 | } 9 | 10 | 11 | identity : Transform 12 | identity = 13 | Transform 1.0 0.0 0.0 14 | 15 | 16 | scale : Float -> Transform -> Transform 17 | scale k_ { k, x, y } = 18 | Transform (k * k_) x y 19 | 20 | 21 | invert : ( Float, Float ) -> Transform -> ( Float, Float ) 22 | invert ( locX, locY ) { k, x, y } = 23 | ( (locX - x) / k, (locY - y) / k ) 24 | 25 | 26 | toString : Transform -> String 27 | toString { k, x, y } = 28 | "translate(" ++ String.fromFloat x ++ "," ++ String.fromFloat y ++ ") scale(" ++ String.fromFloat k ++ ")" 29 | 30 | 31 | translate : ( Float, Float ) -> Transform -> Transform 32 | translate ( locX, locY ) { k, x, y } = 33 | Transform k (x + k * locX) (y + k * locY) 34 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | /elm-stuff/ 2 | -------------------------------------------------------------------------------- /tests/AxisTests.elm: -------------------------------------------------------------------------------- 1 | module AxisTests exposing (renderingTest) 2 | 3 | import Axis 4 | import Expect 5 | import Scale 6 | import Svg exposing (g, line, path, text, text_) 7 | import Svg.Attributes exposing (class, d, dy, fill, fontFamily, fontSize, stroke, textAnchor, transform, x, x2, y, y1, y2) 8 | import Test exposing (Test, test) 9 | 10 | 11 | renderingTest : Test 12 | renderingTest = 13 | test "it has a stable rendering" <| 14 | \() -> 15 | let 16 | expected = 17 | g [ fill "none", fontSize "10", fontFamily "sans-serif", textAnchor "end" ] 18 | [ path [ class "domain", stroke "#000", d "M-6,290.5H0.5V0.5H-6" ] [] 19 | , g [ class "tick", transform "translate(0, 290)" ] 20 | [ line [ stroke "#000", x2 "-6", y1 "0.5", y2 "0.5" ] [] 21 | , text_ [ fill "#000", x "-9", y "0.5", dy "0.32em" ] [ text "0" ] 22 | ] 23 | , g [ class "tick", transform "translate(0, 232)" ] 24 | [ line [ stroke "#000", x2 "-6", y1 "0.5", y2 "0.5" ] [] 25 | , text_ [ fill "#000", x "-9", y "0.5", dy "0.32em" ] [ text "1" ] 26 | ] 27 | , g [ class "tick", transform "translate(0, 174)" ] 28 | [ line [ stroke "#000", x2 "-6", y1 "0.5", y2 "0.5" ] [] 29 | , text_ [ fill "#000", x "-9", y "0.5", dy "0.32em" ] [ text "2" ] 30 | ] 31 | , g [ class "tick", transform "translate(0, 116)" ] 32 | [ line [ stroke "#000", x2 "-6", y1 "0.5", y2 "0.5" ] [] 33 | , text_ [ fill "#000", x "-9", y "0.5", dy "0.32em" ] [ text "3" ] 34 | ] 35 | , g [ class "tick", transform "translate(0, 58)" ] 36 | [ line [ stroke "#000", x2 "-6", y1 "0.5", y2 "0.5" ] [] 37 | , text_ [ fill "#000", x "-9", y "0.5", dy "0.32em" ] [ text "4" ] 38 | ] 39 | , g [ class "tick", transform "translate(0, 0)" ] 40 | [ line [ stroke "#000", x2 "-6", y1 "0.5", y2 "0.5" ] [] 41 | , text_ [ fill "#000", x "-9", y "0.5", dy "0.32em" ] [ text "5" ] 42 | ] 43 | ] 44 | in 45 | Axis.left [ Axis.tickCount 5 ] (Scale.linear ( 290, 0 ) ( 0, 5 )) 46 | |> Expect.equal expected 47 | -------------------------------------------------------------------------------- /tests/Color/LabTests.elm: -------------------------------------------------------------------------------- 1 | module Color.LabTests exposing (suite) 2 | 3 | import Color exposing (Color) 4 | import Color.Lab as Lab 5 | import Expect exposing (Expectation, FloatingPointTolerance(..)) 6 | import Fuzz exposing (Fuzzer, floatRange, triple) 7 | import Test exposing (Test, describe, fuzz, fuzz2, test) 8 | 9 | 10 | unit : Fuzzer Float 11 | unit = 12 | floatRange 0 1 13 | 14 | 15 | guaranteedTolerance : FloatingPointTolerance 16 | guaranteedTolerance = 17 | Absolute 0.0000000001 18 | 19 | 20 | uint8 : Fuzzer Int 21 | uint8 = 22 | Fuzz.intRange 0 255 23 | 24 | 25 | toColorFloat : Int -> Float 26 | toColorFloat n = 27 | toFloat n / 255 28 | 29 | 30 | color : Fuzzer Color 31 | color = 32 | Fuzz.map4 (\r g b a -> Color.rgba (toColorFloat r) (toColorFloat g) (toColorFloat b) a) uint8 uint8 uint8 unit 33 | 34 | 35 | suite : Test 36 | suite = 37 | describe "Color" 38 | [ describe "Color lab" 39 | [ fuzz2 (triple (floatRange 0 100) (floatRange -160 160) (floatRange -160 160)) unit "can represent Lab colors (fromLab)" <| 40 | \( l, a, b ) alpha -> 41 | Lab.fromLab { l = l, a = a, b = b, alpha = alpha } 42 | |> Lab.toLab 43 | |> Expect.all 44 | [ .l >> Expect.within (Absolute 0.001) l 45 | , .a >> Expect.within (Absolute 0.001) a 46 | , .b >> Expect.within (Absolute 0.001) b 47 | , .alpha >> Expect.within guaranteedTolerance alpha 48 | ] 49 | , test "hcl exposes the hue, chroma, luminance values" <| 50 | \() -> 51 | Color.rgb255 170 187 204 52 | |> Lab.toHcl 53 | |> expectHclEqual { hue = 252.37145234745182, chroma = 11.223567114593477, luminance = 74.96879980931759, alpha = 1 } 54 | , test "hcl converts to proper RGB" <| 55 | \() -> 56 | Lab.fromHcl { hue = 120, chroma = 30, luminance = 50, alpha = 0.4 } 57 | |> Color.toRgba 58 | |> expectRgbEqual { red = 105 / 255, green = 126 / 255, blue = 73 / 255, alpha = 0.4 } 59 | , fuzz color "can represent Hcl colors (fromHcl)" <| 60 | \col -> 61 | col 62 | |> Lab.toHcl 63 | |> Lab.fromHcl 64 | |> Color.toRgba 65 | |> expectRgbEqual (Color.toRgba col) 66 | ] 67 | ] 68 | 69 | 70 | expectHclEqual : { a | hue : Float, chroma : Float, luminance : Float, alpha : d } -> { b | hue : Float, chroma : Float, luminance : Float, alpha : d } -> Expectation 71 | expectHclEqual expected actual = 72 | if 73 | areHclCoordsEqual actual.hue expected.hue 74 | && areHclCoordsEqual actual.chroma expected.chroma 75 | && areHclCoordsEqual actual.luminance expected.luminance 76 | && expected.alpha 77 | == actual.alpha 78 | then 79 | Expect.pass 80 | 81 | else 82 | Expect.fail (Debug.toString expected ++ "\n ╷\n │ expectHclEqual\n ╵\n" ++ Debug.toString actual) 83 | 84 | 85 | expectRgbEqual : { a | red : Float, green : Float, blue : Float, alpha : d } -> { b | red : Float, green : Float, blue : Float, alpha : d } -> Expectation 86 | expectRgbEqual expected actual = 87 | if 88 | areRgbCoordsEqual actual.red expected.red 89 | && areRgbCoordsEqual actual.green expected.green 90 | && areRgbCoordsEqual actual.blue expected.blue 91 | && expected.alpha 92 | == actual.alpha 93 | then 94 | Expect.pass 95 | 96 | else 97 | Expect.fail (Debug.toString expected ++ "\n ╷\n │ expectRgbEqual\n ╵\n" ++ Debug.toString actual) 98 | 99 | 100 | areHclCoordsEqual : Float -> Float -> Bool 101 | areHclCoordsEqual expected actual = 102 | (isNaN actual && isNaN expected) || (expected - 1.0e-6 <= actual && actual <= expected + 1.0e-6) 103 | 104 | 105 | areRgbCoordsEqual : Float -> Float -> Bool 106 | areRgbCoordsEqual expected actual = 107 | round (expected * 255) == round (actual * 255) 108 | -------------------------------------------------------------------------------- /tests/ForceTests.elm: -------------------------------------------------------------------------------- 1 | module ForceTests exposing (collisionTest, entityTest, integrationTest) 2 | 3 | import Array 4 | import Expect 5 | import Force 6 | import Test exposing (Test, describe, test) 7 | 8 | 9 | runTicks iterations simulation entitites = 10 | if iterations > 0 then 11 | let 12 | ( newSim, newEnts ) = 13 | Force.tick simulation entitites 14 | in 15 | runTicks (iterations - 1) newSim newEnts 16 | 17 | else 18 | entitites 19 | 20 | 21 | collisionTest : Test 22 | collisionTest = 23 | describe "collision" 24 | [ test "colides nodes" <| 25 | \() -> 26 | let 27 | nodes = 28 | [ (), (), () ] 29 | |> List.indexedMap Force.entity 30 | 31 | simulation = 32 | Force.simulation [ Force.collision 100 [ 0, 1, 2 ] ] 33 | in 34 | runTicks 10 simulation nodes 35 | |> Expect.equal 36 | [ { id = 0, value = (), vx = 0.677346615710878, vy = 0.26976816231064366, x = 174.08616723117228, y = 66.51743051995629 } 37 | , { id = 1, value = (), vx = -0.6981367135383595, vy = 0.7334733779701496, x = -181.17225489926076, y = 189.1274524695976 } 38 | , { id = 2, value = (), vx = 0.020790097827481858, vy = -1.0032415402807933, x = 6.50859004343442, y = -263.1226973950056 } 39 | ] 40 | 41 | -- [ { id = 0, x = 174.08616723117228, y = 66.51743051995625, vy = 0.26976816231064354, vx = 0.677346615710878, value = () } 42 | -- , { id = 1, x = -139.73606544743998, y = 95.69860503079263, vy = 0.3545632444404687, vx = -0.5300880593105067, value = () } 43 | -- , { id = 2, x = -34.9275994083864, y = -169.69384995620052, vy = -0.6243314067511122, vx = -0.1472585564003713, value = () } 44 | -- ] 45 | ] 46 | 47 | 48 | entityTest : Test 49 | entityTest = 50 | describe "entity" 51 | [ test "computes a petal like layout based on indexes" <| 52 | \() -> 53 | [ (), (), () ] 54 | |> List.indexedMap Force.entity 55 | |> Expect.equal 56 | [ { x = sqrt 0.5 * 10 * cos 0 57 | , y = sqrt 0.5 * 10 * sin 0 58 | , vx = 0 59 | , vy = 0 60 | , value = () 61 | , id = 0 62 | } 63 | , { x = sqrt 1.5 * 10 * cos (pi * (3 - sqrt 5)) 64 | , y = sqrt 1.5 * 10 * sin (pi * (3 - sqrt 5)) 65 | , vx = 0 66 | , vy = 0 67 | , value = () 68 | , id = 1 69 | } 70 | , { x = sqrt 2.5 * 10 * cos (2 * pi * (3 - sqrt 5)) 71 | , y = sqrt 2.5 * 10 * sin (2 * pi * (3 - sqrt 5)) 72 | , vx = 0 73 | , vy = 0 74 | , value = () 75 | , id = 2 76 | } 77 | ] 78 | ] 79 | 80 | 81 | integrationTest : Test 82 | integrationTest = 83 | test "computeSimulation with a few forces computes a triangle" <| 84 | \() -> 85 | let 86 | entities = 87 | List.indexedMap Force.entity [ (), (), () ] 88 | 89 | forces = 90 | [ Force.manyBody <| List.map .id entities 91 | , Force.links [ ( 0, 1 ), ( 1, 2 ), ( 2, 0 ) ] 92 | , Force.center 0.0 0.0 93 | ] 94 | 95 | simulation = 96 | Force.simulation forces 97 | 98 | forceMaybe m = 99 | case m of 100 | Just x -> 101 | x 102 | 103 | Nothing -> 104 | Force.entity 0 () 105 | 106 | dist n1 n2 = 107 | sqrt ((n1.x - n2.x) ^ 2 + (n1.y - n2.y) ^ 2) 108 | 109 | computeDistances array = 110 | let 111 | n0 = 112 | forceMaybe <| Array.get 0 array 113 | 114 | n1 = 115 | forceMaybe <| Array.get 1 array 116 | 117 | n2 = 118 | forceMaybe <| Array.get 2 array 119 | in 120 | [ dist n0 n1, dist n1 n2, dist n2 n0 ] 121 | in 122 | Force.computeSimulation simulation entities 123 | |> Array.fromList 124 | |> computeDistances 125 | |> List.map round 126 | |> Expect.equal [ 34, 34, 34 ] 127 | -------------------------------------------------------------------------------- /tests/Helper.elm: -------------------------------------------------------------------------------- 1 | module Helper exposing (assert, atLeastFloat, atMostFloat, expectAll, expectAny, expectMember, isBetween, pathEqual) 2 | 3 | import Expect exposing (Expectation, FloatingPointTolerance(..)) 4 | import Path exposing (Path) 5 | import Regex 6 | import Test.Runner 7 | 8 | 9 | numRegex : Regex.Regex 10 | numRegex = 11 | Regex.fromString "[-+]?(?:\\d+\\.\\d+|\\d+\\.|\\.\\d+|\\d+)(?:[eE][-]?\\d+)?" 12 | |> Maybe.withDefault Regex.never 13 | 14 | 15 | pathEqual : String -> Path -> Expect.Expectation 16 | pathEqual str path = 17 | let 18 | format s = 19 | if abs (s - toFloat (round s)) < 1.0e-6 then 20 | String.fromInt (round s) 21 | 22 | else 23 | String.fromFloat (toFloat (round (s * 1.0e6)) / 1.0e6) 24 | 25 | normalize = 26 | Regex.replace numRegex (\{ match } -> format <| Maybe.withDefault 0 <| String.toFloat match) 27 | >> Path.parse 28 | 29 | pathStr = 30 | normalize (Path.toString path) 31 | 32 | normStr = 33 | normalize str 34 | in 35 | case ( normStr, pathStr ) of 36 | ( Ok normParse, Ok pathParse ) -> 37 | Expect.equal (Path.toString normParse) (Path.toString pathParse) 38 | 39 | ( Err a, Err b ) -> 40 | Expect.fail ("Parsing both strings failed with:" ++ Debug.toString ( a, b )) 41 | 42 | ( _, Err e ) -> 43 | Expect.fail ("Parsing the expected failed with:" ++ Debug.toString e) 44 | 45 | ( Err e, _ ) -> 46 | Expect.fail ("Parsing the model failed with:" ++ Debug.toString e) 47 | 48 | 49 | precision : number 50 | precision = 51 | 100000 52 | 53 | 54 | isBetween : ( Float, Float ) -> Float -> Expectation 55 | isBetween ( b, c ) a = 56 | let 57 | mi = 58 | min b c 59 | 60 | ma = 61 | max b c 62 | in 63 | if a >= mi && a <= ma then 64 | Expect.pass 65 | 66 | else 67 | let 68 | withinExpAMin = 69 | Expect.within (Absolute (1 / precision)) a mi 70 | 71 | withinExpAMax = 72 | Expect.within (Absolute (1 / precision)) a ma 73 | in 74 | if withinExpAMin == Expect.pass || withinExpAMax == Expect.pass then 75 | Expect.pass 76 | 77 | else 78 | Expect.fail (Debug.toString a ++ "\n╷\n| isBetween\n╵\n" ++ Debug.toString ( mi, ma )) 79 | 80 | 81 | atMostFloat : Float -> Float -> Expectation 82 | atMostFloat a b = 83 | compareOrEqual (<=) b a "atMost" 84 | 85 | 86 | atLeastFloat : Float -> Float -> Expectation 87 | atLeastFloat a b = 88 | compareOrEqual (>=) b a "atLeast" 89 | 90 | 91 | compareOrEqual : (Float -> Float -> Bool) -> Float -> Float -> String -> Expectation 92 | compareOrEqual compareFun a b compStr = 93 | if compareFun a b then 94 | Expect.pass 95 | 96 | else 97 | let 98 | withinExp = 99 | Expect.within (Absolute (1 / precision)) a b 100 | in 101 | if withinExp == Expect.pass then 102 | Expect.pass 103 | 104 | else 105 | Expect.fail (Debug.toString a ++ "\n╷\n|" ++ compStr ++ "\n╵\n" ++ Debug.toString b) 106 | 107 | 108 | expectAll : List Expectation -> Expectation 109 | expectAll expectations = 110 | Expect.all (List.map always expectations) () 111 | 112 | 113 | expectAny : List Expectation -> Expectation 114 | expectAny expectations = 115 | let 116 | failures = 117 | List.filterMap Test.Runner.getFailureReason expectations 118 | in 119 | if List.length failures == List.length expectations then 120 | Expect.fail <| "Expected at least one of the following to pass:\n" ++ (String.join "\n" <| List.map (.reason >> Debug.toString) failures) 121 | 122 | else 123 | Expect.pass 124 | 125 | 126 | expectMember : List a -> a -> Expectation 127 | expectMember list item = 128 | if List.member item list then 129 | Expect.pass 130 | 131 | else 132 | Expect.fail ("Expected " ++ Debug.toString item ++ " to be a member of " ++ Debug.toString list) 133 | 134 | 135 | assert : String -> Bool -> Expectation 136 | assert str bool = 137 | if bool then 138 | Expect.pass 139 | 140 | else 141 | Expect.fail str 142 | -------------------------------------------------------------------------------- /tests/HierarchyTests.elm: -------------------------------------------------------------------------------- 1 | module HierarchyTests exposing (fuzzTree) 2 | 3 | import Fuzz exposing (Fuzzer) 4 | import Tree exposing (Tree) 5 | 6 | 7 | fuzzTree : Fuzzer child -> Fuzzer (Tree child) 8 | fuzzTree child = 9 | let 10 | go depth branch = 11 | if depth > 0 then 12 | Fuzz.map2 13 | (\label children -> 14 | Tree.tree label children 15 | ) 16 | child 17 | (Fuzz.listOfLengthBetween 0 branch (go (depth - 1) branch)) 18 | 19 | else 20 | Fuzz.map 21 | (\label -> 22 | Tree.singleton label 23 | ) 24 | child 25 | in 26 | go 5 5 27 | -------------------------------------------------------------------------------- /tests/Histogram/ArrayTests.elm: -------------------------------------------------------------------------------- 1 | module Histogram.ArrayTests exposing (bisectRight) 2 | 3 | import Array 4 | import Expect 5 | import Fuzz exposing (int, list) 6 | import Histogram.Array as Array 7 | import Test exposing (Test, describe, fuzz2) 8 | 9 | 10 | bisectRight : Test 11 | bisectRight = 12 | describe "bisectRight" 13 | [ fuzz2 int (list int) "returns the correct insertion point for maintaining sort" <| 14 | \item list -> 15 | let 16 | sorted = 17 | list 18 | |> List.sort 19 | |> Array.fromList 20 | 21 | bisection = 22 | Array.bisectRight item sorted Nothing 23 | 24 | before = 25 | Array.slice 0 bisection sorted |> Array.toList 26 | 27 | after = 28 | Array.slice bisection (Array.length sorted) sorted |> Array.toList 29 | 30 | allSmaller i = 31 | if List.all (\a -> item < a) after then 32 | Expect.pass 33 | 34 | else 35 | Expect.fail ("Expected " ++ String.fromInt i ++ " to be smaller than " ++ Debug.toString after) 36 | 37 | allGreater i = 38 | if List.all (\a -> item >= a) before then 39 | Expect.pass 40 | 41 | else 42 | Expect.fail ("Expected " ++ String.fromInt i ++ " to be greater than " ++ Debug.toString before) 43 | in 44 | Expect.all [ allGreater, allSmaller ] item 45 | ] 46 | -------------------------------------------------------------------------------- /tests/HistogramTests.elm: -------------------------------------------------------------------------------- 1 | module HistogramTests exposing (histogram) 2 | 3 | import Expect 4 | import Fuzz exposing (float, list) 5 | import Helper exposing (assert) 6 | import Histogram 7 | import Test exposing (Test, describe, fuzz, fuzz2) 8 | 9 | 10 | histogram : Test 11 | histogram = 12 | describe "histogram" 13 | [ fuzz (list float) "keeps all the data" <| 14 | \list -> 15 | Histogram.float 16 | |> Histogram.compute list 17 | |> List.foldl (\item sum -> sum + item.length) 0 18 | |> Expect.equal (List.length list) 19 | , fuzz2 Fuzz.niceFloat (list Fuzz.niceFloat) "computes continous buckets" <| 20 | \head tail -> 21 | let 22 | minI = 23 | Maybe.withDefault head <| List.minimum (head :: tail) 24 | in 25 | Histogram.float 26 | |> Histogram.compute (head :: tail) 27 | |> List.foldl (\item ( fail, minV ) -> ( fail && item.x0 == minV, item.x1 )) ( True, minI ) 28 | |> Tuple.first 29 | |> assert "Expected ranges to be continous" 30 | ] 31 | -------------------------------------------------------------------------------- /tests/Scale/BandTests.elm: -------------------------------------------------------------------------------- 1 | module Scale.BandTests exposing (band) 2 | 3 | import Expect 4 | import Helper exposing (assert, expectAll) 5 | import Scale exposing (defaultBandConfig) 6 | import Test exposing (Test, describe, test) 7 | 8 | 9 | band : Test 10 | band = 11 | describe "band" 12 | [ test "convert a value in the domain returns a a nicely computed value in the range" <| 13 | \() -> 14 | let 15 | scale = 16 | Scale.band defaultBandConfig ( 0, 960 ) [ "foo", "bar" ] 17 | in 18 | expectAll 19 | [ Scale.convert scale "foo" 20 | |> Expect.equal 0 21 | , Scale.convert scale "bar" 22 | |> Expect.equal 480 23 | ] 24 | , test "returns NaN for values outside the domain" <| 25 | \() -> 26 | let 27 | scale = 28 | Scale.band defaultBandConfig ( 0, 960 ) [ "foo", "bar" ] 29 | in 30 | expectAll 31 | [ Scale.convert scale "baz" 32 | |> isNaN 33 | >> assert "isNan" 34 | ] 35 | , test "range values can be descending" <| 36 | \() -> 37 | let 38 | domain = 39 | [ "a", "b", "c" ] 40 | 41 | scale = 42 | Scale.band defaultBandConfig ( 120, 0 ) domain 43 | in 44 | expectAll 45 | [ domain 46 | |> List.map (Scale.convert scale) 47 | |> Expect.equal [ 80, 40, 0 ] 48 | , Scale.bandwidth scale 49 | |> Expect.equal 40 50 | ] 51 | ] 52 | -------------------------------------------------------------------------------- /tests/Scale/DivergingTests.elm: -------------------------------------------------------------------------------- 1 | module Scale.DivergingTests exposing (diverging, divergingLog) 2 | 3 | import Expect exposing (FloatingPointTolerance(..)) 4 | import Fuzz 5 | import Scale 6 | import Test exposing (Test, describe, fuzz, test) 7 | 8 | 9 | convert val scale = 10 | Scale.convert scale val 11 | 12 | 13 | diverging : Test 14 | diverging = 15 | describe "Scale.diverging" 16 | [ fuzz Fuzz.niceFloat "default-ish values work like identity" <| 17 | \s -> 18 | Scale.convert (Scale.diverging identity ( 0, 0.5, 1 )) s 19 | |> Expect.within (Absolute 0.00001) s 20 | , test "works with a custom zero center domain" <| 21 | \() -> 22 | Scale.diverging identity ( -1.2, 0, 2.4 ) 23 | |> Expect.all 24 | [ convert -1.2 >> Expect.within (Absolute 0.00001) 0 25 | , convert 0.6 >> Expect.within (Absolute 0.00001) 0.625 26 | , convert 2.4 >> Expect.within (Absolute 0.00001) 1 27 | ] 28 | , test "handles a degenerate domain x0 = x1" <| 29 | \() -> 30 | Scale.diverging identity ( 2, 2, 3 ) 31 | |> Expect.all 32 | [ convert -1.2 >> Expect.within (Absolute 0.00001) 0.5 33 | , convert 0.6 >> Expect.within (Absolute 0.00001) 0.5 34 | , convert 2.4 >> Expect.within (Absolute 0.00001) 0.7 35 | ] 36 | , test "handles a degenerate domain x1 = x2" <| 37 | \() -> 38 | Scale.diverging identity ( 1, 2, 2 ) 39 | |> Expect.all 40 | [ convert -1 >> Expect.within (Absolute 0.00001) -1 41 | , convert 0.5 >> Expect.within (Absolute 0.00001) -0.25 42 | , convert 2.4 >> Expect.within (Absolute 0.00001) 0.5 43 | ] 44 | , test "handles a degenerate domain x0 = x1 = x2" <| 45 | \() -> 46 | Scale.diverging identity ( 2, 2, 2 ) 47 | |> Expect.all 48 | [ convert -1 >> Expect.within (Absolute 0.00001) 0.5 49 | , convert 0.5 >> Expect.within (Absolute 0.00001) 0.5 50 | , convert 2.4 >> Expect.within (Absolute 0.00001) 0.5 51 | ] 52 | , test "handles a descending domain" <| 53 | \() -> 54 | Scale.diverging identity ( 4, 2, 1 ) 55 | |> Expect.all 56 | [ convert 1.2 >> Expect.within (Absolute 0.00001) 0.9 57 | , convert 2.0 >> Expect.within (Absolute 0.00001) 0.5 58 | , convert 3.0 >> Expect.within (Absolute 0.00001) 0.25 59 | ] 60 | , test "works with a different interpolator" <| 61 | \() -> 62 | Scale.diverging (\t -> t * 2) ( 0, 0.5, 1 ) 63 | |> Expect.all 64 | [ convert -0.5 >> Expect.within (Absolute 0.00001) -1 65 | , convert 0 >> Expect.within (Absolute 0.00001) 0 66 | , convert 0.5 >> Expect.within (Absolute 0.00001) 1 67 | ] 68 | ] 69 | 70 | 71 | divergingLog : Test 72 | divergingLog = 73 | describe "Scale.divergingLog" 74 | [ test "handles a descending domain" <| 75 | \() -> 76 | Scale.divergingLog e identity ( 3, 2, 1 ) 77 | |> Expect.all 78 | [ convert 1.2 >> Expect.within (Absolute 0.00001) (1 - 0.1315172029168969) 79 | , convert 2.0 >> Expect.within (Absolute 0.00001) (1 - 0.5) 80 | , convert 2.8 >> Expect.within (Absolute 0.00001) (1 - 0.9149213210862197) 81 | ] 82 | , test "handles a descending negative domain" <| 83 | \() -> 84 | Scale.divergingLog e identity ( -1, -2, -3 ) 85 | |> Expect.all 86 | [ convert -1.2 >> Expect.within (Absolute 0.00001) 0.1315172029168969 87 | , convert -2.0 >> Expect.within (Absolute 0.00001) 0.5 88 | , convert -2.8 >> Expect.within (Absolute 0.00001) 0.9149213210862197 89 | ] 90 | ] 91 | -------------------------------------------------------------------------------- /tests/Scale/OrdinalTests.elm: -------------------------------------------------------------------------------- 1 | module Scale.OrdinalTests exposing (convert) 2 | 3 | import Expect 4 | import Fuzz exposing (Fuzzer, int, list, string) 5 | import Helper exposing (expectMember) 6 | import Scale 7 | import Test exposing (Test, describe, fuzz2, test) 8 | 9 | 10 | elementInList : Fuzzer a -> Fuzzer ( a, List a ) 11 | elementInList fuzzer = 12 | Fuzz.map3 (\prefix item postfix -> ( item, List.concat [ prefix, [ item ], postfix ] )) (list fuzzer) fuzzer (list fuzzer) 13 | 14 | 15 | nonEmptyList : Fuzzer a -> Fuzzer (List a) 16 | nonEmptyList fuzzer = 17 | Fuzz.map2 (\head tail -> head :: tail) fuzzer (list fuzzer) 18 | 19 | 20 | convert : Test 21 | convert = 22 | describe "convert" 23 | [ test "convert a value in the domain returns a corresponding value in the range" <| 24 | \() -> 25 | let 26 | scale = 27 | Scale.ordinal [ "a", "b" ] [ 1, 2, 3 ] 28 | in 29 | Scale.convert scale 1 30 | |> Expect.equal (Just "a") 31 | , test "convert a value in the domain returns a corresponding value in a wrapped range" <| 32 | \() -> 33 | let 34 | scale = 35 | Scale.ordinal [ "a", "b" ] [ 1, 2, 3 ] 36 | in 37 | Scale.convert scale 3 38 | |> Expect.equal (Just "a") 39 | , fuzz2 (elementInList int) (nonEmptyList string) "converted value is a valid domain" <| 40 | \( value, domain ) range -> 41 | let 42 | scale = 43 | Scale.ordinal range domain 44 | in 45 | Scale.convert scale value 46 | |> Maybe.map (expectMember range) 47 | |> Maybe.withDefault (Expect.fail "was nothing") 48 | , fuzz2 (list int) int "if range is empty returns Nothing" <| 49 | \domain value -> 50 | let 51 | scale = 52 | Scale.ordinal [] domain 53 | in 54 | Scale.convert scale value 55 | |> Expect.equal Nothing 56 | ] 57 | -------------------------------------------------------------------------------- /tests/Scale/QuantileTests.elm: -------------------------------------------------------------------------------- 1 | module Scale.QuantileTests exposing (all) 2 | 3 | import Expect 4 | import Fuzz exposing (Fuzzer, list, pair) 5 | import Scale 6 | import Test exposing (Test, describe, fuzz, test) 7 | 8 | 9 | type Data 10 | = A 11 | | B 12 | | C 13 | 14 | 15 | datum : Fuzzer Data 16 | datum = 17 | Fuzz.oneOf [ Fuzz.constant A, Fuzz.constant B, Fuzz.constant C ] 18 | 19 | 20 | randomRange : Fuzzer ( Data, List Data ) 21 | randomRange = 22 | pair datum (list datum) 23 | 24 | 25 | all : Test 26 | all = 27 | describe "Scale.quantile" 28 | [ test "uses the R-7 algorithm to compute quantiles" <| 29 | \() -> 30 | Scale.quantile ( 0, [ 1, 2, 3 ] ) [ 3, 6, 7, 8, 8, 10, 13, 15, 16, 20 ] 31 | |> Expect.all 32 | [ \scale -> List.map (Scale.convert scale) [ 3, 6, 6.9, 7, 7.1 ] |> Expect.equalLists [ 0, 0, 0, 0, 0 ] 33 | , \scale -> List.map (Scale.convert scale) [ 8, 8.9 ] |> Expect.equalLists [ 1, 1 ] 34 | , \scale -> List.map (Scale.convert scale) [ 9, 9.1, 10, 13 ] |> Expect.equalLists [ 2, 2, 2, 2 ] 35 | , \scale -> List.map (Scale.convert scale) [ 14.9, 15, 15.1, 16, 20 ] |> Expect.equalLists [ 3, 3, 3, 3, 3 ] 36 | ] 37 | , test "Scale.quantiles returns the inner quantiles" <| 38 | \() -> 39 | Scale.quantile ( 0, [ 1, 2, 3 ] ) [ 3, 6, 7, 8, 8, 10, 13, 15, 16, 20 ] 40 | |> Scale.quantiles 41 | |> Expect.equalLists [ 7.25, 9, 14.5 ] 42 | , fuzz randomRange "the cardinality of the range determines the number of quanitles" <| 43 | \range -> 44 | Scale.quantile range [ 3, 6, 7, 8, 8, 10, 13, 15, 16, 20 ] 45 | |> Scale.quantiles 46 | |> List.length 47 | |> Expect.equal (List.length (Tuple.second range)) 48 | , test "invertExtent maps the range to the domain" <| 49 | \() -> 50 | Scale.quantile ( A, [ B ] ) [ 3, 6, 7, 8, 8, 10, 13, 15, 16, 20 ] 51 | |> Expect.all 52 | [ \scale -> Scale.invertExtent scale A |> Expect.equal (Just ( 3, 9 )) 53 | , \scale -> Scale.invertExtent scale B |> Expect.equal (Just ( 9, 20 )) 54 | , \scale -> Scale.invertExtent scale C |> Expect.equal Nothing 55 | ] 56 | , test "invertExtent returns the first match if duplicate values exist in the range" <| 57 | \() -> 58 | Scale.quantile ( A, [ B, C, A ] ) [ 3, 6, 7, 8, 8, 10, 13, 15, 16, 20 ] 59 | |> Expect.all 60 | [ \scale -> Scale.invertExtent scale A |> Expect.equal (Just ( 3, 7.25 )) 61 | , \scale -> Scale.invertExtent scale B |> Expect.equal (Just ( 7.25, 9 )) 62 | , \scale -> Scale.invertExtent scale C |> Expect.equal (Just ( 9, 14.5 )) 63 | ] 64 | ] 65 | -------------------------------------------------------------------------------- /tests/Scale/QuantizeTests.elm: -------------------------------------------------------------------------------- 1 | module Scale.QuantizeTests exposing (all) 2 | 3 | import Expect 4 | import Helper exposing (expectAll) 5 | import Scale 6 | import Test exposing (Test, describe, test) 7 | 8 | 9 | type Data 10 | = A 11 | | B 12 | | C 13 | 14 | 15 | all : Test 16 | all = 17 | describe "Scale.quantize" 18 | [ test "convert maps a number to a discrete value in the range" <| 19 | \() -> 20 | let 21 | convert = 22 | Scale.convert (Scale.quantize ( 0, [ 1, 2 ] ) ( 0, 1 )) 23 | in 24 | expectAll 25 | [ convert 0.0 |> Expect.equal 0 26 | , convert 0.2 |> Expect.equal 0 27 | , convert 0.4 |> Expect.equal 1 28 | , convert 0.6 |> Expect.equal 1 29 | , convert 0.8 |> Expect.equal 2 30 | , convert 1.0 |> Expect.equal 2 31 | ] 32 | , test "clamps input values to the domain" <| 33 | \() -> 34 | let 35 | convert = 36 | Scale.convert (Scale.quantize ( A, [ B, C ] ) ( 0, 1 )) 37 | in 38 | expectAll 39 | [ convert -0.5 |> Expect.equal A 40 | , convert 1.5 |> Expect.equal C 41 | ] 42 | , test "invertExtent maps a value in the range to a domain extent" <| 43 | \() -> 44 | let 45 | invertExtent = 46 | Scale.invertExtent (Scale.quantize ( 0, [ 1, 2, 3 ] ) ( 0, 1 )) 47 | in 48 | expectAll 49 | [ invertExtent 0 |> Expect.equal (Just ( 0.0, 0.25 )) 50 | , invertExtent 1 |> Expect.equal (Just ( 0.25, 0.5 )) 51 | , invertExtent 2 |> Expect.equal (Just ( 0.5, 0.75 )) 52 | , invertExtent 3 |> Expect.equal (Just ( 0.75, 1.0 )) 53 | ] 54 | , test "rangeExtent returns the first and last value of the list" <| 55 | \() -> 56 | let 57 | rangeExtent range = 58 | Scale.rangeExtent (Scale.quantize range ( 0, 1 )) 59 | in 60 | expectAll 61 | [ rangeExtent ( A, [ B, C ] ) |> Expect.equal ( A, C ) 62 | , rangeExtent ( A, [ B ] ) |> Expect.equal ( A, B ) 63 | , rangeExtent ( A, [] ) |> Expect.equal ( A, A ) 64 | , rangeExtent ( 0, [ 1, 2, 3, 4 ] ) |> Expect.equal ( 0, 4 ) 65 | ] 66 | ] 67 | -------------------------------------------------------------------------------- /tests/Scale/ThresholdTests.elm: -------------------------------------------------------------------------------- 1 | module Scale.ThresholdTests exposing (all) 2 | 3 | import Expect 4 | import Scale 5 | import Test exposing (Test, describe, test) 6 | 7 | 8 | type Data 9 | = A 10 | | B 11 | | C 12 | 13 | 14 | convert val scale = 15 | Scale.convert scale val 16 | 17 | 18 | all : Test 19 | all = 20 | describe "Scale.threshold" 21 | [ test "maps a number to a discrete value in the range" <| 22 | \() -> 23 | Scale.threshold ( A, [ ( 1 / 3, B ), ( 2 / 3, C ) ] ) 24 | |> Expect.all 25 | [ convert 0.0 >> Expect.equal A 26 | , convert 0.2 >> Expect.equal A 27 | , convert 0.4 >> Expect.equal B 28 | , convert 0.6 >> Expect.equal B 29 | , convert 0.8 >> Expect.equal C 30 | , convert 1.0 >> Expect.equal C 31 | ] 32 | , test "supports arbitrary orderable ranges" <| 33 | \() -> 34 | Scale.threshold ( 0, [ ( "10", 1 ), ( "2", 2 ) ] ) 35 | |> Expect.all 36 | [ convert "0" >> Expect.equal 0 37 | , convert "12" >> Expect.equal 1 38 | , convert "3" >> Expect.equal 2 39 | ] 40 | ] 41 | -------------------------------------------------------------------------------- /tests/Shape/StackTests.elm: -------------------------------------------------------------------------------- 1 | module Shape.StackTests exposing (offset) 2 | 3 | import Expect 4 | import Shape 5 | import Test exposing (Test, describe, test) 6 | 7 | 8 | series3x3 : List (List ( Float, Float )) 9 | series3x3 = 10 | [ [ ( 0, 1 ), ( 0, 2 ), ( 0, 1 ) ] 11 | , [ ( 0, 3 ), ( 0, 4 ), ( 0, 2 ) ] 12 | , [ ( 0, 5 ), ( 0, 2 ), ( 0, 4 ) ] 13 | ] 14 | 15 | 16 | series4x4 : List (List ( Float, Float )) 17 | series4x4 = 18 | [ [ ( 0, 1 ), ( 0, 2 ), ( 0, 1 ), ( 1, 0 ) ] 19 | , [ ( 0, 3 ), ( 0, 4 ), ( 0, 2 ), ( 2, 0 ) ] 20 | , [ ( 0, 5 ), ( 0, 2 ), ( 0, 4 ), ( 4, 0 ) ] 21 | , [ ( 1, 3 ), ( 6, 3 ), ( 1, 5 ), ( 5, 1 ) ] 22 | ] 23 | 24 | 25 | offset : Test 26 | offset = 27 | describe "stack offset" 28 | [ test "stackOffsetNone produces correct result " <| 29 | \_ -> 30 | let 31 | expected = 32 | [ [ ( 0, 1 ), ( 0, 2 ), ( 0, 1 ) ] 33 | , [ ( 1, 4 ), ( 2, 6 ), ( 1, 3 ) ] 34 | , [ ( 4, 9 ), ( 6, 8 ), ( 3, 7 ) ] 35 | ] 36 | in 37 | Shape.stackOffsetNone series3x3 38 | |> Expect.equal expected 39 | , test "stackOffsetSilhouette produces correct result " <| 40 | \_ -> 41 | let 42 | expected = 43 | [ [ ( -4.5, -3.5 ), ( -4, -2 ), ( -3.5, -2.5 ) ] 44 | , [ ( -3.5, -0.5 ), ( -2, 2 ), ( -2.5, -0.5 ) ] 45 | , [ ( -0.5, 4.5 ), ( 2, 4 ), ( -0.5, 3.5 ) ] 46 | ] 47 | in 48 | Shape.stackOffsetSilhouette series3x3 49 | |> Expect.equal expected 50 | , test "stackOffsetWiggle prodices correct result " <| 51 | \_ -> 52 | let 53 | expected = 54 | [ [ ( 0, 1 ), ( -1, 1 ), ( 0.7857142857142858, 1.7857142857142858 ) ] 55 | , [ ( 1, 4 ), ( 1, 5 ), ( 1.7857142857142858, 3.7857142857142856 ) ] 56 | , [ ( 4, 9 ), ( 5, 7 ), ( 3.7857142857142856, 7.785714285714286 ) ] 57 | ] 58 | in 59 | Shape.stackOffsetWiggle series3x3 60 | |> Expect.equal expected 61 | , test "stackOffsetWiggle prodices correct result for 4 x 4 " <| 62 | \_ -> 63 | let 64 | expected = 65 | [ [ ( 0, 1 ), ( -0.45454545454545453, 1.5454545454545454 ), ( 0.5871212121212122, 1.5871212121212122 ), ( 9.587121212121213, 9.587121212121213 ) ] 66 | , [ ( 1, 4 ), ( 1.5454545454545454, 5.545454545454545 ), ( 1.5871212121212122, 3.587121212121212 ), ( 9.587121212121213, 9.587121212121213 ) ] 67 | , [ ( 4, 9 ), ( 5.545454545454545, 7.545454545454545 ), ( 3.587121212121212, 7.587121212121212 ), ( 9.587121212121213, 9.587121212121213 ) ] 68 | , [ ( 9, 12 ), ( 7.545454545454545, 10.545454545454545 ), ( 7.587121212121212, 12.587121212121211 ), ( 9.587121212121213, 10.587121212121213 ) ] 69 | ] 70 | in 71 | Shape.stackOffsetWiggle series4x4 72 | |> Expect.equal expected 73 | , test "stackOffsetSilhouette produces correct result for 4 x 4 " <| 74 | \_ -> 75 | let 76 | expected = 77 | [ [ ( -6, -5 ), ( -5.5, -3.5 ), ( -6, -5 ), ( -0.5, -0.5 ) ] 78 | , [ ( -5, -2 ), ( -3.5, 0.5 ), ( -5, -3 ), ( -0.5, -0.5 ) ] 79 | , [ ( -2, 3 ), ( 0.5, 2.5 ), ( -3, 1 ), ( -0.5, -0.5 ) ] 80 | , [ ( 3, 6 ), ( 2.5, 5.5 ), ( 1, 6 ), ( -0.5, 0.5 ) ] 81 | ] 82 | in 83 | Shape.stackOffsetSilhouette series4x4 84 | |> Expect.equal expected 85 | , test "stackOffsetExpand produces identity when all values are in [0,1]" <| 86 | \_ -> 87 | let 88 | expected = 89 | [ [ ( 0, 0.1 ), ( 0, 0.5 ) ] 90 | , [ ( 0.1, 0.5 ), ( 0.5, 0.7 ) ] 91 | , [ ( 0.5, 1.0 ), ( 0.7, 1.0 ) ] 92 | ] 93 | in 94 | Shape.stackOffsetExpand expected 95 | |> Expect.equal expected 96 | , test "stackOffsetExpand produces correct result for 4 x 4 " <| 97 | \_ -> 98 | let 99 | expected = 100 | [ [ ( 0, 0.09090909090909091 ), ( 0, 0.18181818181818182 ), ( 0, 0.09090909090909091 ), ( 0, 0.09090909090909091 ) ], [ ( 0.09090909090909091, 0.36363636363636365 ), ( 0.18181818181818182, 0.5454545454545454 ), ( 0.09090909090909091, 0.2727272727272727 ), ( 0.09090909090909091, 0.2727272727272727 ) ], [ ( 0.36363636363636365, 0.8181818181818181 ), ( 0.5454545454545454, 0.7272727272727273 ), ( 0.2727272727272727, 0.6363636363636364 ), ( 0.2727272727272727, 0.6363636363636364 ) ], [ ( 0.8181818181818181, 1 ), ( 0.7272727272727273, 1 ), ( 0.6363636363636364, 1 ), ( 0.6363636363636364, 1 ) ] ] 101 | in 102 | Shape.stackOffsetExpand series4x4 103 | |> Expect.equal expected 104 | , test "stackOffsetDiverging produces correct result for 4 x 4 " <| 105 | \_ -> 106 | let 107 | expected = 108 | [ [ ( 0, 1 ), ( 0, 2 ), ( 0, 1 ), ( -1, 0 ) ] 109 | , [ ( 1, 4 ), ( 2, 6 ), ( 1, 3 ), ( -3, -1 ) ] 110 | , [ ( 4, 9 ), ( 6, 8 ), ( 3, 7 ), ( -7, -3 ) ] 111 | , [ ( 9, 11 ), ( -3, 0 ), ( 7, 11 ), ( -11, -7 ) ] 112 | ] 113 | in 114 | Shape.stackOffsetDiverging series4x4 115 | |> Expect.equal expected 116 | ] 117 | -------------------------------------------------------------------------------- /tests/ZoomTest.elm: -------------------------------------------------------------------------------- 1 | module ZoomTest exposing (interpolateTest) 2 | 3 | import Expect 4 | import Test exposing (Test, test) 5 | import Zoom.Interpolation exposing (interpolate) 6 | 7 | 8 | interpolateTest : Test 9 | interpolateTest = 10 | test "interpolateZoom(a, b) handles nearly-coincident points" <| 11 | \() -> 12 | let 13 | a = 14 | { cx = 324.68721096803614, cy = 59.43501602433761, size = 1.8827137399562621 } 15 | 16 | b = 17 | { cx = 324.6872108946794, cy = 59.43501601062763, size = 7.399052110984391 } 18 | 19 | ( _, inter ) = 20 | interpolate a b 21 | in 22 | inter 0.5 23 | |> Expect.equal { cx = 324.68721093135775, cy = 59.43501601748262, size = 3.7323313186268305 } 24 | -------------------------------------------------------------------------------- /tests/elm-verify-examples.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "../src", 3 | "tests" : [ 4 | "Scale", 5 | "Statistics", 6 | "Shape", 7 | "Transition", 8 | "Interpolation" 9 | ] 10 | } 11 | --------------------------------------------------------------------------------