Cost Meter Gauge in JavaScript

In this post we will look at the steps we need to make if we want to create this beautiful gauge below:

The gauge is done with MindFusion Charts and Gauges for JavaScript library. You can download the sample together with all needed libraries from this link.

I. Project Setup

We will build the gauge using the OvalGauge library from MindFusion JS Charts and Gauges control. We add two references, needed for the control to work properly:

<script type="text/javascript" src="Scripts/MindFusion.Common.js"></script>
<script type="text/javascript" src="Scripts/MindFusion.Gauges.js"></script>

We have placed those files in a Scripts folder. We will write the JavaScript code for the gauge in a separate file, which we call ValueGauge.js. This file is at the same directory where the web page is. We add a reference to it as well:

<script type="text/javascript" src="ValueGauge.js"></script>

The web page with our sample contains a table. We use the table to place the control together with a text box. The text box is not needed, but we will use it to give the user the option to set the value of the gauge by typing it not only by dragging the pointer.

<table cellpadding="10">
    <tbody>
        <tr>
            <td>Project Cost (in thousands)</td>
        </tr>
        <tr>
            <td><canvas id="value_meter" width="300" height="300"></canvas></td>
        </tr>
        <tr>
            <td>Cost <input id="cost" style="width: 80px"></td>
        </tr>
    </tbody>
</table>

The gauge will be rendered through an HTML Canvas element. The location and size of the Canvas determine the location and the size of the gauge. It is important that we add an id the Canvas – this way we can reference it in the JavaScript code page, which will be necessary.

II. The Control

Now we start editing the ValueGauge.js file and there we first add mappings to the namespaces of Mindusion.Gauges.js that we will use:

/// 
var Gauges = MindFusion.Gauges;

var d = MindFusion.Drawing;
var OvalScale = Gauges.OvalScale;
var Length = Gauges.Length;
var LengthType = Gauges.LengthType;
var Thickness = Gauges.Thickness;
var Alignment = Gauges.Alignment;
var LabelRotation = Gauges.LabelRotation;
var TickShape = Gauges.TickShape;
var PointerShape = Gauges.PointerShape;

The first line is a reference to the Intellisense file that allows us to use code completion of the API members, if supported by our JavaScript IDE.

Now we need to get the DOM Element that corresponds to the gauge Canvas and use it to create an instance of the OvalGauge class:

var value_meter = Gauges.OvalGauge.create(document.getElementById('value_meter'), false);

III. Gauge Scales

Gauge scales depend on the type of the gauge. For oval gauges we use OvalScale The OvalScale needs to be associated with a gauge and here is how we create it:

var valueScale = new Gauges.OvalScale(value_meter);
valueScale.setMinValue(0);
valueScale.setMaxValue(100);
valueScale.setStartAngle(120);
valueScale.setEndAngle(420);

The OvalScale class offers the full set of properties needed to customize the scale. We use the setMinValue and setMaxValue methods to specify the value range o the gauge. The setStartAngle and setEndAngle specify the arc of the gauge and we set them to 120 and 420 respectively. You see that the arc is 300 degrees, which is less than a full circle – exactly how we want it to be.

We continue our customization by setting the fill and stroke of the scale. We actually do not want the default scale to be rendered at all, so we use setFill and setStroke to specify transparent colors:

valueScale.setFill('Transparent');
valueScale.setStroke('Transparent');
valueScale.setMargin(new Gauges.Thickness(0.075, 0.075, 0.075, 0.075, true));

Now we can continue with the ticks. Each gauge can have major, middle and minor ticks. Those ticks are not rendered by default.

var majorSettings = valueScale.majorTickSettings;
majorSettings.setTickShape(Gauges.TickShape.Line);
majorSettings.setTickWidth(new Gauges.Length(10, Gauges.LengthType.Relative));
majorSettings.setTickHeight(new Gauges.Length(10, Gauges.LengthType.Relative));
majorSettings.setFontSize(new Length(14, LengthType.Relative));
majorSettings.setNumberPrecision(0);
majorSettings.setFill('rgb(46, 52, 66)');
majorSettings.setStroke('rgb(46, 52, 66)');
majorSettings.setLabelForeground('rgb(175, 175, 175)');
majorSettings.setLabelAlignment(Alignment.InnerCenter);
majorSettings.setLabelRotation(LabelRotation.Auto);
majorSettings.setLabelOffset(new Length(6, LengthType.Relative));
majorSettings.setStep(20);
majorSettings.setTickAlignment (Alignment.OuterOutside);

We start the customization with the majorTickSettings They will render labels and want to have one tick with a tep of 20. So, we use setStep to specify 20 as an interval and use setTickWidth and setTickHeight to set the size of the ticks. Those properties can be set to an absolute or relative value – see the LengthType enumeration. We also need to change the default shape of the pointer – we use TickShape rest of the settings are intuitive – setFill and setStroke specify how the ticks are colored. We also use setLabelAlignment to position the labels outside the ticks. setTickAlignment is also an important property -it allows us to change the alignment of the ticks, so they can be drawn inside the scale.

The TickSettings object is similar to MajorTickSettings

var middleSettings = valueScale.middleTickSettings;
middleSettings.setTickShape(TickShape.Line);
middleSettings.setTickWidth(new Gauges.Length(10, Gauges.LengthType.Relative));
middleSettings.setTickHeight(new Gauges.Length(10, Gauges.LengthType.Relative));
middleSettings.setTickAlignment (Alignment.OuterOutside);
middleSettings.setShowTicks(true);
middleSettings.setShowLabels(false);
middleSettings.setFill('rgb(46, 52, 66)');
middleSettings.setStroke('rgb(46, 52, 66)');
middleSettings.setCount(5);

