From 43e6da8bc860bd8086797bf9d7212b2fae09ae97 Mon Sep 17 00:00:00 2001 From: Jakub Hampl Date: Mon, 22 Jul 2024 10:12:09 +0100 Subject: [PATCH] Adds funnel chart (#168) --- README.md | 24 ++--- elm.json | 2 +- examples/FunnelChart.elm | 197 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 examples/FunnelChart.elm diff --git a/README.md b/README.md index 9ad90b8..5787990 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ![Elm-visualization](https://code.gampleman.eu/elm-visualization/misc/Logo-600.png) -[Tutorial](https://github.com/gampleman/elm-visualization/blob/master/docs/INTRO.md) | [Docs](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/) | [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) +[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) This project is designed to give you all the tools needed to build data visualizations. It is not a charting library in the sense that you have pre-bundled Excel-style @@ -34,52 +34,52 @@ You can use [this Ellie](https://ellie-app.com/p6X5hXxcdRCa1) to run the example ## What's included? -### [Scales](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Scale/) +### [Scales](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Scale/) Most of the time you have data that has properties that you want to display on the screen, however these properties typically aren't in pixels. Scales solve this fundamental problem by giving you convenient ways to transform raw data into positions, sizes, colors, labels and other ways to display data. -### [Axis](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Axis/) +### [Axis](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Axis/) A component that allows you to visualize a Scale. Those little ticks that describe the dimensions of a plot. -### [Shapes](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Shape/) +### [Shapes](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Shape/) This module gives you ways to draw some fundamental shapes used in data visualization, including lines (as in line or area charts), as well as arcs (as in pie charts). -### [Force Layout](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Force/) +### [Force Layout](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Force/) Use a simulation of physical forces to do layout. Suitable for i.e. network graphs. -### [Hierarchy](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Hierarchy/) +### [Hierarchy](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Hierarchy/) Layout algorithms for dealing with trees. -### [Interpolation](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Interpolation/) +### [Interpolation](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Interpolation/) Smoothly transition between pairs of values. Useful for animation, or generating gradients of values. -### [Transition](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Transition/) +### [Transition](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Transition/) Build complex animations using Interpolation. -### [Histogram](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Histogram/) +### [Histogram](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Histogram/) Compute histograms of data. -### [Brush](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Brush/) +### [Brush](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Brush/) Interactively select subregions of a dataset. -### [Zoom](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Zoom/) +### [Zoom](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Zoom/) Build pan and zoom user interactions. -### [Statistics](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.1/Statistics/) +### [Statistics](https://package.elm-lang.org/packages/gampleman/elm-visualization/2.4.2/Statistics/) Process data to extract useful insights for visualizations. diff --git a/elm.json b/elm.json index 74a5f2b..43ecad6 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "gampleman/elm-visualization", "summary": "A data visualization package for Elm", "license": "MIT", - "version": "2.4.1", + "version": "2.4.2", "exposed-modules": [ "Scale", "Scale.Color", diff --git a/examples/FunnelChart.elm b/examples/FunnelChart.elm new file mode 100644 index 0000000..424ba4b --- /dev/null +++ b/examples/FunnelChart.elm @@ -0,0 +1,197 @@ +module FunnelChart exposing (main) + +{-| Shows how to build a Funnel chart. It is quite similar to a bar chart, but with +the addition of an extra shape connecting the bars. + +The design here follows [this great best practices guide](https://www.atlassian.com/data/charts/funnel-chart-complete-guide). + +@category Basics + +-} + +import Color exposing (Color) +import Float.Extra +import List.Extra +import Scale exposing (BandScale, ContinuousScale, SequentialScale, defaultBandConfig) +import Scale.Color +import TypedSvg exposing (g, polygon, rect, svg, text_) +import TypedSvg.Attributes exposing (fill, fillOpacity, fontFamily, points, stroke, textAnchor, transform, viewBox) +import TypedSvg.Attributes.InPx exposing (fontSize, height, strokeWidth, width, x, y) +import TypedSvg.Core exposing (Svg, text) +import TypedSvg.Types exposing (AnchorAlignment(..), Opacity(..), Paint(..), Transform(..)) + + +w : Float +w = + 900 + + +h : Float +h = + 450 + + +titleWidth : Float +titleWidth = + 100 + + +padding : Float +padding = + 30 + + +type alias Datum = + { title : String + , value : Float + } + + +type Shape + = Bar Int Datum + | Connector Datum Datum + + + +-- Here we preprocess the data to create the shapes we want to draw in an interleaved list +-- [Datum "A" 1, Datum "B" 2, Datum "C" 3] +-- -> [ Bar 0 (Datum "A" 1), Connector (Datum "A" 1) (Datum "B" 2), +-- Bar 1 (Datum "B" 2), Connector (Datum "B" 2) (Datum "C" 3), +-- Bar 2 (Datum "C" 3) ] + + +preprocessed : List Shape +preprocessed = + List.map2 Connector data (List.drop 1 data) + |> List.Extra.interweave (List.indexedMap Bar data) + + +maxValue : Float +maxValue = + data + |> List.map .value + |> List.maximum + |> Maybe.withDefault 0 + + +xScale : ContinuousScale Float +xScale = + Scale.linear ( 0, w - 2 * padding - 2 * titleWidth ) ( 0, maxValue ) + + +yScale : BandScale String +yScale = + data + |> List.map .title + |> Scale.band { defaultBandConfig | paddingInner = 0.2 } ( padding, h - padding ) + + +colorScale : SequentialScale Color +colorScale = + Scale.sequential Scale.Color.bluesInterpolator ( -1, toFloat (List.length data - 1) ) + + +view : Svg msg +view = + List.map + (\shape -> + case shape of + Bar index datum -> + let + color = + Scale.convert colorScale (toFloat index) + in + g [] + [ rect + [ x (-(Scale.convert xScale datum.value) / 2) + , y (Scale.convert yScale datum.title) + , width (Scale.convert xScale datum.value) + , height (Scale.bandwidth yScale) + , fill (Paint color) + ] + [] + , text_ + [ x (-w / 2 + padding) + , y (Scale.convert yScale datum.title + Scale.bandwidth yScale / 2) + , transform [ Translate 0 5 ] + ] + [ text datum.title ] + , text_ + [ x (w / 2 - padding) + , y (Scale.convert yScale datum.title + Scale.bandwidth yScale / 2) + , transform [ Translate 0 5 ] + , textAnchor AnchorEnd + ] + [ text (Float.Extra.toFixedDecimalPlaces 1 (100 * datum.value / maxValue) ++ "%") ] + , text_ + [ x 0 + , y (Scale.convert yScale datum.title + Scale.bandwidth yScale / 2) + , transform [ Translate 0 5 ] + , textAnchor AnchorMiddle + , fill + (Paint + -- This is a simple way to make sure the text + -- is readable + -- However, you may want a more sophisticated + -- algorithm for other color scales + (if (Color.toHsla color).lightness > 0.5 then + Color.black + + else + Color.white + ) + ) + , stroke (Paint color) + , strokeWidth 3 + , TypedSvg.Core.attribute "paint-order" "stroke" + , fillOpacity (Opacity 0.9) + ] + [ text (String.fromFloat datum.value) ] + ] + + Connector top bottom -> + g [] + [ polygon + [ points + [ ( -(Scale.convert xScale top.value) / 2, Scale.convert yScale top.title + Scale.bandwidth yScale ) + , ( Scale.convert xScale top.value / 2, Scale.convert yScale top.title + Scale.bandwidth yScale ) + , ( Scale.convert xScale bottom.value / 2, Scale.convert yScale bottom.title ) + , ( -(Scale.convert xScale bottom.value) / 2, Scale.convert yScale bottom.title ) + ] + , fill (Paint (Scale.convert colorScale -1)) + ] + [] + , text_ + [ x 0 + , y (Scale.convert yScale top.title + Scale.bandwidth yScale) + , transform [ Translate 0 14 ] + , textAnchor AnchorMiddle + , fontSize 12 + , fillOpacity (Opacity 0.7) + ] + [ text (Float.Extra.toFixedDecimalPlaces 1 (100 * bottom.value / top.value) ++ "%") ] + ] + ) + preprocessed + |> svg [ viewBox (-w / 2) 0 w h, fontFamily [ "sans-serif" ] ] + + +data : List Datum +data = + [ { title = "Total" + , value = 54809 + } + , { title = "Helpful" + , value = 29434 + } + , { title = "Qualified" + , value = 10345 + } + , { title = "Successful" + , value = 3432 + } + ] + + +main = + view