How to add interactions to annotations in a .NET chart
In this blog, we’ll walk you through how to let the end-users annotate and perform different operations on the annotations in a FlexChart. We know annotations are helpful in communicating information, but identifying relevant data points for annotation might not be obvious. If overused, annotations can distract user's attention from the data, so it's important to have the right balance.
If, on the other hand, we give end users flexibility to annotate on the chart while viewing, we’d both increase interaction and empower the end user to manage their information on their own.
Here’s what we’ll cover in this blog:
- Draw new annotation on the chart
- Select existing annotations
- Delete existing annotations
- Drag annotations around the chart
- Edit an annotation’s content
The following shows a glimpse of these operations in action:
(Note: We talked about increasing the effectiveness of data visualization with annotations using FlexChart in an earlier blog. We also saw developers can use FlexChart annotations to communicate meaningful information to the end-users. I recommend that blog if you haven’t read it yet.)
Introducing the EditableAnnotationLayer in .NET Charts
The 'AnnotationLayer' responsible for drawing FlexChart annotations provides the ability to add annotations at design time. For the interactive operations on annotations - like edit, select, drag, and delete - FlexChart allows developers to write their own implementation using its extensible object model.
We’ll start by extending the ‘AnnotationLayer’ class to create our own implementation: the EditableAnnotationLayer. All the code to perform the interactive operations on annotations will be part of the EditableAnnotationLayer.
public class EditableAnnotationLayer: AnnotationLayer
{
public EditableAnnotationLayer(FlexChart chart) : base(chart)
{
}
}
Before proceeding any further with the implementation, let’s define our objective more clearly. The objective here is to create an independent module that can serve as an alternative to the default AnnotationLayer whenever we need to let users perform interactive operations on the annotations.
With this objective in mind, let’s implement the EditableAnnotationLayer.
Stage 1: Preparing for the Operations
When we’re preparing for the operations, we’ll need to:
-
Determine the events to be captured for the implementation, and
-
Identify annotations on which operations are to be performed
Capturing and listening to the events
The key to implementing any of the interactive operations on the annotations is to listen to the appropriate FlexChart events for handling the end-user's actions. Primarily mouse-triggered, these actions are to be mapped individually or in combinations to the required interactive operations. So the first step is to subscribe and handle the various mouse-related events of FlexChart - MouseDown, MouseUp, MouseMove and MouseDoubleClick.
public EditableAnnotationLayer(FlexChart chart) : base(chart)
{
_flexChart = chart;
_flexChart.MouseDown += OnMouseDown;
_flexChart.MouseUp += OnMouseUp;
_flexChart.MouseMove += OnMouseMove;
_flexChart.MouseDoubleClick += OnMouseDoubleClick;
}
In the sections to follow, we’ll see how each of these event handlers play a vital role in translating the user’s actions to the interactive operations.
Identifying Annotations to perform operations
The operations on the annotations should be performed only when the user points to an annotation. Thus, whenever the user interacts with the chart, we should first be able to identify if the Mouse is pointing to an Annotation. For this purpose, hit-testing on Annotations is important. Here, we’ll be using the existing FlexChart APIs and add a HitTest() method to the EditableAnnotationLayer.
public AnnotationBase HitTest(PointF pt)
{
AnnotationBase selectedAnnotation = null;
foreach (var anno in this.Annotations.Reverse())
{
/*
if anno contains the point pt then
selectedAnnotation = anno;
break foreach
*/
}
return selectedAnnotation;
}
It should be noted here that the above code only reflects the idea behind hit-testing. The complete code can be found in the FlexChartEditableAnnotations sample.
Stage 2: Implementing the Operations
Now that the initial preparation is complete, let’s proceed to the next stage: implementing the operations.
Operation #1: Drawing new Annotations
The most challenging part of the EditableAnnotationLayer implementation is the process of drawing new Annotations on the chart. Multiple factors need to be considered while drawing a new annotation, including: the shape of the Annotation, its attachment, location, dimensions, etc. To keep things simple, we’ll divide this entire process into 4 steps.
The first step involves determining the new annotation’s type and its attachment. This would be done by the following new properties defined in the EditableAnnotationLayer class:
public Type NewAnnotationType { get; set; }
public AnnotationAttachment Attachment { get; set; }
In the second step, we’ll capture the location from the end-user’s starting point. This would be done in the MouseDown event.
private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
var pos = e.Location;
//.....
this._start = pos;
}
You’ll notice that the annotation hasn’t been added yet. The annotation is added only after we’ve verified that it crossed a threshold dimension. This is important to ensure that we don’t have annotations on the chart that are not visible to the end-users.
The third step gets a little trickier. Not only do we have to add the annotations, but we also need to keep updating the dimensions as the user drags the mouse.
The following code would give you an idea of how this works.
private void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
/*
if (AllowAdd && _start != PointF.Empty)
{
if (_drawing)
this.UpdateAnnotaion(e.Location);
else
{
Size sz = _start - e.Location
Size threshold = //Define threshold
if sz > threshold then
AddAnnotation(_start);
}
}
*/
}
private void AddAnnotation(PointF p)
{
/*
annotationToAdd = CreateInstance of NewAnnotationType
this.Annotations.Add(annotationToAdd);
drawing = true; //Start drawing on next MouseMove
*/
}
private void UpdateAnnotaion(PointF p)
{
// Update the annotationToAdd’s dimension
}
In the last step, we end the drawing operation when the user releases the mouse button.
private void OnMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (AllowAdd)
{
_start = _end = PointF.Empty;
_drawing = false;
}
}
The complete implementation includes more checks and has the options for drawing Text and Polygon shaped annotations. We’ve defined two additional polygon-drawing properties called PolygonAddFunc and PolygonResizeFunc; we’ll explain those in the next annotation blog.
Operation #2: Selecting Annotations
Selecting annotations is core to any operations performed on the annotations. The process to implement Selection is fairly a simple one.
First, we’ll define two new properties in the EditableAnnotationLayer: SelectedAnnotation and SelectionStyle. Then, using the HitTest() method, we’ll determine whether the user has clicked on an existing Annotation or not. If the user has clicked on an Annotation, we store its reference in the ‘SelectedAnnotation’ property and apply the ‘SelectionStyle’ to highlight it:
public ChartStyle SelectionStyle { get; set; }
public AnnotationBase SelectedAnnotation
{
get { return _selectedAnnotation; }
private set
{
if (_selectedAnnotation != value)
{
//Reset the Style of previously selected annotation
_selectedAnnotation.SetStyle(_originalStyle);
//Update the reference to the annotation now selected
_selectedAnnotation = value;
//Store the style of new selected Annotation
_originalStyle = _selectedAnnotation.CloneStyle();
_selectedAnnotation.SetStyle(SelectionStyle);
}
}
}
private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
var pos = e.Location;
SelectedAnnotation = HitTest(pos);
}
Operation #3: Deleting Annotations
Deleting annotations is a two-step process. The first step involves selecting an annotation. For this, the user can use the Selection operation we’ve implemented.
The second step involves removing the ‘SelectedAnnotation_’_ from the Annotations collection of the layer. This can be done in any user-initiated action, like a button-click or context menu-click. Neither of these steps require any additional changes to the EditableAnnotationLayer itself.
The user-initiated action can be captured at the application level. For example, we’ll be using a ContextMenu and the ‘Delete’ key in our FlexChartEditableAnnotations sample to delete the annotations.
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.Delete)
//al = EditableAnnotationLayer object
al.Annotations.Remove(al.SelectedAnnotation);
}
Operation #4: Dragging Annotations
Dragging the annotations is a three-step process that handles the MouseDown, MouseMove and MouseUp events. The first step: select an annotation and save its initial location. This step also marks the beginning of the dragging process.
private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
var pos = e.Location;
SelectedAnnotation = HitTest(pos);
if (SelectedAnnotation != null && e.Button == MouseButtons.Left)
{
_oldPoint = pos;
_isDragging = true; // Start Dragging
}
}
In the next step, we’ll calculate the difference by which the annotation’s location needs to be changed, and accordingly offset the annotation’s location:
private void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
/*
if(_isDragging)
{
1. newPoint = e.Location;
2. difference = newPoint – oldPoint
3. newLocation = oldLocation.Offset(difference)
4. SelectedAnnotation.Location = newLocation
5. oldPoint = newPoint
}
*/
}
Finally, we end the drag operation when the mouse is released:
private void OnMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (_isDragging)
{
_isDragging = false;
}
}
Operation #5: Editing Annotations
To edit annotations, we need to present the end-users with an editor. We’ll define a new property of type ‘TextEditor’ in our EditableAnnotationLayer class:
public TextEditor ContentEditor { get; set; }
‘TextEditor’ is an abstract class:
This approach also allows you to create your own custom editors without modifying the EditableAnnotationLayer every time they’re used in different applications. (We’ll see in our next blogs how to define a custom editor for annotations.)
We’ve defined our own custom editor for the FlexChartEditableAnnotations sample:
private void OnMouseDoubleClick(object sender, MouseEventArgs e)
{
if (ContentEditor == null || SelectedAnnotation == null)
return;
if (this.HitTest(e.Location) == this.SelectedAnnotation && SelectedAnnotation.IsEditable())
ShowContentEditor(this.SelectedAnnotation, e.Location);
else
HideContentEditor();
}
This completes the implementation of EditableAnnotationLayer. We’ve covered implementing five interactive operations on annotations that we listed at the start this blog.
Check out the FlexChartEditableAnnotations sample for WinForms and WPF to gets a hands-on experience interacting with Annotations in an application. Also, please stay tuned for our next blog of this series, when we’ll focus on consuming the EditableAnnotationLayer for your application.