We should note here that setShowLabels is false because we want the labels to appear only at intervals of 20. We also use setCount to specify how many ticks we want between each two adjacent major ticks. The rest of the settings are the same as for MajorTickSettings.

IV. Custom Painting

The painting of the colored sections at the edge of the gauge is custom code. The gauges library provides various events that allow the developer to replace the default gauge drawing with custom drawing – see the Events section of the OvalGauge class.

In our sample we will handle two events:

value_meter.addEventListener(Gauges.Events.prepaintBackground, onPrepaintBackground.bind(this));
value_meter.addEventListener(Gauges.Events.prepaintForeground, onPrepaintForeground.bind(this));

prepaintBackground is raised before the background is painted. We can cancel the default painting or add some custom drawing to it. The same is true for prepaintForeground

function onPrepaintBackground(sender, args)
{
	args.setCancelDefaultPainting(true);

	var context = args.getContext();
	var element = args.getElement();
	var bounds = new d.Rect(0, 0, element.getRenderSize().width, element.getRenderSize().height);
        ..................................
}

In the prepaintBackground event handler we first get the handler to the CanvasRenderingContext2D instance. Then we get the bounds of the painted element. This is the inside of the gauge. Each o the colored segments is pained as an arc. We do not create a path figure to fill – instead we set a very thick lineWidth of the stroke:

context.lineWidth = 25;
var correction = context.lineWidth / 2;
	
//light green segment
context.beginPath();
context.strokeStyle = 'rgb(0, 205, 154)';
context.arc(bounds.center().x, bounds.center().y, bounds.width / 2-correction, 0.665*Math.PI, 1*Math.PI, false);	
context.stroke();

We go on painting this way all colored sections of the gauge, only changing the start and end angles. When we are ready we paint the inside of the gauge. We do it with a full arc:

context.beginPath();
bounds.x += 25;
bounds.y += 25;
bounds.width -= 50;
bounds.height -= 50;
context.fillStyle = '#2e3442';

context.arc(bounds.center().x, bounds.center().y, bounds.width / 2, 0*Math.PI, 2*Math.PI, false);
context.fill();

The complete drawing is done inside the prepaintBackground event handler. So, in the prepaintForeground handler we only need to cancel the default painting:

function onPrepaintForeground(sender, args)
{
    args.setCancelDefaultPainting(true);

};

V. The Gauge Pointer

We need to add a Pointer to the OvalScale of the gauge instance if we want to show one:

var pointer = new Gauges.Pointer();
pointer.setFill('white');
pointer.setStroke("#333333");

pointer.setPointerWidth(new Gauges.Length(90, Gauges.LengthType.Relative));
pointer.setPointerHeight(new Gauges.Length(20, Gauges.LengthType.Relative));

pointer.setShape(Gauges.PointerShape.Needle2);
pointer.setIsInteractive(true);

valueScale.addPointer(pointer);

The size of the pointer is also set in LengthType units. This allows us to have the same pointer size relative to the size of the gauge even if we change the size of the Canvas. We use the PointerShape enumeration to specify the type of pointer we want and then we make it interactive with setIsInteractive As an addition to the default needle of the pointer we want to render a circle at the base of the pointer. We do it with custom drawing:

value_meter.addEventListener(Gauges.Events.prepaintPointer, onPrepaintPointer.bind(this));

First we need to handle the prepaintPointer event. In the event handling code we do the drawing:

function onPrepaintPointer(sender, args)
{	
	//args.setCancelDefaultPainting(true);

	var context = args.getContext();
	var element = args.getElement();
	var size = element.getRenderSize();
	var psize = new d.Size(0.2 * size.width, size.height);

	context.save();
	context.transform.apply(context, element.transform.matrix());

	context.beginPath();
	context.arc(psize.width / 2, psize.height / 2, psize.height*0.75, 0, 2 * Math.PI, false);
	var fill = element.getFill();
	context.fillStyle = Gauges.Utils.getBrush(context, fill, new d.Rect(0, 0, size.width, size.height), false);
	context.fill();
	context.strokeStyle = '#333333';
	context.stroke();

	context.restore();
};

Note that in this case we do not cancel the default painting – we will add to it, rather than replace it. Then we get the CanvasRenderingContext2D and size of the rendered element. What is new here is the transform of the CanvasRenderingContext2D object to the center of the gauge. Then we get the Brush that is used to paint the rest of the pointer and use it to fill the custom part as well. We can set the brush directly, but we prefer to take it from the base element – the Pointer This way if we change settings of the Pointer the color of the custom drawn circle will change automatically as well.

VI. Data Binding

What we would like to do now is bind a change in the text field to the value of the gauge scale. We add a method that does it:

function valueChanged(id)
{
	if (isNaN(this.value)) return;
	var gauge = Gauges.OvalGauge.find(id);
	var pointer = gauge.scales[0].pointers[0];
	pointer.setValue(+this.value);
};

When we call the valueChanged method with the instance of the OvalGauge as an argument, we can get its pointer and set its value to the value of ‘this’. We call the valueChanged in such way, that the ‘this’ reference will be the text field:

var cost = document.getElementById('cost');
cost.onchange = valueChanged.bind(cost, ['value_meter']);

Now when the value changes, the event handler takes the pointer and set its value to the value the user has types.

That is the end of this tutorial. You can download the source code of the sample, together with all MindFusion libraries used from the following link:

