A Bar Chart With Multiple Axes and a Legend in WPF

In this post we will create a bar chart with multiple series and two Y-axes. We will do this exclusively in XAML, with the only exception a small code snippet that assigns one of the Y-axis to one of the bar series.

I. Setup of the Project.

We create a new WPF project. The necessary libraries are MindFusion.Charting.Wpf and MindFusion.Licensing. You can add them manually or drag the Bar chart control from the Visual Studio toolbox. In both cases your Window tag should have:

 xmlns:my="clr-namespace:MindFusion.Charting.Wpf;assembly=MindFusion.Charting.Wpf" 

Then we create the bar chart. If you’ve dragged the bar chart control the XAML code has been auto generated.

<my:barchart bartype="Vertical" title="Product Statistics" name="barChart">  
</my:barchart>

II. The Axes

The chart has two Y-axes. We create a new AxesCollection and assign it to the YAxes property:

<my:barchart.yaxes>
     <my:axescollection>
         <my:axis horlabelalignment="Left" titleoffset="10" minvalue="0" maxvalue="80" tick="2" title="Cost of Raw Materials" titlerotationangle="270" interval="8" labeltype="AutoScale">
          </my:axis>
  </my:axescollection>
</my:barchart.yaxes>

Let’s explain the code above line by line. First, the HorLabelAlignment property specifies how the labels will be aligned relative to the axis. If this was an Y2-axis, we would need to set the property to “Right”. Since this is an Y-axis and we want the labels to appear left to the axis, we align them “Let”. The TitleOffset, TitleRotationANgle and Title properties all specify how the label appears – it has an offset of 10 pixels measured from the longest label at the axis (if present). It is rotated so that it doesn’t take much space and its value is “Cost of Raw Materials”.

The MinValue, MaxValue and Interval properties define the axis – it’s start and end values and the span between each pair of adjacent intervals. Tick specifies the length of the ticks – the small lines drawn at each interval point. You can hide them by setting the Tick property to 0.

Finally, the LabelType property. It takes one of the LabelType enumeration values and sets what kind of labels will be drawn at the axis. This Y-axis will show the scale labels.

Then, we add the code for the second Y-axis. We just place the following XAML:

<my:axis horlabelalignment="Left" labels="{StaticResource YLabels}" titleoffset="10" minvalue="0" maxvalue="10" title="Marketing Costs" titlerotationangle="270" interval="1" tick="2" customlabelposition="AutoScalePoints" labeltype="CustomText">
     </my:axis>   

Most of the properties are the same as with the previous axis. The first difference is the value of the LabelType property. “Custom Text” means we will provide the labels for the axis. We create them as a static resource of strings:

<x:array x:key="YLabels" type="sys:String">
        <sys:string>0%</sys:string>
        <sys:string>7%</sys:string>
        <sys:string>6.5%</sys:string>
        <sys:string>6.3%</sys:string>
        <sys:string>6.2%</sys:string>
        <sys:string>5.8%</sys:string>
        <sys:string>5.5%</sys:string>
        <sys:string>5.1%</sys:string>
        <sys:string>4.9%</sys:string>
        <sys:string>4.5%</sys:string>
        <sys:string>4.2%</sys:string>
     </x:array>

Then we assign them to the Labels property. The CustomLabelPosition defines where the labels will be drawn – at the position of the data points of the series, bound to this axis or at the intervals of the auto scale. In this case we render the labels at the auto scale points.

The X-axis is defined similarly:

<my:barchart.xaxes>
     <my:axescollection>
       <my:axis labels="{StaticResource XLabels}" titleoffset="10" minvalue="0" maxvalue="6" title="Month" interval="1" customlabelposition="ChartDataPoints" labeltype="CustomText">
       </my:axis>
      </my:axescollection>
   </my:barchart.xaxes> 

The notable difference here is that we want the labels to appear at the location of chart data. That is why we will need to add a few lines of code later. But now let’s move to the series.

III. The Series

The series in this sample are two. Here is the markup for the first one:

<my:barseries xdata="1, 2, 3, 4, 5, 6" ydata="23, 34, 12, 45, 77, 19" fills="{StaticResource BrushCollection1}" title="Shoes" tooltiptype="ChartData">
     <my:barseries.effect>
           <dropshadoweffect shadowdepth="3" opacity="0.7">
     </dropshadoweffect></my:barseries.effect>
   </my:barseries>

The series are added directly within the tag, because Series is the default content property of the chart control. The Title, though not visible at the series itself is important for the legend as we will see later. The fills for the series are a collection of brushes, which we have defined as a static resource just like we defined the labels. The data for the series is set with the XData and YData properties. We add a visual effect to make the chart more appealing.

