September 2009

Volume 24 Number 09

Foundations - Charting with DataTemplates

By Charles Petzold | September 2009

For WPF programmers, one major revelation about the power of the DataTemplate comes with a demonstration of how a little piece of XAML (Figure 1) can turn business objects into bar charts (Figure 2).

Following the exhilaration at seeing the chart's rendering, the technique unfortunately seems to "stall out." Enhancing the simple bar chart becomes awkward, and using data templates for other types of common charts -- such as pie charts and line charts -- seems nearly impossible. That's too bad, because using data templates in this way is simply too powerful a technique to be relegated to unadorned bar charts.

In this article, I will show you a few techniques to get around the apparent limitations. There will be some code involved to help out with the details that XAML can't manage by itself, but the code will often be generalized enough to use for other applications. The goal is always to keep enough of the visual design in XAML so that changes and experimentation are easy.

Of course, the DataTemplate isn't a universal solution for charting. You might find an actual charting package more suited to your needs. You should also be aware that performance issues might result from an ItemsControl or ListBox with thousands of data items and a DataTemplate with many data bindings. I examined solutions to that problem in the article "Writing More Efficient ItemsControls" in the March 2009 issue of MSDN Magazine (msdn.microsoft.com/en-us/magazine/dd483292.aspx).

Basic Concepts

The downloadable code for this article consists of one Visual Studio solution named ChartingWithDataTemplates, containing one DLL project named ChartingLib and nine WPF application projects. The file shown in Figure 1 is the Window1.xaml file from the SimpleBarChart project.

Some of the classes in the ChartingLib DLL take the form of business objects to supply the data for the sample programs. For this first example, the Doodad class implements INotifyPropertyChanged and includes the properties ModelName and BaseCost. The DoodadCollection class simply derives from ObservableCollection<Doodad> and contains a collection of Doodad objects. The DoodadPresenter class instantiated as a XAML resource in Figure 1 defines a DoodadCollection property, creates all the random data, and includes a timer to change the data dynamically over time.

Figure 1 XAML to Display a Bar Chart

<Window x:Class="SimpleBarChart.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:charts="clr-namespace:ChartingLib;assembly=ChartingLib"
Title="Simple Bar Chart">
<Window.Resources>
<charts:DoodadPresenter x:Key="doodadPresenter" />
</Window.Resources>
<ItemsControl
ItemsSource="{Binding
Source={StaticResource doodadPresenter},
Path=DoodadCollection}"
VerticalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Height="{Binding BaseCost}"
VerticalAlignment="Bottom"
Fill="Blue"
Margin="3">
<Rectangle.ToolTip>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ModelName}" />
<TextBlock
Text="{Binding BaseCost,
StringFormat=’: {0:C0}’}" />
</StackPanel>
</Rectangle.ToolTip>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Window>

It is important that the business object used for the items implement the INotifyPropertyChanged interface so that changes in the underlying data are reflected visually in the chart. The collection object should implement ICollectionChanged so that changes to the collection itself -- such as items added to or removed from the collection -- are reflected in the ItemsControl. The ObservableCollection class implements ICollectionChanged and is a very popular choice for this purpose.


Figure 2 Bar Chart Displayed by the SimpleBarChart Project

The sample programs for this article use an ItemsControl for displaying the items. You can alternatively use a ListBox if you need to provide a way for the user to select an item. Each item is displayed based on the DataTemplate set to the ItemTemplate property of the ItemsControl. The DataTemplate contains bindings to the properties of the object stored in the collection, in this example Doodad.

Notice the ToolTip, which also contains bindings to the properties of the Doodad class. The ToolTip is definitely the easiest way to include textual information about each item in the chart.

Besides the DataTemplate, you'll also need to set the ItemsPanel property of the ItemsControl to an ItemsPanelTemplate containing an appropriate panel. For bar charts arranged horizontally, you can use either a UniformGrid with the Rows property explicitly set to 1, or a StackPanel with the Orientation property set to Horizontal.