Download Value Gauge in JavaScript Source Code

You can use the discussion forum to post your questions, comments and recommendations about the sample or MindFusion charts and gauges.

About MindFusion JavaScript Gauges: A set of two gauge controls: oval and rectangular, with the option to add unlimited nuber of scales and gauges. All gauge elements support complete customization of their appearance. Custom drawing is also possible, where you can replace the default rendering of the gauge element or add to it. The gauge controls include a variety of samples that offer beautiful implementations of the most popular applications of gauges: thermometer, car dashboard, functions, compass, clock, cost meter and more.
Gauges for JavaScript is part of MindFusion charts and Dashboards for JavaScript. Details at https://mindfusion.eu/javascript-chart.html.

Horizontal Full Bar Chart in JavaScript

We use here MindFusion JavaScript library for Charts and Gauges to build this horizontal stacked bar chart that renders custom tooltips:

Run the sample from this link.

You can download the source code together with the libraries used from the link at the bottom of the post.

I. General Setup

We split our chart in two files – one is the web page that hosts an HTML Canvas element that will render the chart. The other file is a JavaScript code-behind file that contains the code for the chart.

We need to add reference to two JavaScript library files that provide the charting and drawing functionality that we need:

MindFusion.Common.js
MindFusion.Charting.js

We place them in a Scripts folder at the same level as our web page and JavaScript code behind file.

<script type="text/javascript" src="Scripts/MindFusion.Common.js"></script>
<script type="text/javascript" src="Scripts/MindFusion.Charting.js"></script>

We also add a reference to the code-behind file that we call StackedBarChart.js:

<script type="text/javascript" src="StackedBarChart.js"></script>

Now we need to create an HTML Canvas element and we must provide it with an id so we can reference it in our JS code:

<canvas id="barChart" width="600px" height="400px"></canvas>

The size of the Canvas determines the size of the chart.

II. Chart Instance and General Settings

We add some namespace mappings that allow us to reference classes from the Chart library in a more consice manner:

var Charting = MindFusion.Charting;
var Controls = MindFusion.Charting.Controls;
var Collections = MindFusion.Charting.Collections;
var Drawing = MindFusion.Charting.Drawing;
var GridType = MindFusion.Charting.GridType;
var ToolTip = Charting.ToolTip;

Then we create an instance of the BarChart control. We need to get the Dom Element that corresponds to the Canvas that we’ve prepared for the chart:

var chartEl = document.getElementById('barChart');
chartEl.width = chartEl.offsetParent.clientWidth;
chartEl.height = chartEl.offsetParent.clientHeight;
var chart = new Controls.BarChart(chartEl, Charting.BarLayout.Stack);

The BarChart constructor supports a second argument that indicates the type of the bar chart to render.

We set the bar chart to horizontal with the horizontalBars property. We also make the bars thicker than normal – the property for this is barSpacingRatio It measures the thickness of the bars as a percente of the bar width.

chart.horizontalBars = true;
chart.barSpacingRatio = 0.2;

III. The Data Series

We want our chart to render labels as tooltips, inside the bars and also we want custom labels at the Y-axis. The predefined BarSeries class accepts 4 lists with data: one for bar data and three with labels inside the bars, at the top of the bars and at the X-axis. So, it is not an exact match for what we want to do and we need to customize it.

We will create our own custom BarSeries that we will call SeriesWithLabels. We will inherit the BarSeries class and override its constructor and getLabel members to provide the desired data for the desired type of labels.

We override the constructor by creating three new variables, which receive the data for the bars and the labels:

var SeriesWithLabels = function (barValues, innerLabels, yLabels) {
    Charting.BarSeries.apply(this, [barValues, innerLabels, yLabels]);
	
	this.yLabels = yLabels;
	this.innerLabels = innerLabels;
	this.values = barValues;
    
};

SeriesWithLabels.prototype = Object.create(Charting.BarSeries.prototype);

Note that before we do anything else in the new constructor we need to call the apply method of the BarSeries class to transfer the provided data to the base class. We also need to create a prototype of the new series and also define its constructor:

 Object.defineProperty(SeriesWithLabels.prototype, 'constructor', {
   	value: SeriesWithLabels,
   	enumerable: false,
   	writable: true
   });

Next we will override the getLabel method. This is the method that returns the correct label according to the requested label kind and the index of the label. We said we want to support inner labels, tooltips and Y-axis labels. So, we make sure our implementation of getLabel returns exactly those labels:

SeriesWithLabels.prototype.getLabel = function (index, kind) {
    if ((kind & Charting.LabelKinds.YAxisLabel) != 0 && this.yLabels)
        return this.yLabels.items()[index];

    if ((kind & Charting.LabelKinds.InnerLabel) != 0 && this.innerLabels)
        return this.innerLabels.items()[index];
	
	if ((kind & Charting.LabelKinds.ToolTip) != 0)
        return getPercentLabel(index, this);
   
    return "";
};

Getting the correct inner and top label is easy – we just return the label at the requested position. What is more work is building the tooltip. We want our tooltip to calculate the portion of the part in the stacked bar the mouse currently is over, to the entire bar. This means we need to calculate the data of all bar portions, which is a combination of the values at the requested position in all three bar series. We do this calculation in a separate method called getPercentLabel.

Before we get to the getPercentLabel method let’s create 3 instances of our custom SeriesWithLabels class:

var labels = new Collections.List([
	"POSITION", "SALARY", "LOCATION", "COLLEAGUES", "WORKTIME"
]);