The second series is similar to this one, so we won’t deal with it now.

IV. The Legend

The control supports unlimited number of legends. They are of two types – SeriesLegend and ChartLegend. In our case we use the ChartLegend. This legend defines the legend labels and brushes based on a list of ChartSeries, that were assigned to it. Both types of legends are ItemsControl-s.

<my:barchart.legends>
  <my:chartlegend x:name="Legend" background="#FFFAFAFA" my:layoutpanel.dock="Bottom" orientation="Horizontal" horizontalalignment="Center" margin="5" cornerradius="5" borderthickness="1" borderbrush="#FFCECEFF" itemssource="{Binding ElementName=barChart}">
     <my:chartlegend.effect>
          <dropshadoweffect shadowdepth="3" opacity="0.7">
     </dropshadoweffect></my:chartlegend.effect>
   </my:chartlegend>
 </my:barchart.legends>

As you can see, the ItemsSource for the legend is the chart. This means it takes all ChartSeries, which are the default content property of the chart and renders their labels.

V. Binding the X-Axis to a ChartSeries

As you might remember the custom labels at the X-axis are bound to the location of the chart data. This means, we must tell the ChartSeries that it should use exactly the X-axis where we want the labels to appear. This is done with the ChartSeries.XAxis property. We do this in the code behind file.

   if (barChart.XAxes.Count > 0 && barChart.Series.Count > 0)
    {
       Axis xAxis = barChart.XAxes[0];
       BarSeries series = (BarSeries)barChart.Series[0];

       series.XAxis = xAxis;
    }

And this is everything. Here is the final chart:

WPF Chart Control: Bar Chart With Multiple Axes

WPF Chart Control: Bar Chart With Multiple Axes

The sample can be downloaded from here.

Learn more about MindFusion Chart Control for WPF here.

Charting for WPF, V2.0

The list below describes recent changes and additions to MindFusion.Charting for WPF:

Support for Multiple Axes

The chart can now render multiple axes at each side of the plot area. To enable that, add a new Axis instance to the XAxes collection properties for bottom axes, to YAxes collection for left-hand axes, and X2Axes / Y2Axes for respectively top and right sides. The old AxisSettings class and XAxisSettings / YAxisSettings objects have been removed, and now their properties are set independently for each Axis instance in the collections.

Multiple axes in the WPF chart control.

Multiple axes in the WPF chart control.

Improved Zooming

Selected area with width smaller than MinZoomSpan does not evoke any action in the control. In addition, the new ZoomChanged event fires whenever zoom occurs and provides useful data for the zoom action with its ZoomChangedArgs.

Cross Hair Improvements

The cross hair control has been improved with several new properties, a method and an event. The properties are:

The new CrossHairPosition method returns the current location of the cross hair. For more precise handling of cross hair movements a new event is available – CrossHairPositionChanged.

Cross hair is drawn in a WPF column chart.

Cross hair is drawn in a WPF column chart.

Greatly Improved 3D Charts

3D charts have received plenty of improvements, new properties and performance optimizations: PointMergeThreshold – The property sets the radius of an area around a given point where no other points are drawn. The result is better performance especially in charts with numerous points, which happen to be close to one another. Of points with similar coordinates only a single one is rendered, the rest are omitted.

InterpolationType.None – A new InterpolationType has been added to the InterpolationType enum, which does not interpolate but adds data directly and connects the points with triangulation.

The SurfaceType enum has been replaced with three bool properties, which makes the API easier to understand and use.

ScatterFaceSize – the property regulates the size of the polygons that build a 3D scatter. Bigger values lead to bigger polygons, which results in faster performance and rougher scatter mesh. Effect3D.ShaderEffect – the property can be applied to all 3D chart elements, including scatters and performs much faster.

3D surface chart with color map and wire frame.

3D surface chart with color map and wire frame.

Exporting Images

Two new methods have been added for exporting the chart as an image – CreateImage and ExportImage.

Custom Formatting of Labels in Real-time Charts

A new property has been added to the RealTimeChart library – Axis.LabelFormatProvider. Use it to specify custom formatting of numeric labels. If required, you can specify format arguments for your format classes with Axis.LabelFormat.

Direct download of the trial version is available from here:

Download MindFusion.Charting for WPF 2.0 Trial Version

You are welcome to contact us with any questions, problems or inquiries about the Charting for Wpf control or any other of our products. MindFusion has always put special emphasis on providing excellent customer support and we usually answer your inquiries in a few hours of receiving them.