The single-row UniformGrid and the horizontal StackPanel seem similar, but they actually have quite different layout behaviors. The single-row UniformGrid sizes its children so that they all have the same width and fit within the dimensions of the panel. The horizontal StackPanel bases its own size on the composite width of its children. Use UniformGrid if you want to maintain a constant width for the chart regardless of how many items are displayed. Use StackPanel if you want to maintain a constant size on each bar, which you'll need to explicitly set. A chart that uses a StackPanel for its ItemsPanel will change size depending on the number of items, so you might want to put the ItemsControl in a ScrollViewer to prevent the chart from taking up too much space on the screen.

Utility and Aesthetics

The SimpleBarChart program displays bars indicating the value of the BaseCost property defined by the Doodad class. The Doodad class also defines AdditionalCost and ExtraCost properties, so one possible enhancement is making a stacked bar chart to display all three of these properties, as demonstrated in the StackedBarChart program. The DataTemplate is shown in Figure 3 and the result in Figure 4.

Notice that a StackPanel encloses the three Rectangle elements; it is this StackPanel that is given the VerticalAlignment and Margin properties. I've eliminated the ToolTip just to make the XAML simpler; if you add it back in, it should be attached to the StackPanel rather than to any of the Rectangles.

Figure 3 DataTemplate for a Stacked Bar Chart

<DataTemplate>
<StackPanel VerticalAlignment="Bottom"
Margin="3">
<Rectangle Height="{Binding ExtraCost}"
Fill="Red" />
<Rectangle Height="{Binding AdditionalCost}"
Fill="Green" />
<Rectangle Height="{Binding BaseCost}"
Fill="Blue" />
</StackPanel>
</DataTemplate>

The primary colors are a bit garish, but you can easily tone them down by putting the StackPanel in a single-cell grid, which then contains another Rectangle to overlay the other three. This fourth Rectangle is colored with a LinearGradientBrush with various degrees of transparent white. The StackedBarChartWithGradientBrush project uses this technique for the visuals in Figure 5.

One of the great benefits that result from defining the bar chart visuals in XAML is the ease of replacing the bar with some other type of graphic, or of coloring the Rectangle with an image brush or drawing brush. Suppose the Doodad is actually a toy robot and you want to use a graphical representation of that toy in the bar chart. Figure 6 shows a DataTemplate containing a Path with a simple figure defined by coordinates between 0 and 1. A ScaleTransform gives each of these figures a uniform width. The horizontal scaling is bound to the BaseCost property. The result is shown in Figure 7.

Is it possible to use different colors in the bar chart? Absolutely. The Doodad class contains an integer property named ProductType with values from 0 through 3. You can base the color of the bar on this ProductType by providing a simple binding converter that converts an integer to a Brush. The IndexToBrushConverter defines a public property named Brushes of type Brush array:

public Brush[] Brushes { get; set; }

To keep this converter generalized, the array is not required to have a fixed number of brushes. Instead, a modulo operation is applied to the incoming integer value based on the size of the array:

return Brushes[(int)value % Brushes.Length];


Figure 4 The StackedBarChart Program Display

In the XAML file, the IndexToBrushConverter is instantiated as a resource and initialized with an array of brushes:

<charts:IndexToBrushConverter x:Key="brushConv">
<charts:IndexToBrushConverter.Brushes>
<x:Array Type="Brush">
<x:Static Member="Brushes.Orange" />
<x:Static Member="Brushes.Purple" />
<x:Static Member="Brushes.SkyBlue" />
<x:Static Member="Brushes.Pink" />
</x:Array>
</charts:IndexToBrushConverter.Brushes>
</charts:IndexToBrushConverter>

The Fill property on the Rectangle element is bound to the ProductType property of Doodad but converted to one of these brushes:

Fill="{Binding ProductType,
Converter={StaticResource brushConv}}"

Perhaps you want a series of repeating colors -- not to indicate anything about the item, but just for a little variety. For example, you might use orange for the first item, the fifth, the ninth, and so on.

At first this seems impossible, because the DataTemplate would need access to the index of the particular data item in the collection. But it's easy enough to include this information in the business object. For example, the Doodad class might define a property named ChildIndex, and the DoodadCollection property would be responsible for setting that property for each of its members.