// create sample data series
var series1 = new SeriesWithLabels(new Collections.List([123, 212, 220, 115, 0.01]), new Collections.List([123, 212, 220, 115, 0]), labels);
var series2 = new SeriesWithLabels(new Collections.List([53, 132, 42, 105, 80]), new Collections.List([53, 132, 42, 105, 80]), null);
var series3 = new SeriesWithLabels(new Collections.List([224, 56, 138, 180, 320]), new Collections.List([224, 56, 138, 180, 320]), null);

The third argument in the SeriesWithLabels constructor is the lists with labels at the Y-axis. We need just one list with labels and we set it with the first series. The other series take null as their third argument.

We need to create a collection with the series and assign it to the series property of the chart:

var series = new Collections.ObservableCollection(new Array(series1, series2, series3));
chart.series = series;

There is a special property called supportedLabels that is member of Series and tells the chart, what type of labels this Series needs to draw. In our case we need to indicate that the first series renders labels at the Y-axis, the inner labels and tooltips. The other two series render inner labels and tooltips:

series1.supportedLabels = Charting.LabelKinds.YAxisLabel | Charting.LabelKinds.InnerLabel | Charting.LabelKinds.ToolTip;
series2.supportedLabels = Charting.LabelKinds.InnerLabel | Charting.LabelKinds.ToolTip;
series3.supportedLabels = Charting.LabelKinds.InnerLabel | Charting.LabelKinds.ToolTip;

Now let’s get back to the method that calculates the tooltip:

function getPercentLabel(index, series)
{
	var value1 = series1.values.items()[index];
	var value2 = series2.values.items()[index];
	var value3 = series3.values.items()[index];
	
	var theValue = series.values.items()[index];	
	var result = theValue/(value1+value2+value3) * 100;
	
	return Number(result).toFixed(0) + "%";	
};

In it we calculate the sum of all data that is rendered by the stacked bar at the specified index. Then we convert the data to percent and format it to have no numbers after the decimal point. That gives us a little inacurracy sometimes, when the value gets rounded to the next number and the sum of all percents actually is 101. You might want to change the formatting to toFixed(2) if you want to see the exact number rendered.

IV. Axes and Tooltip

By default the X-axis shows a title and both axes render the auto scale for the data of the chart. We need to hide the scale and we also hide the ticks that are rendered at the interval values:

chart.xAxis.title = "";
chart.yAxis.title = "";
chart.showXCoordinates = false;
chart.showYCoordinates = false;
chart.showXTicks = false;
chart.showYTicks = false;

We don’t want our chart to render axes at all, so we will draw them with the color of the chart background. You can also draw them with a transparent brush:

chart.theme.axisStroke = new Drawing.Brush(Drawing.Color.knownColors.White);

The tooltip renders automatically when the user hovers a bar. We can customize it with the properties of the static Tooltip class:

ToolTip.brush = new Drawing.Brush("#fafafa");
ToolTip.pen = new Drawing.Pen("#9caac6");
ToolTip.textBrush = new Drawing.Brush("#5050c0");
ToolTip.horizontalPadding = 6;
ToolTip.verticalPadding = 4;
ToolTip.horizontalOffset = 76;
ToolTip.verticalOffset = 34;
ToolTip.font = new Charting.Drawing.Font("Verdana", 12);

We add some padding to the tooltip text and increase its font size. We also render the tooltip with a little offset that will place it inside the bar, ater the inner label.

V. Styling and Legend

Styling o the charts is done through instances of SeriesStyle derived classes. The instance is assigned to the seriesStyle property of the Chart In our case we want to color each bar in three sections. That means the portion of the bar that corresponds to the same series is colored in the same color for all its members. That kind of styling is supported by the PerSeriesStyle class. It accepts a list with brushes and strokes and paints all elements of the series corresponding to the index of the brush in the list with this brush:

// create bar brushes
var thirdBrush = new Drawing.Brush("#97b5b5");
var secondBrush = new Drawing.Brush("#5a79a5");
var firstBrush = new Drawing.Brush("#003466");

// assign one brush per series
var brushes = new Collections.List([firstBrush, secondBrush, thirdBrush]);
chart.plot.seriesStyle = new Charting.PerSeriesStyle(brushes, brushes);

The theme property is the main property for styling the chart. The Theme class exposes fields for customizing the appearance of all chart elements. We first adjust the font and size of the axis labels – remember we have labels only at the Y-axis:

chart.theme.axisTitleFontSize = 14;
chart.theme.axisLabelsFontSize = 11;
chart.theme.axisTitleFontName = "Verdana";
chart.theme.axisLabelsFontName = "Verdana";
chart.theme.axisLabelsFontSize = 14;
chart.theme.axisStroke = new Drawing.Brush(Drawing.Color.knownColors.White);

The labels inside the bars are called data labels and there are dataLabels*** properties that regulate their appearance:

chart.theme.dataLabelsFontName = "Verdana";
chart.theme.dataLabelsFontSize = 14;
chart.theme.dataLabelsBrush = new Drawing.Brush("#ffffff");

The dataLabelsBrush is also used when the legend labels are rendered. In order to make them visible we need to set a darker background for the legend:

chart.theme.legendBackground = new Drawing.Brush("#cccccc");
chart.theme.legendBorderStroke = new Drawing.Brush("#cecece");

The labels inside the legend are taken from the title property of the Series instances:

series.item(0).title = "CAREER START";
series.item(1).title = "MIDDLE OF CAREER";
series.item(2).title = "CAREER END";

Finally we should not forget to call the draw method that actually renders the chart:

chart.draw();

With this our costimization of the chart is done. You can download the source code of the sample and the MindFusion JavaScript libraries used from this link:

Download the Horizontal Stacked Bar Chart Sample: Source Code and Libraries

About Charting for JavaScript: MindFusion library for interactive charts and gauges. It supports all common chart types including 3D bar charts. Charts can have a grid, a legend, unlimitd number of axes and series. Scroll, zoom and pan are supported out of the box. You can easily create your own chart series by implementing the Series interface.
The gauges library is part of Charting for JavaScript. It supports oval and linear gauge with several types of labels and ticks. Various samples show you how the implement the gauges to create and customize all popular gauge types: car dashboard, clock, thermometer, compass etc. Learn more about Charting and Gauges for JavaScript at https://mindfusion.eu/javascript-chart.html.

Pie Chart with Custom Labels in WinForms

In this tutorial we will build the following pie chart using MindFusion Charts and Gauges for WinForms library:

This is a PieChart with a custom Series class that extends the capabilities of a standard PieSeries We need to create a custom Series class because we want the inner labels to follow a special format and we want the outer labels to be rendered as legend items not near pie pieces.

You can download the source code of the sample together with the MindFusion libraries used from the link at the bottom of the post.

I. General Setup

We create an empty WinForms application in C# with Visual Studio. We create a folder called References and there we copy the MindFusion.*.dll -s that we will need. They are:

MindFusion.Charting.dll
MindFusion.Charting.WinForms.dll
MindFusion.Common.dll

We reference those files in our project: we right-click the References folder in the Solution Explorer and click on “Add Reference”. Then we browse to our local References folder and add the 3 dll-s.

We have installed MindFusion Charts and Gauges from the website: http://mindfusion.eu/ChartWinFormsTrial.zip and now we need only to find the PieChart control, drag and drop it onto the WinForms Form of our application.

II. The Custom Pie Series

When you create a custom series you need to implement the Series interface. You need to declare several methods and properties, and one event. Let’s start with the constructor.

We need our chart to use one array with data and one array with labels. We declare two class variables for that and assign to them the values that we receive for them in the constructor. We name the new class CustomPieSeries:

public class CustomPieSeries : Series
{
	public CustomPieSeries(IList data, IList legendLabels )
	{
		values = data;		
		_legendLabels = legendLabels;

		//sum up all pie data
		total = 0.0;
		for (int i = 0; i < data.Count; i++)
			total += data[i];
	}

	IList values;	
	IList _legendLabels;
        double total = 0L;
}

We have added a new class variable called total. It is needed to keep the sum of all data for the chart. We will use this variable when we calculate the percent value of each pie piece.

The SupportedLabels property of type LabelKinds is the way to set which labels will be rendered by the new series. We want tooltips, inner labels and legend labels. There is no special enum field for legend labels. You just set which labels form the series should be rendered as legend items. We decide to use for this the ZAxisLabel, because it is not drawn anywhere on the pie chart and we won’t see it doubled by the legend labels. So, we say that the chart supports ZAxisLabels and we will tell the series that the ZAxisLabels must be rendered in the legend. We will do this later.

public LabelKinds SupportedLabels
{
	get { return LabelKinds.InnerLabel | LabelKinds.ToolTip | LabelKinds.ZAxisLabel; }
}

The GetLabel method is the place where we must return the correct label according to the type and position of the label, which are provided as arguments.

public string GetLabel(int index, LabelKinds kind)
{
	double percent = (values[index] / total) * 100;
	if (kind == LabelKinds.InnerLabel)
		return percent.ToString("F2") + "%\n" + values[index].ToString();
	if (kind == LabelKinds.ToolTip)
		return "Number of interns: " + values[index].ToString("F0") + 
			"\nPercent of total: " + percent.ToString("F2") + "%";

	if (kind == LabelKinds.ZAxisLabel)
		return _legendLabels[index].ToString();

	return null;
}

Here we have the chance to work over the raw data that we have for the series and return the desired labels as a string. We want the inner label to appear as the data value together with its percent representation. We calculate the percent thanks to the total variable and format it in an appropriate way:

 if (kind == LabelKinds.InnerLabel)
	return percent.ToString("F2") + "%\n" + values[index].ToString();

We do the same with the tooltips. We add an explanation text to the tooltip of each piece:

if (kind == LabelKinds.ToolTip)
	return "Number of interns: " + values[index].ToString("F0") + 
		"\nPercent of total: " + percent.ToString("F2") + "%";

The ZAxisLabel is the easiest to do. It will be used by the legend and we perform no special formatting upon it – we just return the label corresponding to the given index.

if (kind == LabelKinds.ZAxisLabel)
		return _legendLabels[index].ToString();

Among the other notable members of the Series interface are the Dimensions and Title properties. Dimensions is the property that specifies how many data dimensions the series has. They are 1 for charts that use one array of data, 2 for axes that use X and Y data, and 3 for 3D charts, which need X, Y and Z data. In our case we return 1 as property value because pie charts, just like radar charts, use only one data array.

public int Dimensions
{
	get { return 1; }
}

The Title property returns the Series title. This is an important property but in our case we will not use it. A common use case for Title is to be rendered in legends. We will not render the Series title in the legend, so we return an empty string.

public string Title
{
	get { return ""; }
}

Building the CustomPieSeries is an easy task:

var values = new List { 23, 54, 17, 9 };
pieChart1.Series = new CustomPieSeries(
	values,				
	new List()
	{
		" <1 month", " <=3 months", " <=6 months", " >6 months"
	}
);

