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.

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.

A Monthly Calendar in Java With Events and Recurring Appointments

This is a step-by-step guide that teaches you how to:

  • Setup the MindFusion Scheduler for Java library to display a single
    month calendar.
  • Attach and handle an event when the user clicks a calendar cell.
  • Create and setup recurrent events/appointments.
  • Perform custom drawing on a specific cell.

The sample builds a monthly calendar, which responds to a user click on a calendar cell by creating a recurrent appointment. The appointment is repeated on each subsequent day of the week for unlimited number of months.

When the user selects a given calendar cell (the 6th day of the month), a special icon appears. The icon is rendered using custom drawing.

Note: In this tutorial we use the words “appointment” and “event” interchangeably. Let’s start:

1. How to Setup the Calendar

All packages of the calendar are included in a single *.jar file – JPlanner.jar In our sample we will reference the following packages:

import com.mindfusion.common.*;
import com.mindfusion.common.Rectangle;
import com.mindfusion.drawing.*;
import com.mindfusion.drawing.awt.AwtImage;
import com.mindfusion.scheduling.*;
import com.mindfusion.scheduling.awt.*;
import com.mindfusion.scheduling.model.*;

For detailed reference about the packages and the classes of the schedule library check the online help.

Here are the first settings for our schedule:

  calendar = new AwtCalendar();
  calendar.beginInit();
  //set the current time
  calendar.setCurrentTime(DateTime.now());
  DateTime today = DateTime.today();
  //set the current date
  calendar.setDate(today);
  // Select the current date
  calendar.getSelection().set(DateTime.today());

We create a new calendar and signal that initialization starts. The time and date shown at the application start are the current date and time. We also select the cell with the current date.

Let’s make the calendar show exactly one month:

calendar.setCurrentView(CalendarView.SingleMonth);

By default each cell in a single month view has its header size set to 0, which makes the current date show in the center of the cell. As a consequence any events in the cell won’t have space to be rendered.

Java Scheduler: the header takes all the cell's height

Java Scheduler: the header takes all the cell’s height

Since we plan to create appointments on user click, we must push the header to the top and free cell space for drawing the events. This is very easy to do, just set:

calendar.getMonthSettings().getDaySettings().setHeaderSize(20);

Now the header is 20 px. and the rest will be for our appointments.

Java Scheduler: the header is 20px.

Java Scheduler: the header is 20px.

For now we are ready initializing the calendar.

calendar.endInit();

2. Handling User Clicks.

User clicks are handled with the dateClick event. We need an instance of the CalendarAdapter and there we associate the dateClick event with a method – onDateClicked. The event is fired when a date is selected.

 calendar.addCalendarListener(new CalendarAdapter(){
            public void dateClick(ResourceDateEvent e) {
                onDateClicked(e);
            }

        });

3. Create an Appointment and a Recurrence.

The first part of our event handler method is:

protected void onDateClicked(ResourceDateEvent e) {

    int dayIndex = e.getDate().getDayOfWeek();

    Appointment item = new Appointment();
    item.setStartTime(e.getDate());
    item.setEndTime(e.getDate());
    item.setHeaderText(events[dayIndex]);
    item.getStyle().setBrush(brushes[dayIndex]);

Here we create an Appointment and set its start and end date to the date which was clicked. The ResourceDateEvent keeps data about the date, the resource, if any and other useful information. The header text is the text, which will be rendered at the appointment. The style object contains appearance data for the cell and we use the setBrush
method to change the background of the appointment.

The second part of the method creates the Recurrence object:

      recurrence = new Recurrence();
      recurrence.setPattern(RecurrencePattern.Weekly);
      recurrence.setDaysOfWeek(getDayOfWeek(dayIndex));
      recurrence.setStartDate(e.getDate());
      recurrence.setRecurrenceEnd(RecurrenceEnd.Never);
      item.setRecurrence(recurrence);

The recurrence is once a week. There’s additional work to be done when we set the day of the week with setDaysOfWeek. The method accepts as an argument one of the DaysOfWeek enumeration values and we have to convert the index of the day to such value.

private int getDayOfWeek ( int i ) {

        switch (i) {
            case 1:
                return DaysOfWeek.Monday;
            case 2:
                return DaysOfWeek.Tuesday;
            case 3:
                return DaysOfWeek.Wednesday;
            case 4:
                return DaysOfWeek.Thursday;
            case 5:
                return DaysOfWeek.Friday;
            case 6:
                return DaysOfWeek.Saturday;
        }

        return DaysOfWeek.Sunday;

    }

Finally, let’s add the item with the recurrence pattern to the schedule items collection:

 calendar.getSchedule().getItems().add(item);

4. Custom Drawing a Cell’s Header

The last thing that needs to be done is to draw the icon on the special 6th day of each month. We will perform item drawing and we add to the calendar initialization code this line:

 calendar.setCustomDraw(CustomDrawElements.CalendarItem);

Now we are ready to handle the draw event:

   //add a listener for custom draw
       calendar.addCalendarListener(new CalendarAdapter()
        {
            @Override()
            public void draw(DrawEvent e) {
                onDraw(e);
            }
        });

Below is the first part of the handler method:

 private void onDraw(DrawEvent e)
    {
 if(e.getDate().getDay() == 6 )
            {
                java.awt.Image img = null;

                try {
                    // Read the image file from an input stream
                    InputStream is = new BufferedInputStream(
                            new FileInputStream("../cake.png"));
                    img = ImageIO.read(is);

                } catch (IOException ioe) {
                }

Here we read an image from an InputStream. The calendar method for drawing images requires mindfusion.scheduling.AwtImage and we must convert the java image:

AwtImage awtImage = new AwtImage(img);

Then we get the bounds of the drawing area and render the image:

   //gets the bounds of the drawing area
    Rectangle r = e.getBounds();
             
   //draw the image
   e.getGraphics().drawImage(awtImage, e.getBounds().getLeft(), e.getBounds().getTop(), 32, 32);

 }

The image is 32 x 32 pixels and gets clipped in the appointment.

Java Scheduler: the appointment's height is not enough.

Java Scheduler: the appointment’s height is not enough.

We’ll have to resize the item header to give it more space:

calendar.getItemSettings().setSize(32);

And now everything works just fine:

MindFusion Java Calendar: Month View with Events

MindFusion Java Calendar: Month View with Events

The sample is available for download from here:

Monthly Calendar in Java with Recurrent Appointments and Events

About Scheduling for Java Swing: A programming class library written entirely in Java that lets you build the most sophisticated schedules, calendars and task managers fast and easy. The component boasts a comprehensive feature set that includes custom-typed events, undo/redo functionality, scrolling, tool tips and much more. You can choose among six view styles, which are easy to change and customize. The appearance of each schedule is completely customizable and supports themes, user-assigned mouse cursors and a variety of font, pen and brush options.

A detailed list with the features of the tool is available at the Scheduling for Java Swing features page. The trial version includes a variety of samples and you have plenty of sample code to study. Online documentation with useful tutorials is also available.

The library is royalty-free, source-code is also available. You can see a list of the current prices here. Check the discount page for a list of the available discounts.

Display Petri nets using MindFusion diagram component.

In this post we show how to build a Petri net using MindFusion.Diagramming for WinForms. Petri nets are used to model and study distributed systems. A net contains places, transitions and arcs. A place represents possible state of the system, and a transition represents the change from one state to another. Arcs connect places to transitions and show the flow direction.

First, create a new .NET Windows Forms project and add a Model.cs file to it where we’ll define Petri net model classes

public class Net
{
	public List Places { get; set; }
	public List Transitions { get; set; }
	public List Arcs { get; set; }

	public Net()
	{
		Places = new List();
		Transitions = new List();
		Arcs = new List();
	}
}

public class Node
{
	public string Label { get; set; }
}

public class Place : Node
{
	public int Tokens { get; set; }
}

public class Transition : Node
{
}

public class Arc
{
	// Arcs run from a place to a transition or vice versa,
	// never between places or between transitions.

	public Arc(Place input, Transition output)
	{
		Input = input;
		Output = output;
	}

	public Arc(Transition input, Place output)
	{
		Input = input;
		Output = output;
	}

	public Node Input { get; private set; }
	public Node Output { get; private set; }

	public int Multiplicity { get; set; }
}

Now we can create a simple Petri net:

Net CreateSampleNet()
{
	var net = new Net();

	var p1 = new Place { Label = "P1", Tokens = 1 };
	var p2 = new Place { Label = "P2", Tokens = 0 };
	var p3 = new Place { Label = "P3", Tokens = 2 };
	var p4 = new Place { Label = "P4", Tokens = 1 };

	net.Places.AddRange(new[] { p1, p2, p3, p4 });

	var t1 = new Transition { Label = "T1" };
	var t2 = new Transition { Label = "T2" };

	net.Transitions.AddRange(new[] { t1, t2 });

	var a1 = new Arc(p1, t1);
	var a2 = new Arc(t1, p2);
	var a3 = new Arc(t1, p3);
	var a4 = new Arc(p2, t2);
	var a5 = new Arc(p3, t2);
	var a6 = new Arc(t2, p4);
	var a7 = new Arc(t2, p1);

	net.Arcs.AddRange(new[] { a1, a2, a3, a4, a5, a6, a7 });

	return net;
}

Next, drop a DiagramView and Diagram objects on the form which we’ll use to visualize the net. Add the following method to create diagram elements representing the model objects, and run LayeredLayout to arrange them:

void BuildDiagram(Net net)
{
	var nodeMap = new Dictionary<node, diagramnode="">();

	var placeBounds = new RectangleF(0, 0, 16, 16);
	var transBounds = new RectangleF(0, 0, 6, 20);

	foreach (var place in net.Places)
	{
		var node = diagram.Factory.CreateShapeNode(placeBounds);
		node.Text = place.Label;
		node.TextFormat.LineAlignment = StringAlignment.Far;
		node.Shape = Shapes.Ellipse;
		node.Tag = place.Tokens;
		node.CustomDraw = CustomDraw.Additional;
		nodeMap[place] = node;
	}

	foreach (var trans in net.Transitions)
	{
		var node = diagram.Factory.CreateShapeNode(transBounds);
		node.Text = trans.Label;
		node.TextFormat.LineAlignment = StringAlignment.Far;
		node.Shape = Shapes.Rectangle;
		nodeMap[trans] = node;
	}

	foreach (var arc in net.Arcs)
	{
		var link = diagram.Factory.CreateDiagramLink(
			nodeMap[arc.Input], nodeMap[arc.Output]);
		link.Tag = arc.Multiplicity;
		link.HeadShape = ArrowHeads.PointerArrow;
	}

	var layout = new LayeredLayout();
	layout.Orientation = Orientation.Horizontal;
	layout.StraightenLongLinks = true;
	layout.Arrange(diagram);
}</node,>

We will use the DrawNode custom draw event to render marks associated with each place. Another possibility is to create a custom node class and override its Draw method.

void OnDrawNode(object sender, DrawNodeEventArgs e)
{
	var node = e.Node;
	var g = e.Graphics;

	if (node.Tag is int)
	{
		var tokens = (int)node.Tag;
		var cx = node.Bounds.Width / 2;
		var cy = node.Bounds.Height / 2;

		if (tokens == 1)
		{
			float r = cx / 2;
			DrawMark(cx, cy, r, g);
		}
		else if (tokens == 2)
		{
			float r = 2 * cx / 5;
			DrawMark(cx / 2, cy, r, g);
			DrawMark(3 * cx / 2, cy, r, g);
		}
		else if (tokens == 3)
		{
			float r = cx / 3;
			float y2 = 4 * cy / 3;
			DrawMark(cx, 2 * cy / 5, r, g);
			DrawMark(cx / 2, y2, r, g);
			DrawMark(3 * cx / 2, y2, r, g);
		}
	}
}

void DrawMark(float x, float y, float r, IGraphics g)
{
	g.FillEllipse(Brushes.Black, x - r, y - r, r * 2, r * 2);
}

Finally, set some appearance properties and call the methods above to build the diagram:

public MainForm()
{
	InitializeComponent();

	diagram.ShadowsStyle = ShadowsStyle.None;
	diagram.DiagramLinkStyle.Brush = new MindFusion.Drawing.SolidBrush(Color.Black);
	diagram.ShapeNodeStyle.Brush = new MindFusion.Drawing.SolidBrush(Color.White);
	diagram.ShapeNodeStyle.FontSize = 10f;
	diagram.ShapeNodeStyle.FontStyle = FontStyle.Bold;

	var textAbove = new[]
	{
		new LineTemplate(-100, -100, 200, -100),
		new LineTemplate(200, -100, 200, 0),
		new LineTemplate(200, 0, -100, 0),
		new LineTemplate(-100, 0, -100, -100)
	};
	Shapes.Ellipse.TextArea = textAbove;
	Shapes.Rectangle.TextArea = textAbove;

	var net = CreateSampleNet();
	BuildDiagram(net);
}

The final result is displayed below.
Petri net diagram

The complete sample project is available for download here:
PetriNet.zip

For more information on Petri nets, see this Wikipedia article:
http://en.wikipedia.org/wiki/Petri_net

All MindFusion.Diagramming libraries expose the same programming interface, so most of the sample code shown above will work with only a few modifications in WPF, ASP.NET, Silverlight and Java versions of the control.

Enjoy!