Another option requires deriving the child index from the visual tree and passing that to the IndexToBrushConverter. This job requires a little knowledge about the visual tree that makes up the ItemsControl. An ItemsControl begins with a Border, which contains an ItemsPresenter, which contains the panel you specify in the ItemsPanelTemplate that you set to the ItemsPanel property of the ItemsControl. This panel contains a number of children, equal to the number the items in the data collection; these children are of type ContentPresenter, where the Content property is set to the visual tree you've defined by the DataTemplate set to the ItemTemplate property of the ItemsControl.


Figure 5 The StackedBarChartWithGradientBrush Display

The ChartingLib DLL contains a class named ChildIndexProvider that derives from Decorator, defines a dependency property named Index, and installs a handler for the LayoutUpdated event. This handler checks if its parent is a ContentPresenter, and if the parent of that ContentPresenter is a Panel. If so, it then finds the child index of that ContentPresenter within the Panel.

Figure 6 A DataTemplate with a Path

<DataTemplate>
<Path VerticalAlignment="Bottom"
Margin="2"
Stroke="Black"
Fill="Silver">
<Path.Data>
<PathGeometry
Figures="M 0 0 L 1 0, 1 0.2, 0.6 0.2, 0.6 0.25,
1 0.25, 1 0.6, 0.9 0.6, 0.9 0.3, 0.8 0.3,
0.8 0.95, 1 0.95, 1 1, 0.55 1, 0.55 0.5,
0.45 0.5, 0.45 1, 0 1, 0 0.95, 0.2 0.95,
0.2 0.3, 0.1 0.3, 0.1 0.6, 0 0.6, 0 0.25,
0.4 0.25, 0.4 0.2, 0 0.2 Z
M 0.2 0.05 L 0.4 0.05, 0.4 0.1, 0.2 0.1 Z
M 0.6 0.05 L 0.8 0.05, 0.8 0.1, 0.6 0.1 Z
M 0.2 0.15 L 0.8 0.15">
<PathGeometry.Transform>
<ScaleTransform ScaleX="25"
ScaleY="{Binding BaseCost}" />
</PathGeometry.Transform>
</PathGeometry>
</Path.Data>
</Path>
</DataTemplate>

The ChildIndexPresenter is intended to be the root element of a DataTemplate, as demonstrated by the BarChartWithRepeatingColors project:

<DataTemplate>
<charts:ChildIndexProvider Name="indexProv">
...
</charts:ChildIndexProvider>
</DataTemplate>

The Fill property of the Rectangle is then bound to the Index property of this element using the IndexToBrushConverter:

Fill="{Binding ElementName=indexProv,
Path=Index,
Converter={StaticResource brushConv}}"

A Fake 3-D Effect

For a really fancy bar chart, you might want to use some 3-D effects for the visuals. It is indeed possible for the DataTemplate to have a root element of type Viewport3D and for each data item to be represented by an entire 3-D scene. But this seems a little extravagant to me, and probably not quite what you want.

What you really want is a single 3-D scene with a single camera and lighting, where each data item is a ModelVisual3D in that scene. But this route would require some major re-architecting of the ItemsControl and its templates. A better solution is to fake the 3-D visuals by using a couple of additional rectangles with skew transforms, as shown in Figure 8.


Figure 7 The BarChartWithFigure Display


Figure 8 The Fake3DBarChart Display

Each of the bars in the chart consists of three rectangles, one in the front, one on the right side, and one on top, so the overall effect looks like a solid block. The overlapping of the items is governed by the order of the blocks in the panel. To create the effect in Figure 8, you can't simply cobble together multiple rectangles, because the panel containing these blocks will lay them out based on the overall width of each block, and they will not overlap.

There are basically two ways you can persuade an element to appear outside the boundaries recognized by the layout system. The first and most obvious is to apply a RenderTransform. As the name implies, RenderTransform changes the rendering of a visual object without affecting how it's interpreted in layout.

But RenderTransform by itself is sometimes awkward. In this particular example, the untransformed rectangles for the right side and top must be defined so as not to exceed the width of the front rectangle.

Another approach to persuading the layout system to ignore certain elements is to make them children of a Canvas. Unless a Canvas is given an explicit height and width, it always reports zero dimensions to the layout system. For example, if you put a Rectangle and a Canvas in a single-cell Grid, then any child of that Canvas will effectively be ignored by the layout system.