We create a new instance of our new class and assign it to the Series property of the PieChart control. We provide a list with the data and the labels that we want to appear as a legend.

III. The Legend

The legend in the PieChart control is regulated by the LegendRenderer property. We set the ShowSeriesElements property to true to let the chart know that we want the data from the series to be rendered as labels and not the title:

pieChart1.LegendRenderer.ShowSeriesElements = true;

Then we use the ElementLabelKind property to specify which labels we want to use in the legend. These are the ZAxisLabels:

pieChart1.LegendRenderer.ElementLabelKind = LabelKinds.ZAxisLabel;

The other properties are self-explanatory. We use the Title property to set the legend title and set a transparent brush for both the Background and the BorderStroke = “Duration”;

pieChart1.LegendRenderer.Background = new SolidBrush(Color.Transparent);
pieChart1.LegendRenderer.BorderStroke = new SolidBrush(Color.Transparent);

IV. Styling

Styling the chart is done through the Theme property and through styles. There are different style classes available and in our case we will use the PerElementSeriesStyle class. This class accepts as arguments for the brushes and strokes lists with lists that contain the brushes. Then, it colors each element in each series with the corresponding brush in the array at the corresponding index. Our PieChart needs just one list with brushes and strokes. The stroke thicknesses are also added as nested arrays:

pieChart1.Plot.SeriesStyle = new PerElementSeriesStyle()
{
	Fills = new List<List>()
	{
		new List()
		{
			new SolidBrush(Color.FromArgb(158, 212, 224)),
			new SolidBrush(Color.FromArgb(187, 236, 247)),
			new SolidBrush(Color.FromArgb(212, 204, 196)),
			new SolidBrush(Color.FromArgb(245, 210, 179))					         }
	},

	Strokes = new List<list>()
	{
		new List()
		{
			new SolidBrush(Color.White)
						
		}
	},
	StrokeThicknesses = new List<list>()
	{
		new List()
		{
			3.0
		}
	}
};

We also set some appearance properties through the Theme field:

pieChart1.Theme.DataLabelsFontSize = 10;
pieChart1.Theme.HighlightStroke = new SolidBrush(Color.FromArgb(237, 175, 120));
pieChart1.Theme.HighlightStrokeThickness = 4;

The HighlightStroke is used to mark the selected chart element by mouse hover. The DataLabelsFontSize is used not only by painting the inner labels but also by painting the labels in the legend.

Finally we set the Title of the chart:

pieChart1.Title = "Internship Statistics";

And that is the end of this tutorial. You can download the code for the chart together with the MindFusion.*.dll libraries used from this link:

Pie Chart with Custom Labels Source Code Download

You can refer to MindFusion helpful support team with any technical questions regarding the WinForms Charts & Gauges control. Use the forum at: https://mindfusion.eu/Forum/YaBB.pl?board=mchart_disc

About MindFusion Charts and Gauges for WinForms: MindFusion Charts and Gauges for WinForms offers a long list of features that enables developer to build any type of chart, gauge or dashboard. The smart API of the library provides the option different chart elements like plots, axes, labels, and series to be combined and arranged in order to build any type of custom looking chart. The library also supports out of the box the most common chart types together with a set of their widely used properties. The gauge control is indispensable part of the library and offers a linear and oval gauge together with a variety of samples that provide you with the most common types of gauges used: clock, compass, thermometer, car dashboard and more. More about MindFusion Charts and Gauges for WinForms at: https://mindfusion.eu/winforms-chart.html.

Combo Chart with the Free JS Chart Library

MindFusion Free Js Chart is a charting library that enables you to create and customize the most popular chart types in pure JavaScript. The library is free for commercial use. No attribution is required.

Here we will take a brief look at the steps you need to take to build this beautiful combo chart from scratch.

I. Setup

The chart needs an HTML Canvas element to render onto and we create one in our web page:

<canvas id="combiChart" width="400" height="400"></canvas>

It is important to provide an id to the Canvas element, because we will reference it from the JavaScript code.

We also need to reference the two JavaScript libraries that provide the charting functionality:

<script type="text/javascript" src="Scripts/MindFusion.Common.js"></script>
<script type="text/javascript" src="Scripts/MindFusion.Charting.js"></script>

And we add a reference to another JavaScript file, that will hold the code for the combo chart:

<script type="text/javascript" src="CombiChart.js"></script>

II. Chart Settings

We create the chart control using the HTML Element of the Canvas:

var chartEl = document.getElementById('combiChart');
chartEl.width = chartEl.offsetParent.clientWidth;
chartEl.height = chartEl.offsetParent.clientHeight;

var chart = new Controls.BarChart(chartEl);

We create a bar chart, to which we will add line rendering capabilities. It is also possible to create a line chart and add rendering of bars to it.

Next, we add a title and a grid to the chart:

chart.title = "Corporate Sales";
chart.titleMargin = new Charting.Margins(0, 20, 0, 20);
chart.gridType = GridType.Horizontal;
chart.barSpacingRatio = 1.5;

The barSpacingRatio indicates how much free space will be left between the group of bars relative to the bar width.

III. Chart Series

We create two series for the bars. Free JS Chart offers a variety of series types to choose from and we use two different series for the bars. The first one is BarSeries We use it because it supports setting the X-labels by default:

var labels = new Collections.List([
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
]);

var series1 = new Charting.BarSeries(new Collections.List([2, 4, 6,  8, 10, 12, 14, 16, 18, 20, 22, 24]), null, null, labels);

