Aug 19, 2015
Tatyana Ryzhenkova (DevExpress)

Enrich Your Tooltips with Custom HTML Markup

We have recently presented a new version of our products - DevExtreme 15.1. In this version, we introduced the capability to display tooltips with custom HTML markup in data visualization widgets. This will allow you to fully customize tooltip appearance.

Rich Tooltip - Summaries

Rich Tooltip - Simple Tooltip

Since default tooltips lack customization and sophistication, we decided to use the power of HTML to make several changes and bring you our new flexible tooltips.

Rich Tooltip - Custom Tooltip

Take a look at how easy it is to use this feature with DevExtreme data visualization widgets.

Starting with 15.1, the customizeTooltip option has a new field - html. To use a tooltip with a custom HTML markup, just create a code structure like this:

JavaScript
$("#container").dxChart({
//... 
    tooltip: {
        enabled: true,
        customizeTooltip: function () {
            return {
                html: "html content"
            }
        },
        //...
    },
//...
});

This construction enables you to create a tooltip containing an HTML table, for example, by assigning the relevant markup to the html field.

Rich Tooltip - Table in a Tooltip

There will undoubtedly come a time when you will need to display a more complex structure than a simple HTML table, for example, another visualization widget. Take a look at the steps below for guidance.

To begin, we will create a sample containing a vector map and a tooltip displaying additional information for map elements using visualization widgets.

In our example, the tooltip will display a weather forecast, atmospheric humidity and pressure, as well as sunrise and sunset times for a few cities in the United States. We will get weather data from the query.yahooapis.com server using an AJAX query.

Let's implement this example step by step.

Step 1

Begin with displaying a map of the United States by adding the dxVectorMap widget with the mapData option, which holds resources for the US map, to your application. The map size will be 1000x700 pixels and its borders will exclude Alaska.

HTML
<script src="usa.js"></script>
<div class='container' style='width: 1000px; height: 700px;'></div>
JavaScript
var map = $(".container").dxVectorMap({
    mapData: DevExpress.viz.map.sources.usa,
    bounds: [-118, 52, -80, 20]
}).dxVectorMap("instance");

The next step is to add a couple of colors to our map.

JavaScript
//...
var paletteIndex = -1;
var map = $(".container").dxVectorMap({
    //...
    background: {
        color: "#1E90FF"
    },
    markerSettings: {
        label: {
            font: {
                color: "#232323"
            }
        }
    },
    areaSettings: {
        palette: "Soft",
        paletteSize: 5,
        borderColor: "none",
        hoverEnabled: false,
        customize: function () {
            return { paletteIndex: (paletteIndex = (paletteIndex + 1) % 5) };
        }
    }
}).dxVectorMap("instance");

Step 2

Continue by addressing map markers. I have chosen about twenty cities in the United States and saved their WOEID to get weather data for them.

Using an AJAX query, we can get the city name, its coordinates, the weather forecast, atmospheric humidity and pressure, and sunrise and sunset times. Once data is received, we can add city markers to the map, which will cause the widget to update. While the widget is being updated, it will display a loading indicator.

JavaScript
//...
var map = $(".container").dxVectorMap({
    //...
}).dxVectorMap("instance");
map.showLoadingIndicator();

var cities = [2459115, 2514815, 2428344, 2383660, 2357024, 2464592, 2379574, 2487129, 2430632, 2424766, 2352824, 2391279, 2487610, 2366355, 2436704, 2442047, 2488042, 2487384, 2490383];

$.ajax({ url: "http://query.yahooapis.com/v1/public/yql?q=select * from weather.forecast where woeid in (" + cities.join(", ") + ")&format=json", dataType: "jsonp" }).done(function (arg) {
    var markers = $.map(arg.query.results.channel, function (ch) {
          return {
              text: ch.location.city,
              coordinates: [Number(ch.item.long), Number(ch.item.lat)],
              forecast: ch.item.forecast,
              humidity: ch.atmosphere.humidity,
              sunriseTime: ch.astronomy.sunrise,
              sunsetTime: ch.astronomy.sunset
          }
    });
    map.option('markers', markers);
});

After these two steps are performed, you can see the US map with city markers.

Rich Tooltip - Map With Markers

Step 3

Now we can customize the tooltip. First, we should decide what markup should be used for the tooltip. I suggest using the following tooltip structure.

Rich Tooltip - Tooltip Layout

Container elements for widgets (gauges and charts) and dynamic content (title, sunrise and sunset times) should have a css class so that jQuery can access them. Our markup is held in the html field of the customizeTooltip option.

CSS
.tooltip-container {
    width: 500px;
    height: 300px;
}
.text {
    color: #232323;
    font-weight: 200;
    text-align: center;
}
.city-title {
    height: 50px;
    font-size: 24px;
}
.pressure-gauge {
    float: left;
    width: 80px;
    height: 230px;
}
.humidity-gauge {
    float: right;
    width: 80px;
    height: 230px;
}
.forecast-chart {
    height: 150px;
}
.time-text {
    height: 45px;
    width: 130px;
    font-weight: 600;
    font-size: 34px;
    text-align: center;
}
.sunrise-time {
    color: #FBC987;
}
.sunset-time {
    color: #FD7888;
}
.left-container {
    margin-top: 5px;
    float: left;
}
.right-container {
    float: left;
    margin-top: 5px;
    margin-left: 20px;
}
.time-container {
    float: left;
    width: 130px;
    height: 65px;
}
.left-margin {
    margin-left: 10px;
}
.top-margin {
    margin-top: 10px;
}
JavaScript
//...
var map = $(".container").dxVectorMap({
    //...
    tooltip: {
        enabled: true,
        customizeTooltip: function (arg) {
            if (arg.type === "marker") {
                return {
                    html: "<div class='tooltip-container'><div class='city-title text'></div><div class='left-container left-margin'><div class='pressure-gauge'></div><div class='humidity-gauge left-margin' ></div></div><div class='right-container'><div class='forecast-chart'></div><div class='top-margin''><div class='time-container'><div class='sunrise-time time-text'></div><div class='text'>Sunrise</div></div><div class='time-container left-margin'><div class='sunset-time time-text'></div><div class='text'>Sunset</div></div></div></div></div>"
                }
            }
        }
    }
}).dxVectorMap("instance");
//...

