Elm logo
elm
examples

elm-visualization

/

examples

/

Treemap

Edit on Ellie

Treemap

module Treemap exposing (TilingMethod, main)


import Color exposing (Color)
import Example
import Hierarchy
import Html exposing (Html)
import Scale exposing (OrdinalScale)
import Scale.Color
import Tree exposing (Tree)
import TypedSvg exposing (g, rect, svg)
import TypedSvg.Attributes exposing (fill, href, id, transform, viewBox)
import TypedSvg.Attributes.InPx exposing (height, width, x)
import TypedSvg.Core exposing (Svg, text)
import TypedSvg.Types exposing (ClipPath(..), Paint(..), Transform(..), em)


w : Float
w =
    990


h : Float
h =
    440


colorScale : OrdinalScale String Color
colorScale =
    Scale.ordinal Scale.Color.tableau10 (tree |> Tree.children |> List.map (Tree.label >> .name))


type TilingMethod
    = Squarify
    | SliceDice
    | Slice
    | Dice


tilingMethods : List ( String, TilingMethod )
tilingMethods =
    [ ( "squarify", Squarify )
    , ( "sliceDice", SliceDice )
    , ( "slice", Slice )
    , ( "dice", Dice )
    ]


main : Example.Program TilingMethod
main =
    tilingMethods
        |> Example.tabbed "Layout: "
        |> Example.application view


view : TilingMethod -> Html msg
view tilingMethod =
    Html.div []
        [ ---legend ,
          svg [ viewBox 0 0 w h ]
            (treemap tilingMethod)
        ]


toAttr : TilingMethod -> Hierarchy.Attribute a { b | tile : Hierarchy.Supported }
toAttr tm =
    case tm of
        Squarify ->
            Hierarchy.tile Hierarchy.squarify

        SliceDice ->
            Hierarchy.tile Hierarchy.sliceDice

        Slice ->
            Hierarchy.tile Hierarchy.slice

        Dice ->
            Hierarchy.tile Hierarchy.dice


treemap : TilingMethod -> List (Svg msg)
treemap tilingMethod =
    tree
        |> Tree.sortWith (\_ a b -> compare (Tree.label b).size (Tree.label a).size)
        |> Hierarchy.treemap [ Hierarchy.padding (always 1), Hierarchy.size w h, toAttr tilingMethod ] .size
        |> Tree.leaves
        |> List.map
            (\item ->
                g [ transform [ Translate item.x item.y ] ]
                    [ TypedSvg.title [] [ text (item.node.name ++ " " ++ String.fromFloat item.node.size) ]
                    , rect
                        [ width item.width
                        , height item.height
                        , fill
                            (item.node.name
                                |> String.split "."
                                |> List.take 2
                                |> String.join "."
                                |> Scale.convert colorScale
                                |> Maybe.withDefault Color.white
                                |> Paint
                            )
                        , id ("rect-" ++ item.node.name)
                        ]
                        []
                    , TypedSvg.clipPath [ id ("clip-" ++ item.node.name) ] [ TypedSvg.use [ href ("#rect-" ++ item.node.name) ] [] ]
                    , item.node.name
                        |> String.split "."
                        |> List.reverse
                        |> List.head
                        |> Maybe.withDefault item.node.name
                        |> String.foldl
                            (\c ( word, soFar ) ->
                                if Char.isUpper c then
                                    ( [ c ]
                                    , case word of
                                        [] ->
                                            soFar

                                        _ ->
                                            String.fromList (List.reverse word) :: soFar
                                    )

                                else
                                    ( c :: word, soFar )
                            )
                            ( [], [] )
                        |> (\( word, soFar ) -> String.fromList (List.reverse word) :: soFar)
                        |> List.append [ String.fromFloat item.node.size ]
                        |> List.reverse
                        |> List.indexedMap
                            (\i st ->
                                TypedSvg.tspan
                                    [ x 3
                                    , TypedSvg.Attributes.y (em (1.1 + toFloat i * 0.9))
                                    ]
                                    [ text st ]
                            )
                        |> TypedSvg.text_
                            [ TypedSvg.Attributes.InPx.fontSize 10
                            , TypedSvg.Attributes.fontFamily [ "sans-serif" ]
                            , TypedSvg.Attributes.clipPath (ClipPathFunc ("url(#clip-" ++ item.node.name ++ ")"))
                            ]
                    ]
            )