The other series of type SimpleSeries – it needs only two parameters – a list with the data and a list with the labels:

var series2 = new Charting.SimpleSeries(new Collections.List([1.4, 8, 13, 15, 13, 8, 2, 8, 13, 15, 13, 8]), null);

We don’t have labels, so we set null. Then we add the series to a collection:

var series = new Collections.ObservableCollection(new Array(series1, series2));
chart.series = series;

and assign the collection to the series property of the chart. We create the line series as an instance of the Series2D class:

//the line series
var series3 = new Charting.Series2D(new Collections.List([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]), new Collections.List([1.7, 6, 10.5, 11.5, 11.5, 10, 8, 12, 15.5, 17.5, 17.5, 16]), null);
series3.title = "Average value";
var lseries = new Collections.ObservableCollection(new Array(series3));

We add it to another collections.

The chart renders by default bars. We will make it render a line series with the help of a LineRenderer We create an instance of the LineRenderer class and provides it with the collection of series that we want to appear as lines. In our case it is just one:

//add a renderer for the line series
var lRenderer = new Charting.LineRenderer(lseries);
lRenderer.seriesStyle = new Charting.UniformSeriesStyle(lbrush, lstroke, 6);
chart.plot.seriesRenderers.add(lRenderer);

Each chart has a plot and the plot has a SeriesRenderer property that holds all renderers for the chart data. By default, a bar chart has a BarRenderer Now we add to this collection the LineRenderer that will draw the series in lseries as lines.

IV. Legend

The chart legend is rendered when showLegend is set to true:

//legend settings
chart.showLegend = true;
chart.legendMargin = new Charting.Margins(10, 10, 10, 10);
chart.legendTitle = "Year";

We set the title of the legend to be “Year” and add some margin. The labels of the legend are taken from the title property of each Series Since the series are rendered by two different renderers we need to tell the legend which are the renders so it can take the labels from both of them and not only from the bar series. This is done with the content property of the legendRenderer:

chart.legendRenderer.content = chart.plot.seriesRenderers;

V. Styling

The styling of the Series is done with different Style instances. For the bar chart we use a PerSeriesStyle instance. It colors all elements of a given series with the respective brush and stroke in the brushes and strokes instances that were provided as parameters:

var firstBrush = new Drawing.Brush("#8898B8");
var secondBrush = new Drawing.Brush("#4E567D");
var firstStroke = new Drawing.Brush("#60759f");
var secondStroke = new Drawing.Brush("#3b415e");

 // assign one brush per series
var brushes = new Collections.List([firstBrush, secondBrush]);
var strokes = new Collections.List([firstStroke, secondStroke]); 
chart.plot.seriesStyle = new Charting.PerSeriesStyle(brushes, strokes);

We assign this style to the seriesStyle property of the plot. The line series is colored with an instance of the UniformSeriesStyle class. It applies one brush and one stroke to all elements in all series:

var lbrush = new Drawing.Brush("#F49B96");
var lstroke = new Drawing.Brush("#f07b75");

//add a renderer for the line series
var lRenderer = new Charting.LineRenderer(lseries);
lRenderer.seriesStyle = new Charting.UniformSeriesStyle(lbrush, lstroke, 6);

The third argument indicates the stroke thickness. Note that now we assign the new style to the seriesStyle property of the LineRenderer rest of the settings for the chart appearance are in the theme property:

//theme settings for customizing the chart's appearance
chart.theme.legendBackground = new Drawing.Brush("#f2f2f2");
chart.theme.legendTitleFontSize = 14;
chart.theme.legendBorderStroke = new Drawing.Brush("#cecece");

chart.theme.axisTitleFontSize = 14;
chart.theme.axisLabelsFontSize = 12;
chart.theme.axisTitleFontName = "Verdana";
chart.theme.axisLabelsFontName = "Verdana";

chart.theme.dataLabelsFontName = "Verdana";
chart.theme.dataLabelsFontSize = 12;

chart.theme.gridLineStyle = Drawing.DashStyle.Dash;
chart.theme.gridColor1 = chart.theme.gridColor2 = new Drawing.Color("#ffffff");
chart.theme.gridLineColor = new Drawing.Color("#cecece");

chart.theme.highlightStroke = new Drawing.Brush("#F49B96");
chart.theme.highlightStrokeThickness = 4;

Here we change the font for the labels, style the legend and the grid. Finally, we customize the stroke that highlights chart elements when the user hovers with the mouse over them.

And that’s the end of this tutorial. You can download the full source code of the sample with the libraries of Free JS Chart from this link: https://mindfusion.eu/samples/javascript/free_chart/CombiChart.zip

You can find out more about MindFusion Free JS Chart library at https://mindfusion.eu/free-js-chart.html.

Custom Nodes in WPF Diagram

Here we will look how to define custom diagram nodes in the WPF diagram control, how to style them, how to make their properties appear in the property grid and how to save and load them with the diagram’s saveToXml and loadFromXml methods.

Here is a screenshot of our SubjectNode custom node class that is used in an application for a school curriculum:

I. XAML Template

You will need to add a XAML template for the node us you are creating a custom node because you want to have special-looking nodes. Let’s create a node that has 3 text fields and a background. We will declare the template for this node that we call SubjectNode in XAML this way:

<style targettype="local:SubjectNode">
    <Setter Property="Template">
      <Setter.Value>
        <DataTemplate DataType="local:SubjectNode">
          <Grid>

            <Rectangle
		Stroke="{Binding Stroke}"
		Fill="{Binding Background}" />

            <Grid>              

               <StackPanel Margin="4,8,0,0"  Orientation="Vertical" Grid.Column="1">
                <TextBlock Text="{Binding Subject}" FontWeight="800" Foreground="Black" />
                <TextBlock Text="{Binding Teacher}" Foreground="Blue" />
                <TextBlock Text="{Binding Remarks}" FontSize="9" Foreground="Black" />
              </StackPanel>
            </Grid>

          </Grid>
        </DataTemplate>
      </Setter.Value>
    </Setter>
  </style>

That goes in the contents of <ResourceDictionary>…..</ResourceDictionary> in the xaml file where you store this resourrce dictionary.

You see here that we use a gird as the principal layout container. There we add a rectangle, whose Fill property is bound to a property called Background in the SubjectNode. Next we have another grid that holds a StackPanel. The stack panel is with vertical orientation and it arranges the three TextBlock-s for the three custom fields of the node.

II. Declaring the Custom Node Class

When you create a custom node you need to inherit the TemplatedNode class. In the static construcotr you should call OverrideMetadata on the DefaultStyleKeyProperty to make it use the template that we’ve declared in XAML:

public class SubjectNode : TemplatedNode
{
	static SubjectNode()
	{
		DefaultStyleKeyProperty.OverrideMetadata(
			typeof(SubjectNode), new FrameworkPropertyMetadata(typeof(SubjectNode)));
}

public SubjectNode()
{			
}

Then we declare a constructor without any parameters that is required for the node to be created in XAML. If you want users to be able to create instance of the SubjectNode through drag and drop, you need to declare one more constructor:

	// Required for creating nodes by dragging them from the NodeListView
public SubjectNode(SubjectNode prototype) : base(prototype)
{
	Subject = prototype.Subject;
	Teacher = prototype.Teacher;
	Remarks = prototype.Remarks;
}

III. Properties

We declare the properties that we want: Subject, TeacherName, Remarks and Background as dependency properties the standard way:

public Brush Background
{
	get { return (Brush)GetValue(BackgroundProperty); }
	set { SetValue(BackgroundProperty, value); }
}

public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register(
	"Background",
	typeof(Brush),
	typeof(SubjectNode),
	new PropertyMetadata(new SolidColorBrush(Color.FromRgb(223, 235, 250))));

and for the text properties:

public string Remarks
{
	get { return (string)GetValue(RemarksProperty); }
	set { SetValue(RemarksProperty, value); }
}

public static readonly DependencyProperty RemarksProperty = DependencyProperty.Register(
	"Remarks",
	typeof(string),
	typeof(SubjectNode),
	new PropertyMetadata(""));

If we want the properties to be listed in a property grid we need to add a new class that inherits from DiagramNodeProperties. In it we do nothing but list the custom properties together with their type:

public class SubjectNodeProperties : DiagramNodeProperties
{
        internal string Subject;
	internal string Teacher;
	internal string Remarks;
	internal Brush Background;
} 

IV. More Options

Standard diagram nodes support undo and redo as well serialization out of the box. If you want your custom class to support those features as well you need to implement a few more methods. The methods to support undo/redo are SaveProperties and RestoreProperties. They take an instance of the DiagramItemProperties class that allows you to transfer data between the instance of the current node and its DiagramItemProperties instance that store the values of the node’s properties:

protected override void RestoreProperties(DiagramItemProperties props)
{
	base.RestoreProperties(props);
	var state = (SubjectNodeProperties)props;
	Subject = state.Subject;
	Teacher = state.Teacher;
	Remarks = state.Remarks;
	Background = state.Background;
}

protected override void SaveToXml(XmlElement xmlElement, XmlPersistContext context)
{
	base.SaveToXml(xmlElement, context);
	context.WriteString(Subject, "Subject", xmlElement);
	context.WriteString(Teacher, "Teacher", xmlElement);
	context.WriteString(Remarks, "Remarks", xmlElement);
	context.WriteBrush(Background, "Background", xmlElement);
}

The Diagram uses XML for serialization, so if you want your node to be saved and loaded correctly through the Diagram‘s saveToXml and loadFromXml methods you should implement SaveToXml and LoadFromXml. There you write the values o the custom properties of SubjectNode to XML elements and read them from XML elements as well:

protected override void SaveToXml(XmlElement xmlElement, XmlPersistContext context)
{
	base.SaveToXml(xmlElement, context);
	context.WriteString(Subject, "Subject", xmlElement);
	context.WriteString(Teacher, "Teacher", xmlElement);
	context.WriteString(Remarks, "Remarks", xmlElement);
	context.WriteBrush(Background, "Background", xmlElement);
}

protected override void LoadFromXml(XmlElement xmlElement, XmlPersistContext context)
{
	base.LoadFromXml(xmlElement, context);
	Subject = context.ReadString("Subject", xmlElement);
	Teacher = context.ReadString("Teacher", xmlElement);
	Remarks = context.ReadString("Remarks", xmlElement);
	Background = context.ReadBrush("Background", xmlElement);
}

You can download the sample that uses custom SubjectNode from https://mindfusion.eu/samples/wpf/diagram/Curriculum.zip

About Diagramming for WPF: This is the right tool to create flowcharts in WPF that always meet your requirements. The library offers more than 100 predefined node shapes, extensive event set and more than 15 exporters and importers. Each diagram that you build has a completely customizable look through styles, themes and appearance properties for each part of the flowchart. The numerous samples and detailed documentation help you learn quickly how to integrate the component into your own application. You can download the trial version, which has no feature restrictions and does not expire from the WPF Diagram Page on MindFusion website.