I managed to come up with a solution which turned out to be a great exercise in what it truly possible with the ChartModifierBase class. The full source for XABCDModifier is attached but I want to highlight a few points for readers. The code snippets below are not all of the class but merely the high points. I’ve documented the attached code really well for clarity.
1. General Requirements
– Quick to create for the user.
– Fully utilize SciChart’s surface calculations for panning, zooming, etc.
– Move with the data series.
– Be placed anywhere on the visible chart.
– Serve as a template for other annotations which I may want to “link” in the future.
– Support dynamic creation.
– Click-driven.
– Point must be editable without breaking the pattern (lines stay connected or “linked”).
2. Solution Overview
– Custom chart modifier inheriting from ChartModifierBase
– Uses double click to activate and deactivate (primarily for testing, needs to be data bound in production so something which won’t interfere with other modifiers).
– Uses a temporary storage of Dictionary<string, AnnotationBase> for easy access.
– Uses the Tag property for distinguishing these annotations from independent annotations created later.
– Uses data binding in code
Enabling/Disabling
There’s definitely nothing Earth-shattering here and this is not how I’d do this in production. Enabling and disabling here is achieved by a simple double click which is very convenient for testing and keeping the modifier free from external dependencies.
/// <summary>
/// Called when a Mouse DoubleClick occurs on the parent <see cref="T:SciChart.Charting.Visuals.SciChartSurface" />
/// </summary>
/// <remarks>
/// This overridden method is only used to activate/deactive the modifer for testing purposes.
/// In a production setting, this modifier will need to be activated via a view model based on appropriate criteria.
/// </remarks>
/// <param name="e">Arguments detailing the mouse button operation</param>
public override void OnModifierDoubleClick(ModifierMouseArgs e)
{
base.OnModifierDoubleClick(e);
if (_isInDrawMode)
{
_linkedLines.Clear();
_isCreationActive = false;
_currentAnnotation.IsEditable = true;
}
// turn on and off via double click
_isInDrawMode = !_isInDrawMode;
}
Handling Key Inputs
In my opinion, a complete user experience can’t be achieved without handling key presses so I implemented a few. The escape key was originally used to serve as a short circuit during testing and should probably remove the entire pattern but here it just stops drawing. The rest of the code here is pretty self-explanatory.
/// <summary>
/// Called when the KeyDown event is fired for the Master of the current <see cref="P:SciChart.Charting.ChartModifiers.ChartModifierBase.MouseEventGroup" />.
/// </summary>
/// <remarks>
/// Used during testing environment to stop creation without a mouse action.
/// </remarks>
/// <param name="e">Arguments detailing the key event</param>
public override void OnModifierKeyDown(ModifierKeyArgs e)
{
if (e.Key == Key.Delete)
{
TryRemoveAnnotation();
}
if (e.Key == Key.Escape)
{
_currentAnnotation.IsEditable = true;
_isInDrawMode = false;
_isCreationActive = false;
_linkedLines.Clear();
return;
}
if (!_isInDrawMode) return;
if (e.Key != Key.LeftCtrl) return;
base.OnModifierKeyDown(e);
_currentAnnotation.IsEditable = true;
_isInDrawMode = false;
_isCreationActive = false;
if (_linkedLines.Count > 1)
{
}
}
Pattern Creation
Here is the meat and potatoes of my solution. I’ll go over the helper methods below. Here’s the full OnModifierMouseDown override. First, I make sure I’m in draw mode which is set in the OnModifierDoubleClick. Once I’m drawing, there are 2 cases with a XABCD pattern. The first is just drawing the simple lines which are XA, AB, BC, and CD. Once those have been drawn, I now have a line count of 5. It’s 5, not 4, because I’ve already started drawing the 5th line but just haven’t place it yet. Once I’m at this point, I don’t want to try to precision click the DB and BX lines since I can easily get the B and X points from previously drawn lines (this is why I store them in _linkedLlines). Therefore, I just create the next line which is BX then start placing and binding the remaining points.
/// <summary>
/// Called when a Mouse Button is pressed on the parent <see cref="T:SciChart.Charting.Visuals.SciChartSurface" />
/// </summary>
/// <remarks>
/// This is the primary method for utilizing this modifier.
/// </remarks>
/// <param name="e">Arguments detailing the mouse button operation</param>
public override void OnModifierMouseDown(ModifierMouseArgs e)
{
if (!_isInDrawMode) return;
base.OnModifierMouseDown(e);
if (_isInDrawMode)
{
// handle subsequent clicks on an existing annotation
if (_isCreationActive)
{
CompletePatternLeg(e);
CreatePatternLeg(e);
LinkAnnotations(_currentAnnotation, _previousAnnotation);
if (_linkedLines.Count == 5)
{
CompletePatternLeg(e);
CreatePatternLeg(e);
LinkAnnotations(_currentAnnotation, _previousAnnotation);
_previousAnnotation.X2 = _linkedLines["BC"].X1;
_previousAnnotation.Y2 = _linkedLines["BC"].Y1;
CompletePatternLeg(e);
_currentAnnotation.X1 = _linkedLines["DB"].X2;
_currentAnnotation.Y1 = _linkedLines["DB"].Y2;
_currentAnnotation.X2 = _linkedLines["XA"].X1;
_currentAnnotation.Y2 = _linkedLines["XA"].Y1;
// need to bind BX X1Y1 to DB X2Y2
LinkAnnotations(_linkedLines["BX"], _linkedLines["DB"]);
LinkAnnotations(_linkedLines["BC"], _linkedLines["DB"]);
LinkAnnotations(_linkedLines["BX"], _linkedLines["AB"]);
LinkAnnotations(_linkedLines["XA"], _linkedLines["BX"]);
CreateDataBinding(_linkedLines["BX"], AnnotationBase.X1Property, _linkedLines["BC"], "X1");
CreateDataBinding(_linkedLines["BX"], AnnotationBase.Y1Property, _linkedLines["BC"], "Y1");
CreateDataBinding(_linkedLines["DB"], AnnotationBase.X2Property, _linkedLines["AB"], "X2");
CreateDataBinding(_linkedLines["DB"], AnnotationBase.Y2Property, _linkedLines["AB"], "Y2");
_previousAnnotation.IsSelected = false;
_currentAnnotation.IsEditable = true;
_currentAnnotation.IsSelected = false;
_isInDrawMode = false;
_isCreationActive = false;
_linkedLines.Clear();
}
}
// handle initially creating an annotation
else
{
_isCreationActive = !_isCreationActive;
CreatePatternLeg(e);
}
}
}
The general creation is
CompletePatternLeg(e); // we've already started drawing so complete that line on mouse click
CreatePatternLeg(e); // now create a new line
LinkAnnotations(_currentAnnotation, _previousAnnotation); // link the just completed line X2Y2 to the newly created X1Y1
Update X2Y2 with OnModifierMouseMove
Of note here is if (_linkedLines.Count == 5)
since this allows me to finish the XABCD pattern programmatically instead of trying to precision click. The data binding of the points for the last 2 lines is a little different so I handled those individually.
Helper Methods
These are what make everything “just work”
Adding a new line. Self explanatory but important if you want to see your work.
/// <summary>
/// Adds the annotation.
/// </summary>
/// <remarks>
/// The _linkedLines dictionary is used to allow connecting non-consecutive annotations in a group or pattern.
/// _linkedLines dictionary is cleared after the XABCDModifier is deactivated.
/// </remarks>
/// <param name="legName">Name of the leg.</param>
/// <param name="annotation">The annotation.</param>
private void AddAnnotation(string legName, AnnotationBase annotation)
{
ParentSurface.Annotations.Add(annotation); // required for annotation to be rendered on SciChartSurface
_linkedLines.Add(legName, annotation); // used internally for advanced/custom annotation linking
}
Data Binding Line Points
This is by far the most crucial piece of making this work with annotations. This method works for the XA, AB, BC, CD lines then I manually handle the DB and BX lines in the Count == 5 case. This method is best thought of as reading the parameters from left to right. Bind TARGET’s TARGETPROPERTY to SOURCE’s SOURCEPROPERTY. I tried using BindingMode.TwoWay but it didn’t work as expected so below is what did work.
/// <summary>
/// Creates the data binding.
/// </summary>
/// <remarks>
/// To prevent annotations from separating, call this function once for each annotation's properties, i.e. line1's X2Y2 and line2's X1Y1.
/// This will allow either annotation itself, or adorner handles to be selected and still keep the annotations connected.
/// </remarks>
/// <param name="target">The target.</param>
/// <param name="targetProperty">The source property. This must be a DependencyProperty.</param>
/// <param name="source">The source.</param>
/// <param name="sourceProperty">The string representation of the property to be data bound.</param>
private void CreateDataBinding(AnnotationBase target, DependencyProperty targetProperty, AnnotationBase source, string sourceProperty)
{
Binding binding = new Binding();
binding.Mode = BindingMode.OneWay;
binding.Source = source;
binding.Path = new PropertyPath(sourceProperty);
target.SetBinding(targetProperty, binding);
}
Creating and completing pattern legs is fairly straightforward but it has some built in “gotchas” that I encountered. When creating an annotation, you must set all coordinate properties ****before**** it may be added to the ParentSurface.Annotations collection. In a slight of hand, I just set both sets of X and Y to the current mouse point position since I’ll just turn right around and update X2Y2 in OnModifierMouseMove. This allow me to create the annotation and receive the real time visual feedback when placing the end of the line.
private void CompletePatternLeg(ModifierMouseArgs e)
{
_previousAnnotation = _currentAnnotation;
_previousAnnotation.IsEditable = true; // can't be set while placing (interferes with mouse clicks), must be after placed
_previousAnnotation.IsSelected = false;
}
private void CreatePatternLeg(ModifierMouseArgs e)
{
_currentAnnotation = CreateNewLine();
SetAllCoordinates(_currentAnnotation, e.MousePoint);
AddAnnotation(GetXABCDLegName(), _currentAnnotation);
}
Creating the line itself is about as simple as it gets. The X/YAxisId must be set though, ask me how I know…
Naming the lines is a simple switch statement that is hard coded.
/// <summary>
/// Creates the new line.
/// </summary>
/// <remarks>
/// The Tag property is specifically set so these annotations may be differentiated
/// from other annotations in <see cref="T:SciChart.Charting.Visuals.SciChartSurface.Annotations"/>.
/// When utilizing from another class or resource, the Tag property must be explicitly casted to a string.
///
/// This function could be easily extended to support other annotation types by switching on a DependencyProperty
/// that specifies the type of annotations this modifier should create.
/// </remarks>
/// <returns>AnnotationBase.</returns>
private AnnotationBase CreateNewLine()
{
/*var gb = new LinearGradientBrush();
gb.MappingMode = BrushMappingMode.RelativeToBoundingBox;
gb.StartPoint = new Point(0, 0);
gb.EndPoint = new Point(1, 1);
gb.GradientStops.Add(new GradientStop(Colors.Blue, 0.0));
gb.GradientStops.Add(new GradientStop(Colors.LawnGreen, 1.0));*/
var line = new LineAnnotation
{
//Tag = ANNOTATION_IDENTIFIER,
Tag = GetXABCDLegName(),
StrokeThickness = 2,
Stroke = Brushes.LawnGreen,
XAxisId = ParentSurface.XAxis.Id,
YAxisId = ParentSurface.XAxis.Id
};
line.Selected += OnLineSelected;
return line;
}
Hopefully this will help the SciChart community as much as it has helped me. This was an awesome experience and really forced me to understand the SciChart API. Also, a big thanks to @Andrew for helping me avoid going down the rabbit hole of trying to do this with a RenderableSeries since I’m using CategoryDateTimeAxis (CDTA requires a 1:1 point count for any additional series even XyDataSeries).
Cheers,
Jason
End result:
- Jason Neeley answered 8 years ago
- last active 8 years ago
NOTE: RubberBandXyZoomModifier, ZoomPanModifier, MouseWheelZoomModifier, XAxisDragModifier are not compatible with AutoRange.Always. To use AutoRange.Always as well as manual zooming, you will need to temporarily disable AutoRange as you zoom, e.g. on mouse-down disable AutoRange and on mouse double-click re-enable it.
Two ways you could do this:
- Handle MouseDown/MouseDoubleClick on the parent SciChartSurface
- Do this by creating a class which inherits RubberBandXyZoomModifier and overriding OnModifierMouseDown, OnModifierMouseDoubleClick
For example
public class RubberBandXyZoomModifierEx : RubberBandXyZoomModifier
{
/// <summary>
/// When the user does mouse drag & mouse up, stop AutoRange and perform the zoom
/// </summary>
public override void OnModifierMouseUp(ModifierMouseArgs e)
{
// Disable AutoRange on all axis!
foreach (var axis in ParentSurface.YAxes)
{
axis.AutoRange = AutoRange.Never;
}
foreach (var axis in ParentSurface.XAxes)
{
axis.AutoRange = AutoRange.Never;
}
// Now do the zoom
base.OnModifierMouseUp(e);
}
/// <summary>
/// When the user double clicks, re-enable zoom and zoom to extents
/// </summary>
public override void OnModifierDoubleClick(ModifierMouseArgs e)
{
base.OnModifierDoubleClick(e);
// Reset AutoRange on all axis
foreach (var axis in ParentSurface.YAxes)
{
axis.AutoRange = AutoRange.Always;
}
foreach (var axis in ParentSurface.XAxes)
{
axis.AutoRange = AutoRange.Always;
}
// Now zoom to extents!
ParentSurface.ZoomExtents();
}
}
Update Oct 2014
We now have an documented workaround in the Knowledgebase showing how to mix Zoom, Pan modifiers with AutoRange. This is the technique we use in the Real Time Ticking Stock Charts example to automatically scroll the latest N points, but allow zooming and panning as well.
Best regards,
Andrew
- Andrew Burnett-Thompson answered 11 years ago
- last active 10 years ago
You know I have another idea for you, and it might just work.
Try using our XyzDataSeries. If you bind this to a RenderableSeries (e.g. a line or scatter series) it will by default using the XY values for drawing. The Z values are superfluous.
On Hit-Test you could show the Z-values as well as X and Y.
If you wanted just a time value, then store this in the Z. If however you wanted a more complex object (e.g. Metadata or ViewModel) you could store an index in here and have a separate array or list of ViewModels that you index.
In fact, I seem to remember this being asked before. So we have a sample in our TestSuite which does both storing Z-States in metadata and RolloverLabel Templating. Please see the attached.
If you have any questions please let us know!
Andrew
- Andrew Burnett-Thompson answered 11 years ago
Hi Jose,
Sorry to interrupt Yuriy’s excellent support handling! There is a little known (and not documented) feature which we included to provide users with a workaround in cases like this.
There is a property called SciChartSurface.ViewportManager (expects IViewportManager) which is queried on render to get the X and Y axis VisibleRange.
See below the implementation of DefaultViewportManager:
using System.Diagnostics; /// <summary> /// The DefaultViewportManager performs a naive calculation for X and Y Axis VisibleRange. /// On each render of the parent SciChartSurface, either autorange to fit the data (depending on the Axis.AutoRange property value), /// or return the original axis range (no change) /// </summary> public class DefaultViewportManager : ViewportManagerBase { /// <summary> /// Called when the <see cref="IAxis.VisibleRange"/> changes for an axis. Override in derived types to get a notification of this occurring /// </summary> /// <param name="axis">The <see cref="IAxis"/>instance</param> public override void OnVisibleRangeChanged(IAxis axis) { } /// <summary> /// Called when the <see cref="ISciChartSurface" /> is rendered. /// </summary> /// <param name="sciChartSurface">The SciChartSurface instance</param> public override void OnParentSurfaceRendered(ISciChartSurface sciChartSurface) { } /// <summary> /// Overridden by derived types, called when the parent <see cref="SciChartSurface" /> requests the XAxis VisibleRange. /// The Range returned by this method will be applied to the chart on render /// </summary> /// <param name="xAxis">The XAxis</param> /// <returns> /// The new VisibleRange for the XAxis /// </returns> protected override IRange OnCalculateNewXRange(IAxis xAxis) { // Calculate the VisibleRange of X Axis, depending on AutoRange property if (xAxis.AutoRange) { var newXRange = xAxis.GetMaximumRange(); if (newXRange != null && newXRange.IsDefined) return newXRange; } return xAxis.VisibleRange; } /// <summary> /// Overridden by derived types, called when the parent <see cref="SciChartSurface" /> requests a YAxis VisibleRange. /// The Range returned by this method will be applied to the chart on render /// </summary> /// <param name="yAxis">The YAxis</param> /// <param name="renderPassInfo"></param> /// <returns> /// The new VisibleRange for the YAxis /// </returns> protected override IRange OnCalculateNewYRange(IAxis yAxis, RenderPassInfo renderPassInfo) { if (yAxis.AutoRange && renderPassInfo.PointSeries != null && renderPassInfo.RenderableSeries != null) { var newYRange = yAxis.CalculateYRange(renderPassInfo); if (newYRange != null && newYRange.IsDefined) { return newYRange; } } return yAxis.VisibleRange; } }
If you create a class like the above and change the behaviour of OnCalculateNewYRange to return a VisibleRange including your threshold level then you can effectively override the AutoRanging of the chart.
Try this method and let me know how it works
Best regards,
Andrew
- Andrew Burnett-Thompson answered 12 years ago
Hi George,
This is a problem we’re trying to solve (MVVM in general) and there’s no good answer at the moment! Just a note, there may be some changes in the SeriesSource API in the next major release (quite a while off though) to make this easier.
The problem is we need to separate view & viewmodel, yet retain control over series-type, colors, point-markers from the viewmodel which are much better defined in the XAML.
If you wanted to apply the same RolloverMarker to all series, you could use an unnamed style, e.g.
<!-- Define in UserControl.Resources, or Application resources, with no key --> <Style TargetType="{x:Type SciChart:FastLineRenderableSeries}"> <Setter Property="RolloverMarkerTemplate"> <Setter.Value> <ControlTemplate> <Ellipse Width="7" Height="7" Fill="SlateGray" Stroke="SlateGray" StrokeThickness="1" /> </ControlTemplate> </Setter.Value> </Setter> </Style>
This will apply the same Rollover Marker to all FastLineRenderableSeries’ in that scope.
Perhaps another solution is to define a ResourceDictionary which contains your styles (this time keyed) and set them using an attached property? E.g. if a resource dictionary was added to the App.xaml you can retrieve styles and templates from it by calling Application.Current.Resources[“key”]. Rather than getting application resources in your viewmodel sometime’s it’s better to define a property in your viewmodel of type string, and use attached properties to apply the style.
For example:
<!-- TODO: ControlTemplates can be defined in resource dictionaries and added to App.Xaml --> <ControlTemplate x:Key="GrayMarkerTemplate"> <Ellipse Width="7" Height="7" Fill="SlateGray" Stroke="SlateGray" StrokeThickness="1" /> </ControlTemplate> <ControlTemplate x:Key="BlueMarkerTemplate"> <Ellipse Width="7" Height="7" Fill="SteelBlue" Stroke="SteelBlue" StrokeThickness="1" /> </ControlTemplate>
// Create attached properties and attached behaviours to assist and work around MVVM's limitations public class RolloverMarkerHelper : DependencyObject { public static readonly DependencyProperty RolloverTemplateKeyProperty = DependencyProperty.RegisterAttached("StyleKey", typeof (string), typeof (RolloverMarkerHelper), new PropertyMetadata(default(string), OnRolloverTemplateKeyChanged)); public static void SetRolloverTemplateKey(UIElement element, string value) { element.SetValue(RolloverTemplateKeyProperty, value); } public static string GetRolloverTemplateKey(UIElement element) { return (string) element.GetValue(RolloverTemplateKeyProperty); } private static void OnRolloverTemplateKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var element = d as BaseRenderableSeries; if (element == null) return; if (e.NewValue == null) { element.RolloverMarkerTemplate = null; return; } if (Application.Current != null) { var resource = Application.Current.TryFindResource(e.NewValue) as ControlTemplate; element.RolloverMarkerTemplate = resource; } } }
Then the template can be applied like this
_chartSeries = new ObservableCollection<IChartSeriesViewModel>(); var rSeries0 = new FastLineRenderableSeries(); RolloverMarkerHelper.SetRolloverTemplateKey(rSeries0, "GrayMarkerTemplate"); _chartSeries.Add(new ChartSeriesViewModel(ds0, rSeries0)); var rSeries1 = new FastLineRenderableSeries(); RolloverMarkerHelper.SetRolloverTemplateKey(rSeries1, "BlueMarkerTemplate"); _chartSeries.Add(new ChartSeriesViewModel(ds1, rSeries1));
I’ve attached a full example to this post. It’s not the most elegant of solutions but it does show how you can work around some of these issues by using attached properties or attached behaviours.
Hope this helps!
Andrew
- Andrew Burnett-Thompson answered 12 years ago
Got it working! I ended up using some different code to get the job done, which I’ll share here:
// Custom mouse wheel usage public override void OnModifierMouseWheel(ModifierMouseArgs e) { // Get region of axis Rect XAxisBounds = XAxis.GetBoundsRelativeTo(RootGrid); Rect YAxisBounds = YAxis.GetBoundsRelativeTo(RootGrid); // Only zoom in that axis if mouse over axis if (XAxisBounds.Contains(e.MousePoint)) { // If zooming in if (e.Delta > 0) XAxis.ZoomBy(-0.1, -0.1); // If zooming out else if (e.Delta < 0) XAxis.ZoomBy( 0.1, 0.1); // Now don't do anything else e.Handled = true; return; } else if (YAxisBounds.Contains(e.MousePoint)) { if (e.Delta > 0) YAxis.ZoomBy(-0.1, -0.1); else if (e.Delta < 0) YAxis.ZoomBy( 0.1, 0.1); e.Handled = true; return; } // Otherwise, operate normally base.OnModifierMouseWheel(e); }
Your initial points and several key methods helped out a lot! I also simplified some parts I didn’t need. Thanks!
- boss1000 answered 11 years ago
I am considering applying server-side licensing for my javerScript application.
In the document below, there is a phrase “Our server-side licensing component is written in C++.”
(https://support-dev.scichart.com/index.php?/Knowledgebase/Article/View/17256/42/)
However, there is only asp.net sample code on the provided github.
(https://github.com/ABTSoftware/SciChart.JS.Examples/tree/master/Sandbox/demo-dotnet-server-licensing)
I wonder if there is a sample code implemented in C++ for server-side licensing.
Can you provide c++ sample code?
Also, are there any examples to run on Ubuntu?
- hhd2002 answered 11 years ago
Hi Egbert,
We have added the ability to show Min Max ticks on an axis, as well as many other axis enhancements such as the TickProvider API in v3.0 of SciChart. Please see this related post on CategoryDateTimeAxis Grid Lines.
Using the TickProvider API
In SciChart v3.0 and above, there is now a way to override the exact Tick Intervals.
See this KB article on Advanced Tick Frequency Overriding which introduces the TickProvider API. This allows exact calculation of ticks (Axis Major, Minor gridlines and labels) as the axis is drawn.
Best regards,
Andrew
- Andrew Burnett-Thompson answered 11 years ago
Update: June 2014
We have gone through all the tutorials on Screenshots, Printing to XPS, Export to Bitmap and updated them. Please see the updated links below below.
- Export chart from screen to bitmap
- Exporting chart with legend to bitmap
- Rendering chart to bitmap in memory
- Screenshots, XPS Printing, X-Axis Text Labels
The Screenshots, XPS Printing, X-Axis Text Labels article now has an updated sample which compiles against SciChart v3.0 so you can download it and get started.
Hope this helps!
Andrew
- Andrew Burnett-Thompson answered 11 years ago
Hi,
Seems like you are trying to take a screenshot without rendering just like me. I have asked a question about it here: https://www.scichart.com/questions/question/taking-a-screenshot-of-the-graph-after-launch-does-not-display-the-data-points
you can download the sample project and see how you can do this. You need to give it a size, measure and arrange the chart and call UpdateLayout.
hope this helps.
kewur
- kewur answered 10 years ago
Hello there,
The Annotations feature in v1.5 uses UIElements. These are very flexible (as they are WPF elements) but are also very slow compared to the bitmap rendering that we employ in SciChart.
We did have a user once who added 50,000 annotations to a WPF SciChart. It did take a few seconds to redraw but it did perform. We got his performance down from minutes (like you) to a few seconds by using the following code:
using (sciChartSurface.SuspendUpdates())
{
// Create temporary AnnotationCollection
var myAnnotations = new AnnotationCollection();
double minx = double.MaxValue;
double maxx = double.MinValue;
foreach (var c in cmdList)
{
if (c.X < minx) minx = c.X;
if (c.X > maxx) maxx = c.X;
var a = new BoxAnnotation
{
// Assign X1,Y1, Background etc...
};
myAnnotations.Add(a);
}
// Reassign annotation collection
sciChartSurface.Annotations = myAnnotations;
sciChartSurface.ZoomExtents();
}
Assigning the annotation collection in one go will prevent internal updates/redraws occurring on each add. Please try this and let me know if it helps.
Thanks!
Andrew
- Andrew Burnett-Thompson answered 12 years ago
- last active 1 month ago
Hello lovely SciChart people,
An update for you. SciChart v3.0 now supports templating of Axis Titles natively. Please see the SciChart v3.0 Release Notes.
5. Axis Styling improvements
- Chart Titles, Axis Titles and Labels can now be styled
- Can set font size, font style, font weight on Axis labels
- Now supports optional rotation of axis tick labels (e.g. 90 degrees)
Usage:
<!-- Styling Tick Labels with rotation and custom font-size and foreground -->
<s:DateTimeAxis>
<s:DateTimeAxis.TickLabelStyle>
<Style TargetType="s:DefaultTickLabel">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontSize" Value="25" />
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="45" />
</Setter.Value>
</Setter>
</Style>
</s:DateTimeAxis.TickLabelStyle>
</s:DateTimeAxis>
</pre>
<!-- Styling Numeric Axis Tick Labels -->
<s:NumericAxis>
<s:NumericAxis.TickLabelStyle>
<Style TargetType="s:NumericTickLabel">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontSize" Value="25" />
</Style>
</s:NumericAxis.TickLabelStyle>
</s:NumericAxis>
<!-- Declare an Axis Title Template -->
<Style x:Key="BottomAxisTitleStyle" TargetType="s:AxisTitle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock Margin="0,3,0,0"
Style="{StaticResource TitleTextStyle}"
Text="Showing time on"/>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding}" />
<TextBlock Margin="0,0,0,3"
Style="{StaticResource TitleTextStyle}"
Text="(daily Timeframe)"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Apply an Axis Title Template -->
<s:DateTimeAxis AxisTitle="Time Axis Title" TitleStyle="{StaticResource BottomAxisTitleStyle}" Style="{StaticResource AxisStyle}"/>
<!-- Apply an Axis Title Template -->
<s:DateTimeAxis AxisTitle="Time Axis Title" TitleStyle="{StaticResource BottomAxisTitleStyle}" Style="{StaticResource AxisStyle}"/>
Also the XAML Styling application demonstrates this.
Best regards,
Andrew
- Andrew Burnett-Thompson answered 11 years ago
- last active 10 years ago
Hello there,
Please find a short example attached which re-creates this chart Updated for SciChart v3.1
- The Histogram is created by using FastColumnRenderableSeries and DataPointWidth=1.0
- The labels are created by using the TextAnnotation)
- The legend is created by using the LegendModifier (as source) and SciChartLegend as consumer
Best regards,
Andrew
- Andrew Burnett-Thompson answered 12 years ago
- last active 11 years ago
Hi Miles,
This is because all mouse clicks are relative to the parent SciChartsurface. If you want to get the coordinates relative to the ModifierSurface itself, you have to perform a simple translation operation.
public override void OnModifierMouseMove(ModifierMouseArgs e) { // gets the bounds of the ModifierSurface with respect to the RootGrid // which hosts the SciChartSurface var modifierRect = ModifierSurface.GetBoundsRelativeTo(RootGrid); // Translates the mouse point (from root grid coords) to ModifierSurface coords var mousePoint = base.GetPointRelativeTo(e.MousePoint, ModifierSurface); }
Can you try the above translation and let me know if it works?
- Andrew Burnett-Thompson answered 12 years ago
Hi Joerg,
Sounds like what you need is annotations! We have a rich annotations API as of SciChart v1.5, you can see this article which demonstrates how to use it:
Also there is an MVVM sample that does the same thing in the demo app here:
Drag Horizontal Threshold Example
Now if you were to set up something like this:
- A SciChartSurface with two HorizontalLineAnnotations and two VerticalLineAnnotations
- A ViewModel with X1, X2, Y1, Y2 properties, type double. All properties should implement PropertyChanged
- Bind each HorizontalLineAnnotation.X1 to each X property in the ViewModel
- Bind each VerticalLineAnnotation.Y1 to each Y property in the ViewModel
When the user drags the mouse on an annotation you will be notified in the setter of the property in your view model. Likewise you can set the view model properties and the annotation positions will update.
Let me know if that helps,
- Andrew Burnett-Thompson answered 12 years ago
Hello there,
As of SciChart v2.0 this is now possible. SciChart supports unlimited X or Y Axes on the left, top, right, bottom of the chart. You can use this feature to place XAxis on the Left and YAxis on the Top as per our WPF Chart Multiple XAxis.
You might also want to check out our tutorials on multiple axis in a WPF chart and the same tutorial for multiple X,Y axis in MVVM
Best regards,
Yuriy
- Yuriy Zadereckii answered 12 years ago
- last active 1 month ago
Hey guys,
We actually have a reproduction of this bug that occurs on some computers – without using SciChart.dll. It appears to be an issue with some Intel Graphics drivers and WriteableBitmap / .NET4/4.5.
Please download the attached solution and run it on computers where you see problems. Click on the main form. If all is working, you should see a coloured square drawn, which changes colour on each click. However, in systems with this problem, the form will be blank.
What we believe is happening is the WriteableBitmap once locked/unlocked once is not allowing any further updates. The same code works perfectly well on many other machines and the only other workaround is to disable hardware rendering for the WPF process on systems that exhibit this problem.
We have contacted Microsoft and Intel to see if any will take ownership of the fix. In the meantime, as a workaround you will have to disable WPF hardware rendering for the process.
Best regards,
Andrew
- Andrew Burnett-Thompson answered 11 years ago
UPDATE JUNE 2014
Hi Everyone,
Axis Label styling was introduced in SciChart v3.0. You can now set a Style for a label including margins, LayoutTransforms etc…
Please see our SciChart v3.0 Release Notes.
5. Axis Styling improvements
- Chart Titles, Axis Titles and Labels can now be styled
- Can set font size, font style, font weight on Axis labels
- Now supports optional rotation of axis tick labels (e.g. 90 degrees)
Usage:
<!-- Styling Tick Labels with rotation and custom font-size and foreground -->
<s:DateTimeAxis>
<s:DateTimeAxis.TickLabelStyle>
<Style TargetType="s:DefaultTickLabel">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontSize" Value="25" />
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="45" />
</Setter.Value>
</Setter>
</Style>
</s:DateTimeAxis.TickLabelStyle>
</s:DateTimeAxis>
<!-- Styling Numeric Axis Tick Labels -->
<s:NumericAxis>
<s:NumericAxis.TickLabelStyle>
<Style TargetType="s:NumericTickLabel">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontSize" Value="25" />
</Style>
</s:NumericAxis.TickLabelStyle>
</s:NumericAxis>
</pre>
<!-- Declare an Axis Title Template -->
<Style x:Key="BottomAxisTitleStyle" TargetType="s:AxisTitle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock Margin="0,3,0,0"
Style="{StaticResource TitleTextStyle}"
Text="Showing time on"/>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding}" />
<TextBlock Margin="0,0,0,3"
Style="{StaticResource TitleTextStyle}"
Text="(daily Timeframe)"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Apply an Axis Title Template -->
<s:DateTimeAxis AxisTitle="Time Axis Title" TitleStyle="{StaticResource BottomAxisTitleStyle}" Style="{StaticResource AxisStyle}"/>
Also the XAML Styling application demonstrates this.
Best regards,
Andrew
- Andrew Burnett-Thompson answered 11 years ago
- last active 9 years ago
OK, for the archives, in case someone doesn’t have enough reading to do:
So this was not the simplest of issues to solve, though perhaps I made it more complex than it needed to be. The basic requirement was that I had to find a way to get arbitrary metadata into a tooltip based on the results invoked by a viewmodel method, when that metadata had no
I ended up creating a SortedList in which I store my series data (I have a lot of pre-processing to do). DataPoint, and one of its properties, DataPointMetadata, are both structs.
Now, RolloverModifer generates the SeriesInfo DTO, which contains the DataSeriesIndex property — as far as I can tell, the index of the datapoint in SciChart’s underlying collection. This, along with the SeriesName, allow me to uniquely identify a datapoint in very quick time.
The remaining challenge is find a way to look up the value using the viewmodel from inside RolloverModifier.ToolTipLabelTemplate. One option was to use MultiBinding, but I’m not sure that would have worked. I instead decided to use a BindingProxy technique and pass the viewmodel as a staticresource to the converter which is responsible for realizing the data.
<scichart:RolloverModifier x:Name="rollover" DrawVerticalLine="False" ShowAxisLabels="False" SourceMode="AllVisibleSeries" ShowTooltipOn="MouseRightButtonDown" > <scichart:RolloverModifier.TooltipLabelTemplate> <ControlTemplate> <Border x:Name="bdToolTip" BorderThickness="1.25" Background="#341B0B" Opacity="0.85" > <Border.Resources> <Style TargetType="{x:Type TextBlock}"> <Setter Property="FontSize" Value="12" /> <Setter Property="Margin" Value="3" /> </Style> </Border.Resources> <Border.BorderBrush> <StaticResource ResourceKey="bControlAltBackground"/> </Border.BorderBrush> <Grid > <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.ColumnSpan="2" Text="{Binding SeriesName}" /> <TextBlock Grid.Row="1" Grid.Column="0" Text="X: " /> <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding XValue, Converter={StaticResource convIComparable}, StringFormat=\{0:N3\}}" /> <TextBlock Grid.Row="2" Grid.Column="0" Text="Y: " /> <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding YValue, Converter={StaticResource convIComparable}, StringFormat=\{0:N3\}}" /> <!-- A UC that shows the specially-retrieved metadata --> <uc:DataPointMetadata Grid.Row="3" Grid.ColumnSpan="2" DataContext="{Binding Converter={StaticResource convSeriesInfoToMetadata}, ConverterParameter={StaticResource proxy}, Mode=OneWay}" /> </Grid> </Border> </ControlTemplate> </scichart:RolloverModifier.TooltipLabelTemplate> </scichart:RolloverModifier>
/// <summary> /// Converts a SeriesInfo Object to a <c>DataPointMetadata</c> struct /// by invoking a method on the viewmodel. One way only. /// </summary> public class SeriesInfoToMetadataConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { //cast to viewmodel from proxy var vmodel = (vm.SciChartPlotViewModel)((lib.BindingProxy)parameter).Data; //cast to SeriesInfo from value var si = (Abt.Controls.SciChart.SeriesInfo)value; //retrieve metadata using viewmodel; viewmodel will query our own series builder facade to retrive the correct //data point object, which will contain the metadata Pellion.Notebook.Plotting.DataPoint dp = vmodel.GetDataPoint(si.SeriesName, si.DataSeriesIndex); return dp.Metadata; //that was easy! } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
- dsantimore answered 11 years ago
- last active 11 years ago
Hi,
Please, try translating the mouse point relative to ModifierSurface before using it. Refer to the following code:
_startPoint = GetPointRelativeTo(e.MousePoint, ModifierSurface);
Thing are done in this way in modifiers code. Also, maybe you need to play around with the ReceiveHandledEvents property (if your modifiers are connected together via MouseEventGroup).
Regarding the second question, we don’t provide such an event, but there is the SciChartSurface.Rendered event, which will be fired after each redraw (initiated either by pan, or zoom, or resize operation). Alternatively, you can subscribe to the AxisBase.VisibleRangeChanged event or extend modifiers classes and introduce your own events for them. So in this case you need to subscribe to the SizeChanged event.
Hope this helps,
Best regards,
Yuriy
- Yuriy Zadereckii answered 11 years ago