About MindFusion.Charting for Wpf: A programming component that combines powerful charting capabilities with an elegant API and easy use. Among the features of the control are fully customizable grid, positive and negative values on all chart axes, 3D charts, gauges and many more – read a detailed list here.

The control provides detailed documentation and various samples that demonstrate how to customize every type of chart. It supports a wide range of 2D and 3D charts including bar, line, radar, bubble pie etc. You can add tooltips, define themes, perform hit testing, zoom and more.

ContainerNode fold / unfold animations

In this post we’ll show how to animate container’s fold and unfold operations using some event handling and custom drawing. You can download the complete project here:

AnimatedFold.zip

The sample code will demonstrate several features of the Diagram control and .NET:

  • use LINQ to collect contained items
  • handle fold/unfold events
  • custom draw from DrawForeground event
  • draw items from custom drawing code

Let’s start by creating some items and containers when the form loads:

private void Form1_Load(object sender, EventArgs e)
{
    var ctr = diagram.Factory.CreateContainerNode(20, 20, 100, 100, true);
    var node1 = diagram.Factory.CreateShapeNode(30, 35, 15, 15);
    var node2 = diagram.Factory.CreateShapeNode(80, 45, 15, 15);
    diagram.Factory.CreateDiagramLink(node1, node2);

    ctr.Add(node1);
    ctr.Add(node2);

    var ctr2 = diagram.Factory.CreateContainerNode(20, 20, 100, 100, true);
    ctr2.Add(ctr);
}

We’ll use LINQ extensions methods to find all items within a ContainerNode, including ones contained recursively in child containers:

List GetDescendents(ContainerNode container)
{
    var nodes = diagram.Nodes.Where(
        container.ContainsRecursively);

    var links = diagram.Links.Where(l =>
        nodes.Contains(l.Origin) ||
        nodes.Contains(l.Destination));

    return
        nodes.Cast().Concat(
        links.Cast()).ToList();
}

Add handlers for ContainerFolded and ContainerUnfolded events that will start animation for the container:

void OnContainerFolded(object sender, NodeEventArgs e)
{
    var container = (ContainerNode)e.Node;
    StartAnimation(container, true);
}

void OnContainerUnfolded(object sender, NodeEventArgs e)
{
    var container = (ContainerNode)e.Node;
    StartAnimation(container, false);
}

The StartAnimation method stores a list of items that should be redrawn during animation and a few other animation attributes:

void StartAnimation(ContainerNode container, bool fold)
{
    var bounds = container.Bounds;
    var scaleCenter = new PointF(
        (bounds.Left + bounds.Right) / 2, bounds.Top);

    // collect items that will be unfolded
    animatedItems = GetDescendents(container);

    // animation will also draw this rectangle as background
    ctrBounds = bounds;
    ctrBounds.Size = container.UnfoldedSize;
    ctrBounds.Y += container.CaptionHeight;
    ctrBounds.Height -= container.CaptionHeight;

    // start animation timers
    Animate(scaleCenter, fold);

    if (!fold)
    {
        // temporarily fold back when animating unfold operation
        // so that contained items stay invisible
        container.Folded = true;
        toUnfold = container;
    }
}

The Animate method starts a timer whose Tick event invalidates the DiagramView and stops the timer when final frame has been reached:

void Animate(PointF scaleCenter, bool scaleDown)
{
    if (scaleDown)
    {
        frameCounter = maxFrames;
        frameIncrement = -1;
    }
    else
    {
        frameCounter = 0;
        frameIncrement = +1;
    }
    this.scaleCenter = scaleCenter;

    animationTimer = new Timer();
    animationTimer.Tick += OnAnimationTimer;
    animationTimer.Interval = duration / maxFrames;
    animationTimer.Start();
}

void OnAnimationTimer(object sender, EventArgs e)
{
    frameCounter += frameIncrement;
    diagramView.Invalidate();
    if (frameCounter == 0 || frameCounter == maxFrames)
    {
        animationTimer.Stop();
        animationTimer.Dispose();
        animationTimer = null;
        animatedItems = null;

        if (toUnfold != null)
        {
            toUnfold.Folded = false;
            toUnfold = null;
        }
    }
}

Add a DrawForeground event handler that applies scale transform proportional to current frame of animation and draws the container’s descendants stored in animatedItems list:

