Hover Text and Formatting in F#

How to use hover text and formatting in F# with Plotly.


Plotly is known for its graphing libraries, but it's also a broader ecosystem for building interactive data apps, AI data analysis tools, and deploying and hosting Dash apps. Explore the full set of tools and platforms designed to help turn data into action. Discover Plotly.

Note: We are retiring documentation for R, MATLAB, Julia, and F#. Learn more about this change here.

In [1]:
#r "nuget: Newtonsoft.Json, 12.0.3"
#r "nuget: Plotly.NET,  2.0.0-preview.9"
#r "nuget: Plotly.NET.Interactive,  2.0.0-preview.9"
#r "nuget: FSharp.Data"
Installed Packages
  • FSharp.Data, 4.2.4
  • Newtonsoft.Json, 12.0.3
  • Plotly.NET, 2.0.0-preview.9
  • Plotly.NET.Interactive, 2.0.0-preview.9

Loading extensions from Plotly.NET.Interactive.dll

Added Kernel Extension including formatters for Plotly.NET charts.

Hover Labels

One of the most deceptively-powerful features of interactive visualization using Plotly.NET is the ability for the user to reveal more information about a data point by moving their mouse cursor over the point and having a hover label appear.

In [2]:
open Plotly.NET

let x = [for i in 0..10 -> i]
let y = [for i in x -> i*2]
let labels = [for i in x -> "Text "+ string i]

Chart.Point(x,y,Labels=labels)
Out[2]:

Customizing Hover Mode

There are three hover modes available in Plotly.NET. The default setting is Layout.HoverMode='Closest', wherein a single hover label appears for the point directly underneath the cursor.

In [3]:
open Plotly.NET

let x = [for i in 0..10 -> i]
let y1 = [for i in x -> 2.0*Math.Cos(float i)]
let y2 = [for i in x -> 5.0*Math.Sin(float i)]

let labels = [for i in x -> "Text "+ string i]

[
Chart.Line(x,y1,Labels=labels,ShowMarkers=true);
Chart.Line(x,y2,Labels=labels,ShowMarkers=true);
]
|> Chart.combine
|> Chart.withLayout(Layout.init(HoverMode=StyleParam.HoverMode.Closest))
Out[3]:

HoverMode X or Y

If Layout.HoverMode='X' (or 'Y'), a single hover label appears per trace, for points at the same x (or y) value as the cursor. If multiple points in a given trace exist at the same coordinate, only one will get a hover label. In the line plot below we have forced markers to appear, to make it clearer what can be hovered over:

In [4]:
open FSharp.Data

let csv = CsvFile.Load("https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv")

let Oceania = Seq.filter (fun (row:CsvRow) -> row.GetColumn("continent") = "Oceania") csv.Rows
                |> Seq.groupBy (fun c -> c.GetColumn("country"))

[for country,countryDetails in Oceania -> 
                let x = countryDetails |> Seq.map (fun x -> x.GetColumn("year"))
                let y = countryDetails |> Seq.map (fun x -> x.GetColumn("lifeExp"))
                
                Chart.Line(x,y,Name=country,ShowMarkers=true)]
|> Chart.combine
|> Chart.withLayout(Layout.init(HoverMode=StyleParam.HoverMode.X))
|> Chart.withTitle("LayoutMode.HoverMode='X'")
Out[4]:

HoverMode = XUnified (Will Be Implemented)

Customizing Hover Label Appearance

Hover Label color and font can be set through Layout.HoverLabel attribute as shown below

In [5]:
open FSharp.Data
open Plotly.NET.LayoutObjects

let csv = CsvFile.Load("https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv")

let Oceania = Seq.filter (fun (row:CsvRow) -> row.GetColumn("continent") = "Oceania") csv.Rows
                |> Seq.groupBy (fun c -> c.GetColumn("country"))


let hoverLabel = Hoverlabel.init(BgColor=Color.fromString "white",
                                    Font=Font.init(Size=16.,Family=StyleParam.FontFamily.Custom "Rockwell"))

[for country,countryDetails in Oceania -> 
                let x = countryDetails |> Seq.map (fun x -> x.GetColumn("year"))
                let y = countryDetails |> Seq.map (fun x -> x.GetColumn("lifeExp"))
                
                Chart.Line(x,y,Name=country,ShowMarkers=true)]
|> Chart.combine
|> Chart.withLayout(Layout.init(Hoverlabel=hoverLabel))
|> Chart.withTitle("LayoutMode.HoverMode='X'")
Out[5]:

Customizing hover text with a HoverTemplate

To customize the tooltip on your graph you can use the HoverTemplate attribute of corresponding TraceStyle (i.e Trace2DStyle,TraceDomainStyle etc), which is a template string used for rendering the information that appear on hoverbox. This template string can include variables in %{variable} format, numbers in d3-format's syntax, and date in d3-time-format's syntax. In the example below, the empty tag removes the part of the hover where the trace name is usually displayed in a contrasting color. The tag can be used to display other parts of the hovertemplate, it is not reserved for the trace name.

Note that a HoverTemplate customizes the tooltip text, while a TextTemplate customizes the text that appears on your chart.

Set the horizontal alignment of the text within tooltip with Hoverlabel.Align.

In [6]:
let hoverLabel = Hoverlabel.init(BgColor=Color.fromString "white",
                                   Align=StyleParam.Align.Right)

let hoverTemplate = "<i>Price</i>: $%{y:.2f}"+
                    "<br><b>X</b>: %{x}<br>"+
                    "<b>%{text}</b>";

let text = [for i in 1..5 -> $"Custom text {i+1}"] 

let chart1 = Chart.Line(x=[1..5],
                        y = [2.02825;1.63728;6.83839;4.8485;4.73463],
                        ShowMarkers=true, ShowLegend=false)
                
                |> GenericChart.mapTrace (Trace2DStyle.Scatter(HoverTemplate=hoverTemplate,MultiText=text))

let chart2 = Chart.Line(x=[1..5],
                        y = [3.02825;2.63728;4.83839;3.8485;1.73463],
                        ShowMarkers=true, ShowLegend=false)
                
                |> GenericChart.mapTrace (Trace2DStyle.Scatter(HoverTemplate="Price: %{y:$.2f}<extra></extra>"))

[chart1;chart2]
|> Chart.combine
|> Chart.withLayout(Layout.init(Hoverlabel=hoverLabel))
|> Chart.withTitle("Set hover text with HoverTemplate")
Out[6]:
In [7]:
open Plotly.NET

Chart.Pie(values=[2.; 5.; 3.; 2.5],Name="",
            Labels = ["R"; "Python"; "Java Script"; "Matlab"])
    |> GenericChart.mapTrace(TraceDomainStyle.Pie(Text=["TextA"; "TextB"; "TextC"; "TextD"],
                                                    HoverTemplate="%{label}: <br>Popularity: %{percent} </br> %{text}"))
Out[7]:

Hover Templates with Mixtures of Period data

When displaying periodic data with mixed-sized periods (i.e. quarterly and monthly) in conjunction with x or x unified hovermodes and using hovertemplate, the XHoverFormat attribute can be used to control how each period's X value is displayed, and the special %{xother} hover-template directive can be used to control how the X value is displayed for points that do not share the exact X coordinate with the point that is being hovered on. %{xother} will return an empty string when the X value is the one being hovered on, otherwise it will return (%{x}). The special %{xother}, %{xother} and %{xother} variations will display with spaces before, after or around the parentheses, respectively.

In [8]:
open Plotly.NET.LayoutObjects

let chart1 = Chart.Column([1000; 1500; 1700],["2020-01-01"; "2020-04-01"; "2020-07-01"])
                |> GenericChart.mapTrace(Trace2DStyle.Bar(XPeriod="M3",
                                            XPeriodAlignment=StyleParam.PeriodAlignment.Middle,
                                            XHoverFormat="Q%q",
                                            HoverTemplate = "%{y}%{_xother}"))
                                            
let x=["2020-01-01"; "2020-02-01"; "2020-03-01";
                            "2020-04-01"; "2020-05-01"; "2020-06-01";
                            "2020-07-01"; "2020-08-01"; "2020-09-01"]
let y=[1100;1050;1200;1300;1400;1700;1500;1400;1600]

let chart2 =Chart.Line(x=x,y=y,ShowMarkers=true)
                |> GenericChart.mapTrace(Trace2DStyle.Scatter(XPeriod="M1",
                                            XPeriodAlignment=StyleParam.PeriodAlignment.Middle,
                                            XHoverFormat="Q%q",
                                            HoverTemplate = "%{y}%{_xother}"))

let layout = //Workaround
    let tmp  = new Layout()
    tmp?hovermode <- "x unified"
    tmp

[chart1;chart2]
|>  Chart.combine
|>  Chart.withLayout(layout)
Out[8]:

Advanced Hover Template

The following example shows how to format a hover template.

In [9]:
open Plotly.NET.TraceObjects
open Plotly.NET.LayoutObjects   

let csv = CsvFile.Load("https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv")

let continentNames = ["Africa"; "Americas";"Asia"; "Europe"; "Oceania"]

let contientGrouping = csv.Rows |> Seq.groupBy (fun row -> row.GetColumn("continent")) 

let hoverTemplate = "<b>%{text}</b><br><br>" +
                        "GDP per Capita: %{x:$,.0f}<br>" +
                        "Life Expectation: %{y:.0%}<br>" +
                        "Population: %{marker.size:,}" +
                        "<extra></extra>"

let rand = new Random()

[for continent,countryList in contientGrouping -> 
                                                    let x = [for (row:CsvRow) in countryList -> row.GetColumn("gdpPercap")]
                                                    let y = [for (row:CsvRow) in countryList -> row.GetColumn("lifeExp")]
                                                    let size = [for (row:CsvRow) in countryList -> int <| rand.NextDouble()*300. ]
                                                    let text = countryList |> Seq.map (fun c -> c.GetColumn("continent"))
                                                    Chart.Scatter(x,y,StyleParam.Mode.Markers,Name=continent)                                                    
                                                    |> Chart.withMarkerStyle(MultiSize=size,SizeMode=StyleParam.MarkerSizeMode.Area,SizeRef=10)
                                                    |> GenericChart.mapTrace(Trace2DStyle.Scatter(HoverTemplate=hoverTemplate,MultiText=text))]

|> Chart.combine
|> Chart.withXAxis(LinearAxis.init(AxisType=StyleParam.AxisType.Log))
Out[9]:

Adding other data to the hover with CustomData and a HoverTemplate

You can then use CustomData inside a HoverTemplate to display the value of CustomData.

In [10]:
open System

let rand = new Random()
let hoverTemplate ="<b>y:%{y:.3f}</b><br> custom:%{customdata:.3f}}"

let customdata = [|for i in 1..5 -> rand.NextDouble()|]

Chart.Line(x=[1..5],
            y = [2.02825;1.63728;6.83839;4.8485;4.73463],
            ShowMarkers=true, ShowLegend=false)
                
|> GenericChart.mapTrace (Trace2DStyle.Scatter(HoverTemplate=hoverTemplate,CustomData=customdata))
Out[10]:

Setting the Hover Template in Mapbox Maps (Missing Abstraction)

In [11]:
// let cityNames = [
//         "Montreal"; "Toronto"; "Vancouver"; "Calgary"; "Edmonton";
//         "Ottawa"; "Halifax"; "Victoria"; "Winnepeg"; "Regina"
//     ]
// let hoverTemplate = "<b>%{marker.symbol} </b><br><br>" +
//                     "longitude: %{lon}<br>" +
//                     "latitude: %{lat}<br>"  

// Chart.ScatterMapbox(longitudes=[-75; -80; -50],
//                     latitudes=[45; 20; -20],
//                     mode=StyleParam.Mode.Lines_Markers_Text)
// |> Chart.withMarker(Marker.init(Size=20))
// |> GenericChart.mapTrace (TraceMapboxStyle.ScatterMapbox())
// |> Chart.withMapbox(
//     Mapbox.init(Style=StyleParam.MapboxStyle.OpenStreetMap,
//                 Zoom=1.,
//                 Center=(-84.6,30.45)
//     )
// )

Spike lines

Plotly.NET supports "spike lines" which link a point to the axis on hover, and can be configured per axis.

In [12]:
open FSharp.Data

let csv = CsvFile.Load("https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv")

let Oceania = Seq.filter (fun (row:CsvRow) -> row.GetColumn("continent") = "Oceania") csv.Rows
                |> Seq.groupBy (fun c -> c.GetColumn("country"))

[for country,countryDetails in Oceania -> 
                let x = countryDetails |> Seq.map (fun x -> x.GetColumn("year"))
                let y = countryDetails |> Seq.map (fun x -> x.GetColumn("lifeExp"))
                
                Chart.Line(x,y,Name=country,ShowMarkers=true)]
|> Chart.combine
|> Chart.withXAxis(LinearAxis.init(ShowSpikes=true))
|> Chart.withYAxis(LinearAxis.init(ShowSpikes=true))
|> Chart.withTitle("Spike lines active")
Out[12]: