top of page

Plot DVHs with OxyPlot (Part 3: Customize the Plot's Look)

The default look of the plots you’ve seen in Part 1 and Part 2 of this blog series are pretty decent. But you may want to improve your plot's appearance by adding a legend, changing the line colors, axis font size, border thickness, and other plot properties to your liking. In this section, you’ll see how OxyPlot lets you make many customizations to change the look of your plots. You’re going to modify the script from the last post to play with these customizations.


The source code related to this post is in the customize branch of the DvhPlotWithOxyPlot repository on GitHub.


Legend


By default, a legend is shown on a plot if the titles of the series have been set. You can set each series title when the series is created. Update the CreateDvhSeries method to set the title:


private Series CreateDvhSeries(string structureId, DVHData dvh)
{
    var series = new LineSeries
    {
        Title = structureId,
        Tag = structureId
    };
    var points = dvh.CurveData.Select(CreateDataPoint);
    series.Points.AddRange(points);
    return series;
}

The line series title is set to the ID of the structure it represents. If you run the script now, you’ll see the legend displayed on the top right corner of the plot, inside the plot’s borders.


The PlotModel object has many properties you can change to customize its look, including the legend. You’re going to change the legend’s position so that it’s shown outside and below the plot horizontally. You’ll also add a border around it and change the background to a light gray. Update the CreatePlotModel method as follows:


private PlotModel CreatePlotModel()
{
    var plotModel = new PlotModel();
    AddAxes(plotModel);
    SetupLegend(plotModel);
    return plotModel;
}

private void SetupLegend(PlotModel pm)
{
    pm.LegendBorder = OxyColors.Black;
    pm.LegendBackground = OxyColor.FromAColor(32, OxyColors.Black);
    pm.LegendPosition = LegendPosition.BottomCenter;
    pm.LegendOrientation = LegendOrientation.Horizontal;
    pm.LegendPlacement = LegendPlacement.Outside;
}

The main change is the addition of the SetupLegend method. It sets the legend-related properties of the PlotModel to custom values. Some of the types for these properties are enumerations (enum in C#), so the possible choices you can set them to are clearly defined (for example, the LegendPlacement property can only be Inside or Outside).


For a property of type OxyColor, you can set it to a named color provided by the OxyColors class, or to a specific color using any of the various static methods in the OxyColor class. One interesting method is FromAColor, which takes an alpha (or opacity) value and a base color. For example, the LegendBackground is set to a base color of black with an alpha value of 32 (or only 12.5% opacity), so it’ll show as a very light gray.


If you run the script and select a few structures, the plot should now look something like this:



Axes

Currently, the axis titles appear too small and too close to the axes. You’re going to increase the font size and make them bold. You’ll also add more space between the title and the axes. Finally, you’ll add the major and minor grid lines so it’s easier to know the dose and volume values for each line. Modify the AddAxes method as follows:


private static void AddAxes(PlotModel plotModel)
{
    plotModel.Axes.Add(new LinearAxis
    {
        Title = " Dose  [Gy]",
        TitleFontSize = 14,
        TitleFontWeight = FontWeights.Bold,
        AxisTitleDistance = 15,
        MajorGridlineStyle = LineStyle.Solid,
        MinorGridlineStyle = LineStyle.Solid,
        Position = AxisPosition.Bottom
    });

    plotModel.Axes.Add(new LinearAxis
    {
        Title = " Volume  [cc]",
        TitleFontSize = 14,
        TitleFontWeight = FontWeights.Bold,
        AxisTitleDistance = 15,
        MajorGridlineStyle = LineStyle.Solid,
        MinorGridlineStyle = LineStyle.Solid,
        Position = AxisPosition.Left
    });
}

To get your plot to look right, you may need to play around with different values. Unfortunately, the OxyPlot documentation does not always tell you what the default values are. For example, it may not be clear that a font size of 14 is an increase in size until you try it. Here’s what the plot looks like after making these changes:



There are many more axis settings you can change, such as the major and minor step sizes, the minimum and maximum axis limits, whether to reverse the axis, the positions of the tick marks (inside, center, or outside), and the colors for the title, axes, tick marks, and grid lines. I suggest you explore the available properties in Visual Studio.


Plot Area


To show you that OxyPlot is highly configurable, you’re going to make the plot look more like Eclipse’s DVH plot. In other words, you’re going to change the plot’s background color to black and match each DVH line color to the corresponding structure’s color in Eclipse. You’re also going to change the line thickness and type to emphasize certain structures.


The PlotModel class has the properties Background and PlotAreaBackground. Background refers to the entire figure, including the axes title, labels, and plot. PlotAreaBackground refers to only the plot area. Modify the CreatePlotModel method to make the plot area 90% black (I found that 100% black was too strong):


private PlotModel CreatePlotModel()
{
    var plotModel = new PlotModel
    {
        PlotAreaBackground = OxyColor.FromAColor(230, OxyColors.Black)
    };
    AddAxes(plotModel);
    SetupLegend(plotModel);
    return plotModel;
}

Because the background is so dark now, you need to lighten the color of the grid lines; otherwise, they won’t be visible. Update the AddAxes method as follows:


private static void AddAxes(PlotModel plotModel)
{
    plotModel.Axes.Add(new LinearAxis
    {
        Title = " Dose  [Gy]",
        TitleFontSize = 14,
        TitleFontWeight = FontWeights.Bold,
        AxisTitleDistance = 15,
        MajorGridlineColor = OxyColor.FromAColor(64, OxyColors.White),
        MinorGridlineColor = OxyColor.FromAColor(32, OxyColors.White),
        MajorGridlineStyle = LineStyle.Solid,
        MinorGridlineStyle = LineStyle.Solid,
        Position = AxisPosition.Bottom
    });

    plotModel.Axes.Add(new LinearAxis
    {
        Title = " Volume  [cc]",
        TitleFontSize = 14,
        TitleFontWeight = FontWeights.Bold,
        AxisTitleDistance = 15,
        MajorGridlineColor = OxyColor.FromAColor(64, OxyColors.White),
        MinorGridlineColor = OxyColor.FromAColor(32, OxyColors.White),
        MajorGridlineStyle = LineStyle.Solid,
        MinorGridlineStyle = LineStyle.Solid,
        Position = AxisPosition.Left
    });
}

The main change is to add the grid line colors. They’re now based on white with an opacity of about 25% for the major grid lines and 12.5% for the minor grid lines. The default was based on black with the same opacities.


Now you’re going to change the color, thickness, and style of the DVH lines. The color will be obtained from ESAPI since every Structure object has a Color property. The thickness and style will be determined from the contents of the structure ID. The rule is that if the structure ID contains "PTV," it should have a thicker line. If it contains "_R" (representing the organ on the right side), it should have a dashed line. This will help differentiate it from the left organ since they are often the same color in Eclipse. Update the CreateDvhSeries method to the following:


private Series CreateDvhSeries(string structureId, DVHData dvh)
{
    var series = new LineSeries
    {
        Title = structureId,
        Tag = structureId,
        Color = GetStructureColor(structureId),
        StrokeThickness = GetLineThickness(structureId),
        LineStyle = GetLineStyle(structureId)
    };
    var points = dvh.CurveData.Select(CreateDataPoint);
    series.Points.AddRange(points);
    return series;
}

private OxyColor GetStructureColor(string structureId)
{
    var structures = _plan.StructureSet.Structures;
    var structure = structures.First(x => x.Id == structureId);
    var color = structure.Color;
    return OxyColor.FromRgb(color.R, color.G, color.B);
}

private double GetLineThickness(string structureId)
{
    if (structureId.ToUpper().Contains("PTV "))
        return 5;
    return 2;
}

private LineStyle GetLineStyle(string structureId)
{
    if (structureId.ToUpper().Contains("_R"))
        return LineStyle.Dash;
    return LineStyle.Solid;
}

The Color property of the LineSeries is assigned to the color returned by the GetStructureColor method. In this method, the ESAPI structure is obtained from the plan, as you’ve seen before. Event though the structure has a Color property, you can’t return it directly because it’s not of the expected OxyColor type. To convert it to an OxyColor, you use the FromRgb method to create a new OxyColor object with its red, green, and blue components taken from the structure’s color.


Next, the StrokeThickness of the line is determined using the GetLineThickness method. This method checks whether the structure ID contains the string "PTV." Notice that the ID is first converted to uppercase in order to account for case differences (you want structure IDs that contain "Ptv," "ptv," etc. to be considered). The underlying structure ID is not actually changed, as the ToUpper method only returns the uppercase string but doesn’t change it. If the structure ID does contain "PTV", a thickness of 5 is returned. For all other structures, a thickness of 2 is returned.


Finally, the LineStyle property is assigned using the GetLineStyle method. It returns a line style of Dash if the structure ID (uppercase) contains "_R". Otherwise, it returns the a Solid line style. As mentioned, the "_R" is one convention for specifying the structure on the right side. Your clinic may use a different convention, such as "R_" or "Right-".


If you run the script now, the plot should look like this:



OxyPlot has many properties you can change to customize your plot the way you want. You’ve only seen a fraction of these properties. I encourage you to explore the PlotModel, Axis, and LineSeries classes to see what else you can customize. The other types of series (like column and heatmap) also contain their own set of properties.

Related Posts

See All

ESAPI Essentials 1.1 and 2.0

A few months ago, I introduced ESAPI Essentials—a toolkit for working with ESAPI (available via NuGet). I've recently added one major feature: asynchronous access to ESAPI for binary plugin scripts. Y

Announcement: ESAPI Subreddit

A few months ago, Matt Schmidt started the ESAPI subreddit. It's another useful resource for finding and discussing ESAPI-related topics. According to the description, This community is to post and di

Dump All Patient Data from ESAPI

If, for whatever reason, you need to dump out all of the data for a patient from ESAPI, there's a quick and dirty way to do it. Well, there are probably several ways to do it, but here's one I've foun

bottom of page