void OnDrawForeground(object sender, DiagramEventArgs e)
{
    if (animatedItems != null && frameCounter > 0)
    {
        var options = new RenderOptions();
        var g = e.Graphics;

        // apply scale corresponding to current frame
        var scale = (float)frameCounter / maxFrames;
        g.TranslateTransform(scaleCenter.X, scaleCenter.Y);
        g.ScaleTransform(scale, scale);
        g.TranslateTransform(-scaleCenter.X, -scaleCenter.Y);

        // draw container background
        g.FillRectangle(Brushes.White, ctrBounds);
        g.DrawRectangle(Pens.Black, ctrBounds);

        // draw contained items
        foreach (var item in animatedItems)
            item.Draw(e.Graphics, options);
    }
}

Same technique can be applied to animate collapse and expand operations on tree branches. To implement that, handle NodeExpanded and NodeCollapsed events instead, and collect items reachable recursively from the branch’ root by following outgoing links.

The code above uses MindFusion’s .NET API and can be used with Windows Forms, WPF, Silverlight and ASP.NET diagramming components. The Java API for Android and desktop Swing application will look similar, with setter method calls instead of property assignments.

You can download the trial version of any MindFusion.Diagramming component from this page.

Enjoy!

Line Chart With Multiple Axes in WPF

A common scenario when building charts is the ability to render multiple series bound to multiple axes, each one with its own scale. To deal with this issue, MindFusion.Charting for WPF control has added support for multiple axes of all types – X, Y, Y2, X2 and in this post we’ll look how to add and customize them and how to create series bound to a given axis.

The sample imitates a production chart, where three different scales measure three different values – work output, capital and energy consumption – all of which presumably participate in producing a single unit of a product. On the right side we have a single Y2 axis, which measures the amount of units produced. The X-axis displays the time scale. Let’s look at the chart elements, one by one.

I. The Y-Axes

The Y-axes, as all axes in the chart are an instance of the Axis class and are added to the appropriate collection property. The Axis class defines all types of useful properties needed to customize an axis. We define the three axes in XAML:

<chart:linechart.yaxes>
    <chart:axescollection>
        <chart:axis minvalue="0" interval="5" maxvalue="60" labelformat="F0" tick="3" title="kWh/day" titlerotationangle="270" labelstroke="Red" titlestroke="Red"></chart:axis>
        <chart:axis minvalue="0" interval="300" maxvalue="2100" title="Capital (USD)" tick="3" titlerotationangle="270" labelstroke="Purple" titlestroke="Purple"></chart:axis>
        <chart:axis minvalue="100" interval="2.5" maxvalue="130" title="Work Productivity (%)" customlabelposition="AutoScalePoints" axiscrossingpoint="100.0" labeltype="CustomText" tick="3" titlerotationangle="270" labelstroke="Green" titlestroke="Green"></chart:axis>
    </chart:axescollection>
</chart:linechart.yaxes>

The property names easily describe what is set: the minimum and maximum values on each of the three axes, the title, the stroke for the labels and the title, the interval and the length of the axis ticks. Let’s note that the type of labels for the last Y-axis is “CustomText” – this means we will specify the labels explicitly rather than allow the control to generate them as with the other two axes – they don’t set a label type and the default value (the auto scale) is rendered.

Here is how we define the labels:

double start = 100.0;

    //130 is the last number at the axis
    while (start <= 130)
    {
        string l = start.ToString("F1") + "%";
        chart.YAxes[2].Labels.Add(l);
        start += 2.5;
    }

II. The Y2 Axis

The Y2-axis is just one and it is entirely declared in XAML:

<chart:linechart.y2axes>
    <chart:axescollection>
        <chart:axis minvalue="0" interval="1000" maxvalue="12000" tick="3" labelformat="F0" titlerotationangle="270" title="Units"></chart:axis>
     </chart:axescollection>
</chart:linechart.y2axes>

The label format is set with the standard .NET numeric strings – in this case it is a floating number without trailing zeros. In this axis, as well in the other Y-axes you might have noticed that we use the TitleRotationAngle property. This property rotates the title label at an arbitrary angle between 0 and 360. In our case we want the label drawn vertically, to conserve space.

III. The Series

The series are created in code. They specify scatter type because we want each series to have markers at data points. The YAxis property specifies the Y-axis, which a given Series is bound to. Finally, we specify the tool tip type because we want to have a tool tip when the mouse hovers a data point.

 LineSeries series0 = new LineSeries();
 series0.YAxis = chart.YAxes[0];
 series0.ScatterType = ScatterType.Square;
 series0.ScatterFills = new BrushCollection() { Brushes.Pink };
 series0.ScatterStrokes = new BrushCollection() { Brushes.Red };
 series0.Strokes = new BrushCollection() { Brushes.Red };
 series0.ToolTipType = ToolTipType.ChartData;