Figure 9 shows the DataTemplate for the Fake3DBarChart program. A single-cell Grid is given an explicit Width and a Height based on the BaseCost property of the Doodad object. That Grid contains a Rectangle for the front of the block and a Canvas. The Canvas contains two additional single-cell Grids for the right side and top. These Grids have RenderTransforms applied for the skewing effect, but even the untransformed Grids are ignored by the layout system.

Notice how the colors are shaded: The basic color is a resource in the DataTemplate named "brush." The front Rectangle gets that color straight up. The right side is actually two Rectangles within the single-cell Grid. The first one is colored with the "brush" resource; the second one lies on top of the first and is colored with a partially transparent black brush. This causes the overall color to be darkened. For the top, one rectangle colored with "brush" is covered with a partially transparent white brush, making the overall color lighter. This is an excellent technique to create different shades of a particular fixed color or a color obtained through a data binding.

The Scaling Issue

All the examples I've shown so far have been based on the Doodad class, which very conveniently has sales figures in the low two figures and hence is just about ideal for binding directly to the heights of Rectangles (or other elements) in a bar chart.

Values of real data are generally not so compliant, and you really need some way to convert data values to appropriate display dimensions. You'll probably also want to display a little scale at the side of the chart that shows the actual values corresponding to the heights of the bars. Moreover, if the maximum value of some collection of data items is 76,543 (for example), you probably want the scale to go up to a rounded value such as 100,000.

Issues like these often cause programmers to abandon the DataTemplate approach to charting and embrace custom charting controls. But there's no reason to be so hasty. There are a couple of solutions to scaling bars in a chart. If you know beforehand the approximate range of values you'll be charting, you can apply a ScaleTransform in the XAML. For example, if the values you need to graph range from 0 to 10, you might apply a ScaleTransform with a ScaleY property set to 15 to create bars up to 150 units tall.