Step 4

Customize the widgets displayed in a tooltip. There are gauges for displaying information about atmospheric pressure and humidity, as well as a weather forecast chart. For the pressure gauge, we should set its orientation to vertical, and specify title and scale settings. Also, all elements of this gauge should have specific colors. Isolate the appropriate options into a separate object.

JavaScript
var pressureGaugeOptions = {
    title: {
        text: "Pressure,\ninHg",
        font: {
            color: "#232323"
        }
    },
    geometry: {
        orientation: "vertical"
    },
    scale: {
        startValue: 10,
        endValue: 40,
        label: {
            font: {
                color: "#7E8AB6"
            }
        }
    },
    valueIndicator: {
        color: "#7E8AB6"
    }
};
//...
var map...

We can follow the same instructions to customize the second gauge for atmospheric humidity.

JavaScript
//...
var humidityGaugeOptions = {
    title: {
        text: "Humidity,\n%",
        font: {
            color: "#232323"
        }
    },
    geometry: {
        orientation: "vertical"
    },
    scale: {
        label: {
            font: {
                color: "#75C0E0"
            }
        }
    },
    valueIndicator: {
        color: "#75C0E0"
    }
};
//...
var map...

Customize the weather forecast chart. We need one data series with argument and value fields, while the legend should be invisible.

JavaScript
//... 
var chartOptions = {
    series: {
        argumentField: "date",
        valueField: "high",
        color: "#556FA6"
    },
    legend: {
        visible: false
    }
};
//...
var map...

The argument axis requires defining the data type. In our case, it's “datetime”. We can also customize margins and labels for this axis.

JavaScript
//...
var chartOptions = {
    //..
    argumentAxis: {
        argumentType: "datetime",
        valueMarginsEnabled: false,
        tickInterval: {
            days: 1
        },
        label: {
            format: "MonthAndDay",
            overlappingBehavior: {
                mode: "ignore"
            },
            font: {
                color: "#232323",
                weight: 200
            }
        }
    }
};

The value axis also requires defining the data type, which is “numeric” in this case. This axis will have a visible axis line, tickmarks and minor grid lines.

JavaScript
//...
var chartOptions = {
    //...
    valueAxis: {
        valueType: "numeric",
        visible: true,
        tick: {
            visible: true
        },
        minorGrid: {
            visible: true
        },
        tickInterval: 1,
        minorGridCount: 3,
        label: {
            customizeText: function (arg) {
                return arg.valueText + "&#176;F";
            },
            font: {
                color: "#556FA6"
            }
        }
    }
};

As a result of the above-mentioned steps, our widgets will look like the following.

Rich Tooltip - Tooltip Widgets

Step 5

Now we are ready to display a tooltip. We should begin by defining tooltip values within the tooltipShown event handler. For this, we need to pass a handling function to the onTooltipShown option of the map widget. We'll obtain the target marker's data from the event argument and distribute this data among widgets and dynamic content of our tooltip.

JavaScript
//...
var map = $(".container").dxVectorMap({
    //…
    onTooltipShown: function (e) {
        var marker = e.target;

        $(".city-title").text(marker.text);
        $(".sunrise-time").text(marker.sunriseTime);
        $(".sunset-time").text(marker.sunsetTime);

        $(".pressure-gauge").dxLinearGauge(pressureGaugeOptions).dxLinearGauge("instance").value(marker.pressure);
        $(".humidity-gauge").dxLinearGauge(humidityGaugeOptions).dxLinearGauge("instance").value(marker.humidity);
        $(".forecast-chart").dxChart(chartOptions).dxChart("instance").option("dataSource", marker.forecast);
    }
}).dxVectorMap("instance");
//...

Step 6

To avoid memory leaks, we must implement a function that will dispose of a tooltip's content and pass this function to the onTooltipHidden option.

JavaScript
//...
var $tooltipContainer;
var map = $(".container").dxVectorMap({
    //...
    onTooltipShown: function (e) {
        //...
        $tooltipContainer = $(".tooltip-container");
    },
    onTooltipHidden: function () {
        $tooltipContainer.remove();
    }
}).dxVectorMap("instance");
//...

Now, take a look at the result.

Rich Tooltip - Result

Finally, let’s recap.

Using an asynchronous AJAX query, we can display data in marker tooltips.

Once a pointer appears over a marker, a tooltip is shown and fires the appropriate event. We handle this event, apply the predefined markup to a tooltip and distribute data associated with the marker among widgets and dynamic content.

Once a tooltip is hidden, we dispose of a tooltip’s content to avoid memory leaks.

You can follow the same instructions to display sophisticated tooltips for any data visualization widget. For example, you can create a bar chart with a tooltip whose markup includes a pie chart.

The use of custom HTML markup enables you to easily implement flexible customized tooltips that display the required information regardless of its complexity.