The data is random generated numbers. We use the Axis.XData and Axis.YData properties to set it.

 for (int i = 0; i < 30; i++)
     {
        series0.XData.Add(i * 6);
        data1.Add(rand.NextDouble() * 60.0);     
      }

      data1.Sort();
      series0.YData = new DoubleCollection(data1);
      //don't forget to add the series
      chart.Series.Add(series0);

Last but not least – don’t forget to add the series to the Series collection property of the chart. With that our chart is ready – here is the result:

Charting for WPF: Multiple Axes and Series

Charting for WPF: Multiple Axes and Series

You can download the sample with the chart libraries from here:

WPF Chart With Multiple Axes Sample Download

If you have any questions regarding the chart component use the forum, email or the help desk to contact MindFusion. More information about Charting for WPF, which includes a premium 3D charting library and a Real time charting library optimized to handle huge data sets can be found here.

Diagramming for ASP.NET, V5.5

Here is the list of the recent changes and additions to MindFusion WebForms Diagram control:

Resize table columns and rows

Note: This feature is not available in ImageMap mode.

Columns and rows of a TableNode can now be resized interactively if its AllowResizeColumns or AllowResizeRows properties are enabled. In order to resize, move the mouse pointer to the border line on column’s right side or row’s bottom side until it shows resize cursor and start dragging. The control raises TableColumnResizing and TableRowResizing events to let you validate new size or prevent resizing some elements. The TableColumnResized and TableRowResized events are raised after the operation completes.

Diagramming for ASP.NET: Table Nodes

Diagramming for ASP.NET: Table Nodes

Canvas mode improvements

  • Shape library files and ShapeLibraryLocation properties of DiagramView and ShapeListBox can now be used in Canvas mode.
  • Items can now be deleted using Backspace key when running on Mac.
  • Caption divider line in TableNode and ContainerNode is now rendered clipped when caption height is smaller than corner radius.
  • The TooltipDelay property specifies the delay in milliseconds before showing tooltips.
  • The Orientation property of NodeListView lets you set the view’s orientation to Horizontal or Vertical (members of Orientation enum).
  • Fixed overlaps in client-side TreeLayout when arranging nodes of different sizes.
  • Fixed a bug where deserialization of custom item classes worked correctly only if their names contained Node or Link suffix.
  • MsAjaxLocation and JQueryLocation properties let you change the location of external script libraries.

Diagramming for ASP.NET: Node Shapes

Diagramming for ASP.NET: Node Shapes

ImageMap mode improvements

  • Image generator implementation changed from Page to IHttpHandler , this makes it more lightweight avoiding the full page life-cycle.
  • Image generation is now implemented by the built-in ImageHandler class inside MindFusion.Common.WebForms assembly and it no longer requires adding external .aspx file to the project. Instead, it can be listed in web.config file:
    <handlers>
        <add name="ImageHandler" path="ImageGen.ashx" verb="GET" type="MindFusion.Common.WebForms.ImageHandler, MindFusion.Common.WebForms">
    </add></handlers>
  • For convenience, there is still optional ImageGen.ashx provided as external file for easier set-up of the project (add existing file instead of changing web.config).
  • ZoomControl no longer draws using <canvas> element when running in ImageMap mode, but uses ImageHandler to generate bitmaps.

API changes

ZoomControl can now be used with other MindFusion components and has been moved to MindFusion.Common.WebForms namespace and assembly.

Here is a direct link to download the trial version:

Download MindFusion.Diagramming for ASP.NET, V5.5 Trial Version

Technical support
MindFusion support team is happy to assist you with any questions you might have about Diagramming for ASP.NET or any other of our products. You can leave a message at the discussion board, use the help desk or e-mail support@mindfusion.dev.. We strive to provide competent and detailed answers to your questions within hours of receiving them.

About Diagramming for ASP.NET: An advanced WebForms programming component that offers all the functionality that is needed for creating, styling and presenting attractive flowcharts, hierarchies, trees, graphs, schemes, diagrams and many more. The control offers numerous utility methods, path finding and cycle detection, rich event set and many useful user interaction features like tool tips, multiple selection, copy/paste to/from Windows clipboard and many more.

NetDiagram offers more than 100 predefined node shapes, scrollable tables, 13 automatic layouts and many more. You can check the online demo to see some of the features in action. The control includes many samples, detailed documentation and step-by-step tutorials. Every features is duly documented and there’s plenty of code to copy. The component is not only powerful and scalable, but easy to learn and fun to use.