Skip to content

Commit

Permalink
completely customize labels if needed
Browse files Browse the repository at this point in the history
  • Loading branch information
benjajaja committed Jun 9, 2020
1 parent 4fa1568 commit 3d1f76b
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 45 deletions.
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -2,11 +2,17 @@

A small library to display just one type of charts: radar charts, also known as web chart, spider chart, spider web chart, star chart, star plot, cobweb chart, irregular polygon, polar chart, or Kiviat diagram.

These kind of charts are often not particularly useful, or can even be misleading
as they may "connect" unrelated data.

However, they look cool. They can be useful for example to convey a pattern
immediately, if the user has already interiorized many samples.

Sample output:
![Sample output](https://github.com/gipsy-king/radar-chart/blob/master/sample_output.svg?raw=true)

Ellie demo playground:
https://ellie-app.com/93J93BR7dGHa1
https://ellie-app.com/965yy8M8Knja1

## Installation

Expand Down
2 changes: 1 addition & 1 deletion elm.json
Expand Up @@ -3,7 +3,7 @@
"name": "gipsy-king/radar-chart",
"summary": "An SVG radar chart",
"license": "BSD-3-Clause",
"version": "2.0.0",
"version": "3.0.0",
"exposed-modules": [
"RadarChart"
],
Expand Down
23 changes: 19 additions & 4 deletions examples/Default.elm
Expand Up @@ -3,6 +3,8 @@ module Default exposing (main)
import Html
import Html.Attributes
import RadarChart exposing (view)
import Svg
import Svg.Attributes


main : Html.Html msg
Expand All @@ -12,19 +14,32 @@ main =
[ Html.div [] [ Html.text "Default options:" ]
, RadarChart.view
RadarChart.defaultOptions
[ "Values", "Variables", "Conditionals", "Loops", "Functions", "Programs" ]
(RadarChart.simpleLabels [ "Values", "Variables", "Conditionals", "Loops", "Functions", "Programs" ])
[ { color = "#333333", data = List.take 6 someData } ]
, Html.div [] [ Html.text "Minimal axis, filled area:" ]
, Html.div [] [ Html.text "Minimal axis, filled area, custom labels with tooltips:" ]
, RadarChart.view
{ maximum = RadarChart.FixedMax 500
, fontSize = 2.0
, margin = 0.333
, strokeWidth = 1
, axisColor = "lightgrey"
, axisStyle = RadarChart.Minimal
, lineStyle = RadarChart.Filled (2 / 3)
}
[ "Unit", "Integration", "End-to-end", "QA", "Monitoring" ]
(RadarChart.customLabels
[ ( "Unit", "Unit tests" )
, ( "Integration", "Integration tests" )
, ( "E2E", "End-to-end tests" )
, ( "QA", "Quality assurance testing" )
, ( "Monitoring", "Production monitoring" )
]
(\( label, tooltip ) attrs ->
Svg.text_
((Svg.Attributes.fontSize <| String.fromFloat 1.5)
:: attrs
)
[ Svg.text label, Svg.title [] [ Svg.text tooltip ] ]
)
)
[ { color = "blue", data = List.drop 2 someData }
, { color = "red", data = List.take 5 someData }
]
Expand Down
137 changes: 98 additions & 39 deletions src/RadarChart.elm
@@ -1,29 +1,50 @@
module RadarChart exposing
( Options, AxisStyle(..), LineStyle(..), DatumSeries, Maximum(..)
, view, defaultOptions
( DatumSeries
, view, defaultOptions, simpleLabels
, Options, AxisStyle(..), LineStyle(..), Maximum(..), customLabels
, LabelMaker, Point, TextAlign
)

{-|
# Customize a chart a little bit, or use defaults
# Simple chart data
@docs Options, AxisStyle, LineStyle, DatumSeries, Maximum
@docs DatumSeries
# Show a radar chart
@docs view, defaultOptions
@docs view, defaultOptions, simpleLabels
# Customize chart a bit
@docs Options, AxisStyle, LineStyle, Maximum, customLabels
# Customize chart labels completely
@docs LabelMaker, Point, TextAlign
-}

import Svg exposing (Svg, circle, svg, text, text_)
import Svg.Attributes exposing (dominantBaseline, fill, fillOpacity, fontSize, stroke, strokeLinecap, strokeLinejoin, strokeWidth, textAnchor, viewBox)


{-| You can have multiple "series", or polygons, on one chart.
The `data` list should be of same size as axis labels.
-}
type alias DatumSeries =
{ color : String
, data : List Float
}


{-| Render a radar chart with options, labels, and some values
-}
view : Options -> List String -> List DatumSeries -> Svg msg
view : Options -> List (LabelMaker msg) -> List DatumSeries -> Svg msg
view options labels series =
let
axisCount =
Expand Down Expand Up @@ -53,20 +74,44 @@ view options labels series =
)
series
)
++ List.indexedMap (axisLabel options axisCount) labels
++ axisLabels options axisCount labels


{-| You can have multiple "series" or polygons on one chart
{-| Get a default options object.
-}
type alias DatumSeries =
{ color : String
, data : List Float
defaultOptions : Options
defaultOptions =
{ maximum = Infer
, margin = 0.333
, strokeWidth = 0.5
, axisColor = "darkgrey"
, axisStyle = Web 6
, lineStyle = Empty
}


{-| Default text labels, positioned conveniently
-}
simpleLabels : List String -> List (LabelMaker msg)
simpleLabels labels =
List.indexedMap axisLabel labels


{-| Custom labels: use a list of anything, and a function that maps the elements
together with position/alignment SVG attributes to `Svg msg`.
-}
customLabels : List a -> (a -> List (Svg.Attribute msg) -> Svg msg) -> List (LabelMaker msg)
customLabels list fn =
List.map
(\a ->
\point align ->
fn a <| labelAttributes point align
)
list


{-| Chart options:
- `fontSize` See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/font-size>
- `margin` Between 0 and 1, to leave some space for labels
- `strokeWidth` For all lines, see <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width>
- `axisColor` axis stroke color, any valid HTML color string (hex, color-name, rgba(...), etc.)
Expand All @@ -76,7 +121,6 @@ type alias DatumSeries =
-}
type alias Options =
{ maximum : Maximum
, fontSize : Float
, margin : Float
, strokeWidth : Float
, axisColor : String
Expand Down Expand Up @@ -110,24 +154,24 @@ type Maximum
| Infer


{-| Get a default options object.
{-| You can even completely make your own attributes and everything
-}
defaultOptions : Options
defaultOptions =
{ maximum = Infer
, fontSize = 3.0
, margin = 0.333
, strokeWidth = 0.5
, axisColor = "darkgrey"
, axisStyle = Web 6
, lineStyle = Empty
}
type alias LabelMaker msg =
Point -> TextAlign -> Svg msg


{-| Point in SVG, suitable for `x` and `y` attributes of text
-}
type alias Point =
( Float, Float )


{-| Text align is simply suitable values for `dominant-baseline` and `text-anchor`, respectively.
-}
type alias TextAlign =
( String, String )


lines : Options -> Int -> String -> List Float -> Float -> List Point -> List (Svg msg)
lines options count color list max result =
case list of
Expand Down Expand Up @@ -218,23 +262,38 @@ webLine options count i segment =
[]


axisLabel : Options -> Int -> Int -> String -> Svg msg
axisLabel options count i label =
let
( x, y ) =
pointOnCircle count i fullRadius fullRadius <| options.margin * 0.9
axisLabels : Options -> Int -> List (LabelMaker msg) -> List (Svg msg)
axisLabels options count fns =
List.indexedMap
(\i fn ->
let
point =
pointOnCircle count i fullRadius fullRadius <| options.margin * 0.9

( vertAnchor, horizAnchor ) =
anchors count i
in
anchors_ =
anchors count i
in
fn point anchors_
)
fns


axisLabel : Int -> String -> Point -> TextAlign -> Svg msg
axisLabel _ label point align =
Svg.text_
[ fontSize <| String.fromFloat options.fontSize
, Svg.Attributes.x <| String.fromFloat <| x
, Svg.Attributes.y <| String.fromFloat <| y
, textAnchor horizAnchor
, dominantBaseline vertAnchor
]
[ Svg.text label ]
((fontSize <| String.fromFloat 2.0)
:: labelAttributes point align
)
[ text label ]


labelAttributes : Point -> TextAlign -> List (Svg.Attribute msg)
labelAttributes ( x, y ) ( vert, horiz ) =
[ Svg.Attributes.x <| String.fromFloat <| x
, Svg.Attributes.y <| String.fromFloat <| y
, dominantBaseline vert
, textAnchor horiz
]


pointOnCircle : Int -> Int -> Float -> Float -> Float -> Point
Expand Down

0 comments on commit 3d1f76b

Please sign in to comment.