Another approach (which I'll be demonstrating here) requires additional properties to be added to the business objects. The scaling calculation is performed in code, but the scaled values can be accessed in XAML.

The Gizmo class in ChartingLib has a Name property and a Revenues property, but it also contains properties named Scaling Factor and ScaledRevenues. Whenever Revenues or ScalingFactor changes, the class simply multiplies the two values and sets the result to ScaledRevenues. (ScaledRevenues is really a height in device-independent units, but that's a detail that Gizmo really needn't bother itself with.)

Figure 9 The Fake3DBarChart Data Template

<DataTemplate>
<DataTemplate.Resources>
<SolidColorBrush x:Key="brush" Color="#4080FF" />
</DataTemplate.Resources>
<Grid VerticalAlignment="Bottom"
Margin="4"
Width="20"
Height="{Binding BaseCost}">
<!-- Front -->
<Rectangle Fill="{StaticResource brush}" />
<Canvas>
<!-- Right Side -->
<Grid Canvas.Left="20"
Height="{Binding BaseCost}"
Width="25"
VerticalAlignment="Bottom">
<Grid.RenderTransform>
<SkewTransform AngleY="-30" />
</Grid.RenderTransform>
<Rectangle Fill="{StaticResource brush}" />
<Rectangle Fill="#40000000" />
</Grid>
<!-- Top: 14.4 = 25 * tan(30) -->
<Grid Canvas.Top="-14.4"
Height="14.4"
Width="20">
<Grid.RenderTransform>
<SkewTransform CenterY="14.4" AngleX="-60" />
</Grid.RenderTransform>
<Rectangle Fill="{StaticResource brush}" />
<Rectangle Fill="#40FFFFFF" />
</Grid>
</Canvas>
</Grid>
</DataTemplate>

Somebody has to keep track of the maximum value of the Revenues properties of all the Gizmo objects. Probably the best spot for this logic is the GizmoCollection class that derives from ObservableCollection<Gizmo>. Because Gizmo itself implements the INotifyPropertyChanged interface, GizmoCollection can install PropertyChanged event handlers on all the Gizmo items in the collection. If the Revenues property on any Gizmo object changes, GizmoCollection calculates a new maximum that it stores in a property named MaximumRevenues. GizmoCollection also defines a MaximumRevenuesChanged event that it fires whenever MaximumRevenues changes.

The remainder of the logic is handled in the GizmoPresenter class. The GizmoPresenter class defines a property named Gizmos of type ObservableCollection<Gizmo>, creates that collection, and installs a handler for the MaximumRevenuesChanged event.
GizmoPresenter defines a property named DisplayHeight, which will be set in XAML to the maximum desired height of the bars in the bar chart. It also defines two read-only properties, named RevenuesBase and HalfRevenuesBase, intended to be of assistance in creating a visual scale for the bar chart.

When the MaximumRevenuesChanged event is fired, the GizmoPresenter gets the new MaximumRevenues value from the GizmoCollection object and calculates a RevenuesBase value. RevenuesBase is basically MaximumRevenues rounded up to some value with a lot of zeroes. It's the Revenues value that corresponds to DisplayHeight. But you probably don't want to restrict RevenuesBase to a power of 10 (for example, 10, 100, 1000, and so forth) because if MaximumRevenues is 1,001, RevenuesBase would be 10,000, and the tallest bars would only be about one-tenth the height of the chart. It's preferable to use a geometric sequence, such as a 1-2-5 series, so that Revenues base will equal a value from the series 1, 2, 5, 10, 20, 50, 100, 200, 500, and so forth.

The ChartingLibHelper class has a static method named GetRoundedMaximum to help out with this calculation. (It's fairly simple, but logarithms are involved.) You simply pass in a calculated maximum and an ordered series of values between 1 and 10, for example, 2 and 5. GizmoPresenter uses this RevenuesBase value and the DisplayHeight to set the ScalingFactor value in each of the Gizmo objects. The ScaledBarChart project demonstrates this technique.

The height of each bar is bound not to the Revenues property of Gizmo but to the ScaledRevenues property. In addition, enough information is provided from GizmoPresenter to construct a simple scale at the left entirely in XAML. The result is shown in Figure 10.

If you run this program and wait for the random-number generator to make one of the Revenues values greater than 100,000, you'll see the scale shift to showing a maximum of 200,000, and all the bars decrease in height by half.


Figure 10 The ScaledBarChart Display

Tackling the Pie Chart

At first a pie chart seems quite unsuited to realization by an ItemsControl, but it's really not too bad. You don't even need a custom panel, but you will need a custom element to display the slices of the pie.

The PieSlice class in ChartingLib derives from Shape using techniques I discussed in my column "Vector Graphics and the WPF Shape Class" in the March 2008 issue of MSDN Magazine (msdn.microsoft.com/en-us/magazine/cc337899.aspx). It has a Center property of type Point, a Radius property of type double, and StartAngle and SweepAngle properties, also of type double. The StartAngle is relative to a vertical line extending up from the center, with positive angles going clockwise.

PieSlice also overrides the MeasureOverride method to report a size as if the slice encompassed the entire circle and not just one part of it. This allows all the slices to be put into a single-cell Grid.

The pie chart I'll be showing uses a DataTemplate to display seven objects of type Product. These are collected in a class named ProductLineup that derives from ObservableCollection<Product>. The Product class has Name and Sales properties, as well as two properties that help out with the display: a Percentage property, which is a percent of the total sales, and an AccumulatedPercentage, which is the accumulated percentage of sales of items in the collection preceding this item. These properties are set by the collection class. Obviously, AccumulatedPercentage is intended to be bound to the StartAngle of the pie slice, and Percentage is bound to the Sweep angle, as shown in the portion of the DataTemplate shown in Figure 11.

Figure 11 DataTemplate for a Pie Chart

<DataTemplate>
<charts:ChildIndexProvider Name="indexProvider">
<Grid>
<charts:PieSlice
Name="pieslice"
StartAngle="{Binding Accumulated,
Converter={StaticResource angleConv}}"
SweepAngle="{Binding Percentage,
Converter={StaticResource angleConv}}"
Fill="{Binding ElementName=indexProvider,
Path=Index,
Converter={StaticResource brushConv}}"
Stroke="Black"
StrokeThickness="1"
Center="200 200"
Radius="200">
</charts:PieSlice>
...
</Grid>
</charts:ChildIndexProvider>
</DataTemplate>