tree : Tree { name : String, size : Float }
tree =
    Tree.stratifyWithPath
        { path = \item -> String.split "." item.name
        , createMissingNode = \path -> { name = String.join "." path, size = 0 }
        }
        data
        |> Result.withDefault (Tree.singleton { name = "flare", size = 0 })
        |> Tree.sumUp identity
            (\node children ->
                { node | size = List.sum (List.map .size children) }
            )


data : List { name : String, size : Float }
data =
    [ { name = "flare", size = 0 }
    , { name = "flare.analytics", size = 0 }
    , { name = "flare.analytics.cluster", size = 0 }
    , { name = "flare.analytics.cluster.AgglomerativeCluster", size = 3938 }
    , { name = "flare.analytics.cluster.CommunityStructure", size = 3812 }
    , { name = "flare.analytics.cluster.HierarchicalCluster", size = 6714 }
    , { name = "flare.analytics.cluster.MergeEdge", size = 743 }
    , { name = "flare.analytics.graph", size = 0 }
    , { name = "flare.analytics.graph.BetweennessCentrality", size = 3534 }
    , { name = "flare.analytics.graph.LinkDistance", size = 5731 }
    , { name = "flare.analytics.graph.MaxFlowMinCut", size = 7840 }
    , { name = "flare.analytics.graph.ShortestPaths", size = 5914 }
    , { name = "flare.analytics.graph.SpanningTree", size = 3416 }
    , { name = "flare.analytics.optimization", size = 0 }
    , { name = "flare.analytics.optimization.AspectRatioBanker", size = 7074 }
    , { name = "flare.animate", size = 0 }
    , { name = "flare.animate.Easing", size = 17010 }
    , { name = "flare.animate.FunctionSequence", size = 5842 }
    , { name = "flare.animate.interpolate", size = 0 }
    , { name = "flare.animate.interpolate.ArrayInterpolator", size = 1983 }
    , { name = "flare.animate.interpolate.ColorInterpolator", size = 2047 }
    , { name = "flare.animate.interpolate.DateInterpolator", size = 1375 }
    , { name = "flare.animate.interpolate.Interpolator", size = 8746 }
    , { name = "flare.animate.interpolate.MatrixInterpolator", size = 2202 }
    , { name = "flare.animate.interpolate.NumberInterpolator", size = 1382 }
    , { name = "flare.animate.interpolate.ObjectInterpolator", size = 1629 }
    , { name = "flare.animate.interpolate.PointInterpolator", size = 1675 }
    , { name = "flare.animate.interpolate.RectangleInterpolator", size = 2042 }
    , { name = "flare.animate.ISchedulable", size = 1041 }
    , { name = "flare.animate.Parallel", size = 5176 }
    , { name = "flare.animate.Pause", size = 449 }
    , { name = "flare.animate.Scheduler", size = 5593 }
    , { name = "flare.animate.Sequence", size = 5534 }
    , { name = "flare.animate.Transition", size = 9201 }
    , { name = "flare.animate.Transitioner", size = 19975 }
    , { name = "flare.animate.TransitionEvent", size = 1116 }
    , { name = "flare.animate.Tween", size = 6006 }
    , { name = "flare.data", size = 0 }
    , { name = "flare.data.converters", size = 0 }
    , { name = "flare.data.converters.Converters", size = 721 }
    , { name = "flare.data.converters.DelimitedTextConverter", size = 4294 }
    , { name = "flare.data.converters.GraphMLConverter", size = 9800 }
    , { name = "flare.data.converters.IDataConverter", size = 1314 }
    , { name = "flare.data.converters.JSONConverter", size = 2220 }
    , { name = "flare.data.DataField", size = 1759 }
    , { name = "flare.data.DataSchema", size = 2165 }
    , { name = "flare.data.DataSet", size = 586 }
    , { name = "flare.data.DataSource", size = 3331 }
    , { name = "flare.data.DataTable", size = 772 }
    , { name = "flare.data.DataUtil", size = 3322 }
    , { name = "flare.display", size = 0 }
    , { name = "flare.display.DirtySprite", size = 8833 }
    , { name = "flare.display.LineSprite", size = 1732 }
    , { name = "flare.display.RectSprite", size = 3623 }
    , { name = "flare.display.TextSprite", size = 10066 }
    , { name = "flare.flex", size = 0 }
    , { name = "flare.flex.FlareVis", size = 4116 }
    , { name = "flare.physics", size = 0 }
    , { name = "flare.physics.DragForce", size = 1082 }
    , { name = "flare.physics.GravityForce", size = 1336 }
    , { name = "flare.physics.IForce", size = 319 }
    , { name = "flare.physics.NBodyForce", size = 10498 }
    , { name = "flare.physics.Particle", size = 2822 }
    , { name = "flare.physics.Simulation", size = 9983 }
    , { name = "flare.physics.Spring", size = 2213 }
    , { name = "flare.physics.SpringForce", size = 1681 }
    , { name = "flare.query", size = 0 }
    , { name = "flare.query.AggregateExpression", size = 1616 }
    , { name = "flare.query.And", size = 1027 }
    , { name = "flare.query.Arithmetic", size = 3891 }
    , { name = "flare.query.Average", size = 891 }
    , { name = "flare.query.BinaryExpression", size = 2893 }
    , { name = "flare.query.Comparison", size = 5103 }
    , { name = "flare.query.CompositeExpression", size = 3677 }
    , { name = "flare.query.Count", size = 781 }
    , { name = "flare.query.DateUtil", size = 4141 }
    , { name = "flare.query.Distinct", size = 933 }
    , { name = "flare.query.Expression", size = 5130 }
    , { name = "flare.query.ExpressionIterator", size = 3617 }
    , { name = "flare.query.Fn", size = 3240 }
    , { name = "flare.query.If", size = 2732 }
    , { name = "flare.query.IsA", size = 2039 }
    , { name = "flare.query.Literal", size = 1214 }
    , { name = "flare.query.Match", size = 3748 }
    , { name = "flare.query.Maximum", size = 843 }
    , { name = "flare.query.methods", size = 0 }
    , { name = "flare.query.methods.add", size = 593 }
    , { name = "flare.query.methods.and", size = 330 }
    , { name = "flare.query.methods.average", size = 287 }
    , { name = "flare.query.methods.count", size = 277 }
    , { name = "flare.query.methods.distinct", size = 292 }
    , { name = "flare.query.methods.div", size = 595 }
    , { name = "flare.query.methods.eq", size = 594 }
    , { name = "flare.query.methods.fn", size = 460 }
    , { name = "flare.query.methods.gt", size = 603 }
    , { name = "flare.query.methods.gte", size = 625 }
    , { name = "flare.query.methods.iff", size = 748 }
    , { name = "flare.query.methods.isa", size = 461 }
    , { name = "flare.query.methods.lt", size = 597 }
    , { name = "flare.query.methods.lte", size = 619 }
    , { name = "flare.query.methods.max", size = 283 }
    , { name = "flare.query.methods.min", size = 283 }
    , { name = "flare.query.methods.mod", size = 591 }
    , { name = "flare.query.methods.mul", size = 603 }
    , { name = "flare.query.methods.neq", size = 599 }
    , { name = "flare.query.methods.not", size = 386 }
    , { name = "flare.query.methods.or", size = 323 }
    , { name = "flare.query.methods.orderby", size = 307 }
    , { name = "flare.query.methods.range", size = 772 }
    , { name = "flare.query.methods.select", size = 296 }
    , { name = "flare.query.methods.stddev", size = 363 }
    , { name = "flare.query.methods.sub", size = 600 }
    , { name = "flare.query.methods.sum", size = 280 }
    , { name = "flare.query.methods.update", size = 307 }
    , { name = "flare.query.methods.variance", size = 335 }
    , { name = "flare.query.methods.where", size = 299 }
    , { name = "flare.query.methods.xor", size = 354 }
    , { name = "flare.query.methods._", size = 264 }
    , { name = "flare.query.Minimum", size = 843 }
    , { name = "flare.query.Not", size = 1554 }
    , { name = "flare.query.Or", size = 970 }
    , { name = "flare.query.Query", size = 13896 }
    , { name = "flare.query.Range", size = 1594 }
    , { name = "flare.query.StringUtil", size = 4130 }
    , { name = "flare.query.Sum", size = 791 }
    , { name = "flare.query.Variable", size = 1124 }
    , { name = "flare.query.Variance", size = 1876 }
    , { name = "flare.query.Xor", size = 1101 }
    , { name = "flare.scale", size = 0 }
    , { name = "flare.scale.IScaleMap", size = 2105 }
    , { name = "flare.scale.LinearScale", size = 1316 }
    , { name = "flare.scale.LogScale", size = 3151 }
    , { name = "flare.scale.OrdinalScale", size = 3770 }
    , { name = "flare.scale.QuantileScale", size = 2435 }
    , { name = "flare.scale.QuantitativeScale", size = 4839 }
    , { name = "flare.scale.RootScale", size = 1756 }
    , { name = "flare.scale.Scale", size = 4268 }
    , { name = "flare.scale.ScaleType", size = 1821 }
    , { name = "flare.scale.TimeScale", size = 5833 }
    , { name = "flare.util", size = 0 }
    , { name = "flare.util.Arrays", size = 8258 }
    , { name = "flare.util.Colors", size = 10001 }
    , { name = "flare.util.Dates", size = 8217 }
    , { name = "flare.util.Displays", size = 12555 }
    , { name = "flare.util.Filter", size = 2324 }
    , { name = "flare.util.Geometry", size = 10993 }
    , { name = "flare.util.heap", size = 0 }
    , { name = "flare.util.heap.FibonacciHeap", size = 9354 }
    , { name = "flare.util.heap.HeapNode", size = 1233 }
    , { name = "flare.util.IEvaluable", size = 335 }
    , { name = "flare.util.IPredicate", size = 383 }
    , { name = "flare.util.IValueProxy", size = 874 }
    , { name = "flare.util.math", size = 0 }
    , { name = "flare.util.math.DenseMatrix", size = 3165 }
    , { name = "flare.util.math.IMatrix", size = 2815 }
    , { name = "flare.util.math.SparseMatrix", size = 3366 }
    , { name = "flare.util.Maths", size = 17705 }
    , { name = "flare.util.Orientation", size = 1486 }
    , { name = "flare.util.palette", size = 0 }
    , { name = "flare.util.palette.ColorPalette", size = 6367 }
    , { name = "flare.util.palette.Palette", size = 1229 }
    , { name = "flare.util.palette.ShapePalette", size = 2059 }
    , { name = "flare.util.palette.SizePalette", size = 2291 }
    , { name = "flare.util.Property", size = 5559 }
    , { name = "flare.util.Shapes", size = 19118 }
    , { name = "flare.util.Sort", size = 6887 }
    , { name = "flare.util.Stats", size = 6557 }
    , { name = "flare.util.Strings", size = 22026 }
    , { name = "flare.vis", size = 0 }
    , { name = "flare.vis.axis", size = 0 }
    , { name = "flare.vis.axis.Axes", size = 1302 }
    , { name = "flare.vis.axis.Axis", size = 24593 }
    , { name = "flare.vis.axis.AxisGridLine", size = 652 }
    , { name = "flare.vis.axis.AxisLabel", size = 636 }
    , { name = "flare.vis.axis.CartesianAxes", size = 6703 }
    , { name = "flare.vis.controls", size = 0 }
    , { name = "flare.vis.controls.AnchorControl", size = 2138 }
    , { name = "flare.vis.controls.ClickControl", size = 3824 }
    , { name = "flare.vis.controls.Control", size = 1353 }
    , { name = "flare.vis.controls.ControlList", size = 4665 }
    , { name = "flare.vis.controls.DragControl", size = 2649 }
    , { name = "flare.vis.controls.ExpandControl", size = 2832 }
    , { name = "flare.vis.controls.HoverControl", size = 4896 }
    , { name = "flare.vis.controls.IControl", size = 763 }
    , { name = "flare.vis.controls.PanZoomControl", size = 5222 }
    , { name = "flare.vis.controls.SelectionControl", size = 7862 }
    , { name = "flare.vis.controls.TooltipControl", size = 8435 }
    , { name = "flare.vis.data", size = 0 }
    , { name = "flare.vis.data.Data", size = 20544 }
    , { name = "flare.vis.data.DataList", size = 19788 }
    , { name = "flare.vis.data.DataSprite", size = 10349 }
    , { name = "flare.vis.data.EdgeSprite", size = 3301 }
    , { name = "flare.vis.data.NodeSprite", size = 19382 }
    , { name = "flare.vis.data.render", size = 0 }
    , { name = "flare.vis.data.render.ArrowType", size = 698 }
    , { name = "flare.vis.data.render.EdgeRenderer", size = 5569 }
    , { name = "flare.vis.data.render.IRenderer", size = 353 }
    , { name = "flare.vis.data.render.ShapeRenderer", size = 2247 }
    , { name = "flare.vis.data.ScaleBinding", size = 11275 }
    , { name = "flare.vis.data.Tree", size = 7147 }
    , { name = "flare.vis.data.TreeBuilder", size = 9930 }
    , { name = "flare.vis.events", size = 0 }
    , { name = "flare.vis.events.DataEvent", size = 2313 }
    , { name = "flare.vis.events.SelectionEvent", size = 1880 }
    , { name = "flare.vis.events.TooltipEvent", size = 1701 }
    , { name = "flare.vis.events.VisualizationEvent", size = 1117 }
    , { name = "flare.vis.legend", size = 0 }
    , { name = "flare.vis.legend.Legend", size = 20859 }
    , { name = "flare.vis.legend.LegendItem", size = 4614 }
    , { name = "flare.vis.legend.LegendRange", size = 10530 }
    , { name = "flare.vis.operator", size = 0 }
    , { name = "flare.vis.operator.distortion", size = 0 }
    , { name = "flare.vis.operator.distortion.BifocalDistortion", size = 4461 }
    , { name = "flare.vis.operator.distortion.Distortion", size = 6314 }
    , { name = "flare.vis.operator.distortion.FisheyeDistortion", size = 3444 }
    , { name = "flare.vis.operator.encoder", size = 0 }
    , { name = "flare.vis.operator.encoder.ColorEncoder", size = 3179 }
    , { name = "flare.vis.operator.encoder.Encoder", size = 4060 }
    , { name = "flare.vis.operator.encoder.PropertyEncoder", size = 4138 }
    , { name = "flare.vis.operator.encoder.ShapeEncoder", size = 1690 }
    , { name = "flare.vis.operator.encoder.SizeEncoder", size = 1830 }
    , { name = "flare.vis.operator.filter", size = 0 }
    , { name = "flare.vis.operator.filter.FisheyeTreeFilter", size = 5219 }
    , { name = "flare.vis.operator.filter.GraphDistanceFilter", size = 3165 }
    , { name = "flare.vis.operator.filter.VisibilityFilter", size = 3509 }
    , { name = "flare.vis.operator.IOperator", size = 1286 }
    , { name = "flare.vis.operator.label", size = 0 }
    , { name = "flare.vis.operator.label.Labeler", size = 9956 }
    , { name = "flare.vis.operator.label.RadialLabeler", size = 3899 }
    , { name = "flare.vis.operator.label.StackedAreaLabeler", size = 3202 }
    , { name = "flare.vis.operator.layout", size = 0 }
    , { name = "flare.vis.operator.layout.AxisLayout", size = 6725 }
    , { name = "flare.vis.operator.layout.BundledEdgeRouter", size = 3727 }
    , { name = "flare.vis.operator.layout.CircleLayout", size = 9317 }
    , { name = "flare.vis.operator.layout.CirclePackingLayout", size = 12003 }
    , { name = "flare.vis.operator.layout.DendrogramLayout", size = 4853 }
    , { name = "flare.vis.operator.layout.ForceDirectedLayout", size = 8411 }
    , { name = "flare.vis.operator.layout.IcicleTreeLayout", size = 4864 }
    , { name = "flare.vis.operator.layout.IndentedTreeLayout", size = 3174 }
    , { name = "flare.vis.operator.layout.Layout", size = 7881 }
    , { name = "flare.vis.operator.layout.NodeLinkTreeLayout", size = 12870 }
    , { name = "flare.vis.operator.layout.PieLayout", size = 2728 }
    , { name = "flare.vis.operator.layout.RadialTreeLayout", size = 12348 }
    , { name = "flare.vis.operator.layout.RandomLayout", size = 870 }
    , { name = "flare.vis.operator.layout.StackedAreaLayout", size = 9121 }
    , { name = "flare.vis.operator.layout.TreeMapLayout", size = 9191 }
    , { name = "flare.vis.operator.Operator", size = 2490 }
    , { name = "flare.vis.operator.OperatorList", size = 5248 }
    , { name = "flare.vis.operator.OperatorSequence", size = 4190 }
    , { name = "flare.vis.operator.OperatorSwitch", size = 2581 }
    , { name = "flare.vis.operator.SortOperator", size = 2023 }
    , { name = "flare.vis.Visualization", size = 16540 }
    ]