├── .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 | # 
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 | [](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 |
2 |
--------------------------------------------------------------------------------
/docs/intro/bar1.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/intro/bar2.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/docs/intro/bar3.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/intro/bar4.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/intro/force.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
--------------------------------------------------------------------------------