The "angleConv" resource key references the PercentageToAngleConverter that simply multiplies values by 360. The ItemsPanelTemplate is a single-cell Grid.

There are no ToolTips defined here. Instead, I wanted actual labels. For this feature, I added a read-only property in PieSlice named CenterAngle. Its value is calculated as the StartAngle plus one-half the SweepAngle. It's fairly easy to add a Line element and a TextBlock to the template and rotate them by CenterAngle degrees, but you'll get some upside-down text, and that's not good. You really want the text to be consistently horizontal.
I decided another piece of code was in order, a Decorator derivative called CircleShifter that is intended to arrange a child element around a circle. CircleShifter has a property named BasePoint of type Point and another named RotateTransform of type RotateTransform. CircleShifter positions its child at BasePoint rotated by RotateTransform -- but differently if the Angle property of the RotateTransform is between 0 and 180 degrees (the right of the pie) or between 180 and 360 degrees (the left of the pie). Figure 12 shows the additional XAML and Figure 13 shows the result.

If the slices get too small, there's no logic to prevent the labels from running into each other. However, CircleShifter might be enhanced so that labels toward the top and bottom of the pie chart are more centered.

Figure 12 DataTemplate for Pie Chart Labels

<DataTemplate>
<charts:ChildIndexProvider Name="indexProvider">
<Grid>
...
<Canvas>
<Line X1="200" Y1="50" X2="200" Y2="-25" Stroke="Black">
<Line.RenderTransform>
<RotateTransform
Angle="{Binding ElementName=pieslice,
Path=CenterAngle}"
CenterX="200" CenterY="200"/>
</Line.RenderTransform>
</Line>
<charts:CircleShifter BasePoint="200 -25">
<charts:CircleShifter.RotateTransform>
<RotateTransform
Angle="{Binding ElementName=pieslice,
Path=CenterAngle}"
CenterX="200" CenterY="200"/>
</charts:CircleShifter.RotateTransform>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Sales,
StringFormat=’: {0:C}’}" />
</StackPanel>
</charts:CircleShifter>
</Canvas>
</Grid>
</charts:ChildIndexProvider>
</DataTemplate>

It's tempting to try to derive some transforms to make an "exploded" pie chart, where one or more slices are pulled away from the center. This feature is probably best handled within the PieSlice class itself. An additional property would indicate an offset from the center (for purposes of the MeasureOverride method), and a Boolean property would govern whether that offset should actually be applied to the rendered slice.


Figure 13 The PieChart with Labels Display

A 3-D pie chart can be desirable, but it presents some challenges. Visually, the best results would be obtained from putting all the pie slices in a single Viewport3D, but as I mentioned earlier, that doesn't allow you to use an ItemsControl with a DataTemplate. The DataTemplate itself can be a Viewport3D containing a GeometryModel3D defining a 3-D pie slice, but these multiple Viewport3D elements will be stacked from background to foreground in the same order as their child indices in the Grid. The only foolproof way to avoid odd overlapping effects is to explode the slices sufficiently from the center so that they don't overlap at all.

The fake 3-D effect I used for the bar chart was simplified by the uniform shape of the sides and tops and the uniform left-to-right overlapping. A fake 3-D effect might be implemented in the PieSlice class, but each slice would need to look a little different depending on its position, and the overlapping would still be a problem.

Connecting the Points

When implementing a line chart, it really becomes questionable whether an ItemsControl and DataTemplate are the way to go. The big problem is that a line chart needs to deal with two variables, and the data must be scaled both horizontally and vertically. Also, it's often desirable to connect the points with a line or smoothed curve, which involves something external to each DataTemplate getting information from all the DataTemplates. Still, the advantage of defining the visual elements of the line chart entirely in XAML makes the effort worthwhile.

Next, I'll explore the code support necessary to make a line-charting DataTemplate a reality.


Charles Petzold is a longtime contributing editor for MSDN Magazine. His most recent book is "The Annotated Turing: A Guided Tour Through Alan Turing's Historic Paper on Computability and the Turing Machine" (Wiley, 2008). His Web site is charespetzold.com.