SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, iOS Chart, Android Chart and JavaScript Chart Components

1 vote

Hello Stephan,

Thanks for your query! This is possible by using an attached property called the MouseManager. This is used in the SciTrader demo on the examples suite.

In the code-behind of SciTraderView.xaml you will see something like this:

<s:SciChartSurface x:Name="chart0">
   ... 
   <s:SciChartSurface.ChartModifier>
       <s:RolloverModifier 
           s:MouseManager.MouseEventGroup="MySharedMouseGroup"/>
   </s:SciChartSurface.ChartModifier>
</s:SciChartSurface>

<s:SciChartSurface x:Name="chart1">
   ... 
   <s:SciChartSurface.ChartModifier>
       <s:RolloverModifier 
           s:MouseManager.MouseEventGroup="MySharedMouseGroup"/>
   </s:SciChartSurface.ChartModifier>
</s:SciChartSurface>

The MouseEventGroup attached property will synchronize mouse events across charts. If one chart raises a mouse move event, the other will get it as a secondary event. This way rollovers can be synchronized across charts.

Can you give that a go and let me know if it works?

1 vote

Hi Marcel,

We’ve had to make some minor changes to the code to support X-Values in RolloverModifier. We have uploaded a hotfix to http://http://www.scichart.com/downloads/

You can find release notes with changes at http://http://www.abtsoftware.co.uk/Downloads/Release/releasenotes_v1.3.0.1040.txt

Finally, please find attached a zip file example which demonstrates Rollover X,Y values. Existing code with the RolloverModifier will continue to work.

The binding to the new XValue, YValue properties on SeriesInfo is performed as follows:

<TextBlock Grid.Column="0"
            Margin="3,3,20,3"
            FontSize="13"
            FontWeight="Bold"
            Foreground="{Binding SeriesColor, Converter={StaticResource ColorToBrushConverter}}"
            Text="{Binding SeriesName}" />
<TextBlock Grid.Column="1"
            Margin="3,3,3,3"
            FontSize="13"
            FontWeight="Bold"
            Foreground="{Binding SeriesColor, Converter={StaticResource ColorToBrushConverter}}">
  <Run Text="X: "/>
  <Run Text="{Binding XValue, Converter={StaticResource ComparableToDoubleConverter}}"/>
  <Run Text=", Y: "/>
  <Run Text="{Binding YValue, Converter={StaticResource ComparableToDoubleConverter}}"/>
</TextBlock>

Where the converter is used to work around a bug (feature?!) in WPF, converting IComparable values to double as follows:

public enum ConvertTo
{
    Double, 
    DateTime
}

public class IComparableConverter : IValueConverter
{
    public ConvertTo ConvertTo { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return ConvertTo == ConvertTo.Double ? 
            (object)System.Convert.ToDouble(value) :
            (object)System.Convert.ToDateTime(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

I hope this helps. Get back to me if you have any issues running the attached sample.

1 vote

Hi Straliciuc, thanks for the positive feedback!

Are you able to share a screenshot of what you have now, and what you are trying to achieve? I don’t quite understand what you need.

There are a few things that help with alignment of YAxes – if you have multiple charts (vertical) and want YAxis widths to be the same width, there is an attached property to synchronize the widths of the axes:

<!-- Using VerticalChartGroup, chart0 and chart1 -->
<!-- YAxis widths will be synchronized -->
<s:SciChartSurface x:Name="chart0" 
       s:SciChartGroup.VerticalChartGroup="chartGroup">
    <s:SciChartSurface.YAxis>
        <s:NumericAxis/>
    </s:SciChartSurface.YAxis>
</s:SciChartSurface>

<s:SciChartSurface x:Name="chart1" 
       s:SciChartGroup.VerticalChartGroup="chartGroup">
    <s:SciChartSurface.YAxis>
        <s:NumericAxis/>
    </s:SciChartSurface.YAxis>
</s:SciChartSurface>

If however you have a chart with two YAxes and want the zero lines to be the same, here you would have to set the VisibleRange manually.

Another trick which we use with mutliple YAxis charts is to disable the GridLines of secondary Y-Axis.

e.g.

<!-- When you have two or more Y-Axes -->
<!-- hide the grid lines of subsequent axes -->
<s:SciChartSurface x:Name="chart0" 
       s:SciChartGroup.VerticalChartGroup="chartGroup">
    <s:SciChartSurface.YAxes>
        <s:NumericAxis />
        <s:NumericAxis Id="SecondaryAxis" 
                       DrawMajorGridLines="False" 
                       DrawMinorGridLines="False"/>
    </s:SciChartSurface.YAxes>
</s:SciChartSurface>

Please let me know if this is helpful. If not – any information you can give us to help solve the problem would be appreciated!

0 votes

Thanks for the images – that really clarifies the matter.

Ok by default SciChart tries to zoom to fit the first time you add data to the chart. After this you can change the VisibleRange manually on the Y-Axis.

In the above case you need to set each YAxis to have equal positive, negative visible ranges.

For instance:

// Sinewave visible range
yAxis1.VisibleRange = new DoubleRange(-1.0, 1.0)

// Exponential visible range
yAxis2.VisibleRange = new DoubleRange(-20000, 20000)

By setting the visible range min equal to -max it will ensure the zero line is always in the centre of the chart. Can you try this and let me know how you get on?

Some other properties on the Axis which you may be interested in:

  • AxisBase.AutoTicks: set to false to manually set major, minor delta
  • AxisBase.MaxAutoTicks: constrains number of ticks generated with AutoTicks=true
  • AxisBase.MajorDelta: Major delta spacing
  • AxisBase.MinorDelta: Minor delta spacing
0 votes

I think the only way to do this is to create an algorithm which calculates the desired Minimum, Maximum range on each axis so that the zero crossings are at the same location. The naive approach is as I said, equal and opposite Min/Max values on both axes.

A more complex approach would calculate min, max values where the ratio between positive and negative halves is the same for both axes.

So unknown algorithm taking inputs:

  • Viewport Height (giving axis height)
  • Series Min/Max on Axis 1
  • Series Min/Max on Axis 2

and yielding outputs:

  • VisibleRange Min/Max on Axis 1
  • VisibleRange Min/Max on Axis 2

If you can think of an algorithm to perform this calculation, I can assist to write the code to set the visible ranges on appending data 🙂

1 vote

Hello Alex, thanks for your post!

As promised here are some answers to your questions. If you have any further queries feel free to ask. We’re here to help 🙂

  1. The RolloverModifier will require that a RolloverMarkerTemplate is set on the FastLineRenderableSeries or it will not work. Yes you can declare this in code-behind, however its of type ControlTemplate. A workaround might be to create the control template in Xaml and index it as follows:

Xaml

<ControlTemplate x:Key="MyControlTemplate">
    <Ellipse Width="7" Height="7" 
          Fill="#33FFFFFF" Stroke="#FFFF55555"
          StrokeThickness="1"/>
</ControlTemplate>

C#

   var controlTemplate = Resources["MyControlTemplate"] as ControlTemplate;
   myRenderableSeries.RolloverMarkerTemplate = controlTemplate;

An alternative is create a style in Xaml for the type FastLineRenderableSeries without an x:Key attribute,
setting the RolloverMarker property here.

  1. Yes, you can set left/right axis side using sciChartSurface.YAxis.AxisAlignment = AxisAlignment.Left. Multiple axes are also possible on the left and right sides. See our demo “Secondary Y-Axis” and “Quad Left/Right Axis” at http://www.scichart.com/demo for source code examples

  2. Yes, you can subscribe to SciChartSurface.Rendered, which occurs immediately after a render, where you can get the sciChartSurface.YAxis.VisibleRange and sciChartSurface.XAxis.VisibleRange

  3. Yes, we have a tutorial on custom markers & annotations here: http://http://www.scichart.com/annotations-adding-trade-markers-to-a-chart/

Any UIElement annotation may be added to the chart and in future we are working to improve this functionality to provide a full suite of interactive annotations.

  1. Yes, please see the example “Modify Axis Behavior” in our demo app at http://http://www.scichart.com/demo . The property you are looking for is TextFormatting on the axis
1 vote

UPDATE: Using Annotations feature (v1.5 and greater).

Using the Annotations Feature a zero line can be achieved with the following XAML on a chart:

<s:SciChartSurface.Annotations>
                
    <!-- Draws Bands behind an axis. Assumes there is a YAxis with ElementName myAxis -->
    <s:BoxAnnotation CoordinateMode="RelativeX" X1="0" X2="1" Y1="{Binding VisibleRange.Min, ElementName=myAxis}" Y2="{Binding VisibleRange.Max, ElementName= myAxis}" Background="#11000000" AnnotationCanvas="BelowChart"/>

    <!-- Draws the zero lines for an axis. Assumes AxisId of the YAxis is myAxisId -->
    <s:HorizontalLineAnnotation Y1="0" YAxisId="myAxisId" HorizontalAlignment="Stretch" StrokeThickness="1" AnnotationCanvas="BelowChart"/>
</s:SciChartSurface.Annotations>

This technique is demonstrated in the Vertically Stacked Axis example.

0 votes

Hello Alex,

You’re right – I just looked at the RolloverModifier code and it assigns a new (empty) ObservableCollection before filling it with values.

May I ask, why do you need a notification of rollover? There may be another way to achieve what you want.

It’s a bit messy but the following code should get you a notifcation of new Rollover data

this.rolloverModifier.RolloverData.PropertyChanged += (s, e) =>
{
    // Rollover data was changed
    if (e.PropertyName == "SeriesInfo")
    {                
        this.rolloverModifier.RolloverData.CollectionChanged += (c, args) => 
        {
             // SerisInfo was added to the collection
             // NOTE: Be careful to unsubscribe to events as this handler will keep the
             // ObservableCollection alive on GC
        }
    }
};

Another way might be to handle the MouseMove events and RenderedEvent on SciChartSurface itself and testing for new RolloverData here. For instance:

this.sciChartSurface.MouseMove += (s, e) => OnMouseMoveOrRendered();
this.sciChartSurface.Rendered += (s, e) => OnMouseMoveOrRendered();

private void OnMouseMoveOrRendered()
{
    // Test for rollover data here, which is updated on mouse move
    // and on render of new data
    if (this.rolloverModifier.RolloverData.SeriesInfo != null)
    {
        // ... 
    }
}
0 votes

Hi there,

As of SciChart version 4 we now have a Scrollbar feature which allows you to zoom, pan, slide and scroll each axis individually.

You can learn more about the SciChart WPF Scrollbars feature here.

WPF Chart Per-Axis Scrollbars

Best regards
Andrew

4 votes
In reply to: Documentation

Hello Catalin,

Yes there is. All of our documentation is now online. Please see this related question: Where is the Documentation.

We also have a demo on Xaml styling in the examples suite, Please see our WPF Chart Xaml Styling Example

0 votes

Hi Marcel,

I took a quick look at the mountain series – adding a digital line would take a day or two to make this modification and regression test everything. However we can add a property to the column series called DefaultPointWidth (0.0-1.0) which lets you define the percentage of space taken with a much quicker turnaround.

This works quite well (see attached). We’ll have to test and release a patch but shouldn’t take too long. There are some other outstanding BAU bugs which need to go in as well. I’ll inform you when we have something for you.

The property is DefaultPointWidth on FastColumnRenderableSeries. Set this between 0.0 – 1.0 (where 1.0 is full width) to expand the size of columns. Also columns now render centred on the data-point.

0 votes

Hi there,

Thanks for posting such a clear question & example! 🙂 I know why this doesn’t work. The ControlTemplate internally is rendered to a bitmap and stored on the RenderableSeries. Later it is blitted directly to the screen, i.e. the UIElement created by the ControlTemplate is never in the Visual Tree.

The only way I can think of to do this, is to create the control template in code via a helper class. See this article on creating control templates programmatically.

For instance, if you had a class, call it PointMarkerHelper, it might have a method with the following signature

public PointMarkerHelper
{
    public ControlTemplate CreateMarker(Color fill, Color? stroke, Shape shape)
    {
        // TODO
    }
}

Then you could create a point marker with minimum fuss programmatically.

Here, I’ve attached a sample to this post. Also a screenshot of the result. Take a look and let me know if it helps!

1 vote

Your code looks correct, you need to bind to AxisCollection not ObservableCollection, SciChart expects this type. Of course, you’ll need to initialize the AxisCollection to an empty collection and you should then be able to add/remove axes freely.

So you need to do this

Xaml:

<SciChart:SciChartSurface x:Name="historicalChart" 
    RenderableSeries="{Binding HistoricalRenderableSeries, Mode=TwoWay}"
    YAxes="{Binding ChartYAxes, Mode=TwoWay}"
    SciChart:ThemeManager.Theme="ExpressionLight">
   ...
</s:SciChartSurface>

ViewModel

I tried both an ObservableCollection and a AxisCollection in my view model.

private AxisCollection _chartYAxes = new AxisCollection();
public AxisCollection ChartYAxes
{
  get { return _chartYAxes; }
  set
  {
    _chartYAxes = value;
    NotifyPropertyChanged("ChartYAxes");
  }
}

Then, you can add or remove Axis to the AxisCollection dynamically. Don’t forget when dynamically adding or removing Axes:

  1. To set the AxisBase.Id to a new, unique string. The first axis has by default “DefaultAxisId”. Subsequent axis need another user-provided ID.
  2. To ensure that the RenderableSeries you are using have YAxisId, XAxisId set (unless default). If not, they will not render.

Let me know if this solves the problem!

0 votes

Hi there,

You are right, the FastLineRenderableSeries is not in the visual tree, as these are rendered using bitmaps only. If you want to create a context menu that shows only when right-clicking on a chart, I suggest you handle the SciChartSurface MouseRightButtonUp event and decide whether to show a context menu based on a hit-test result.

You can find a demonstration of the hit-test API here:

http://http://www.scichart.com/Abt.Controls.SciChart.SL.ExampleTestPage.html#/Abt.Controls.SciChart.Example;component/Examples/IWantTo/InspectDatapoints/HitTestDatapoints.xaml

and code to enact a hit test here:

private void SciChartSurfaceMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
    // Hit tests are done per renderable series, so get a renderable series
    // or iterate over all RenderableSeries
    var renderableSeries = this.sciChartSurface.RenderableSeries[0];

    // Perform the hit test relative to the GridLinesPanel
    Point mousePoint = e.GetPosition(sciChartSurface.GridLinesPanel as UIElement);
    var hitTest = renderableSeries.HitTest(mousePoint);

    // Output results 
    string formattedString =
        string.Format("Mouse Coord: {0:0}, {1:0}\t\tNearest Datapoint Coord: {2}, {3}\tData Value: {4}, {5}\t\tIsHit? {6}",
                      mousePoint.X, mousePoint.Y,
                      hitTest.HitTestPoint.X, hitTest.HitTestPoint.Y,
                      hitTest.XValue, hitTest.YValue,
                      hitTest.IsHit);

    Debug.WriteLine(formattedString);
}  

Hope this helps!

0 votes

In SciChart v2.0 the chart checks if any of the axes has invalid range (either null, or infinite) and if so, it autoranges that axis once, on startup.

In SciChart v3.0 this problem goes away, as there is a default VisibleRange applied to each axis, and if overridden by a user (e.g. setting your own VisibleRange), then the auto ranging once on startup is ignored.

There is a KB article here on the topic:

AutoRange.Never vs AutoRange.Auto vs AutoRange.Always

Best regards,
Andrew

1 vote

Some properties and limitations of the MouseEventGroup:

  • MouseManager.MouseEventGroup is designed to be set on a ModifierGroup and ensures that all child modifiers in that group publish their mouse events to other ModifierGroups that are listening
  • This means that if your ModifierGroup contains XAxisDragModifiers, YAxisDragModifiers, Cursors, Rollovers, if any of these publish an event the corresponding type of modifier will also get the event on the other group
  • It’s not currently possible to link a subset of modifiers, or an individual modifier with another

It sounds like what you’re trying to do is separate the other chart modifiers but link rollovers on multiple charts. Is this correct? Do you also want to link visibleRange (i.e. xaxis range) on multiple charts?

If so then take a two-fold approach:

Linking VisibleRange

This can be done in MVVM by binding two charts to a common property on a parent ViewModel. E.g.

<!-- Chart 1 bound to ChildViewModel1 --> 
<s:SciChartSurface>
  <s:SciChartSurface.XAxis>
     <s:NumericAxis VisibleRange="{Binding ParentViewModel.XVisibleRange, Mode=TwoWay}"/>
  </s:SciChartSurface.XAxis>
</s:SciChartSurface>

<!-- Chart 2 bound to ChildViewModel2 -->
<s:SciChartSurface>
  <s:SciChartSurface.XAxis>
     <s:NumericAxis VisibleRange="{Binding ParentViewModel.XVisibleRange, Mode=TwoWay}"/>
  </s:SciChartSurface.XAxis>
</s:SciChartSurface>

Linking Rollovers on Two Charts

Now with the above you could create a type derived from RolloverModifier and perform the link manually. Previously I posted a class called MasterRolloverModifier here: http://http://www.scichart.com/questions/question/share-rollover-between-two-charts/

public class MasterRolloverModifier : RolloverModifier
{
    private IEnumerable<RolloverModifier> childModifiers;
    public MasterRolloverModifier()
    {            
    }

    public void RegisterChildModifiers(params RolloverModifier[] childModifiers)
    {
        this.childModifiers = childModifiers;
    }

    // Forward on the mouse move event to child modifiers
    public override void OnModifierMouseMove(ModifierMouseArgs e)
    {
        base.OnModifierMouseMove(e);

        if (e.IsMaster)
        {
            foreach (var child in this.childModifiers)
            {
                e.IsMaster = false;
                child.OnModifierMouseMove(e);
            }
        }
    }
}

If you attach one of these to all your charts (Dropping the MouseEventGroup) and in a parent viewmodel register rollover A with B (and vice versa) it will work.

Can you try this and let me know how you get on? If this does not solve the problem and you are stuck, please send us an email with any code (or a solution) to reproduce the issue. We can turn around fixes much more quickly when we can see the code.

0 votes
In reply to: Format DateTimeAxis

DateTimeAxis (and NumericAxis) has a TextFormatting property. The property is used in code like this:

        public override string FormatText(IComparable value, string format)
        {
            string textFormat = format.IsNullOrEmpty() ? TextFormatting : format;

            return ((DateTime)value).ToString(textFormat);
        }

You can supply any format that .NET accepts, for instance:

  • dd MMM yyyy: formats day, Month, year
  • HH:mm:ss: formats Hours:Minutes:seconds
  • dd MMM yyyy HH:mm:ss.fff formats date, time and milliseconds

Beyond that you can provide a LabelProvider to an axis for fine-grained control over the label output.

There are detailed KB articles on Label Formatting, Text Formatting and Cursor Formatting here:

Axis Text Formatting, Cursor Text Formatting

Specifying Text Labels with ILabelProvider

Hope this is helpful,

4 votes

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:

Annotations Are Easy!

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,

0 votes

Hi Straliciuc,

This can be resolved by setting a YAxis.GrowBy value as follows:

<s:SciChartSurface x:Name="sciChartSurface" s:ThemeManager.Theme="BrightSpark" >
    <s:SciChartSurface.ChartModifier>
        <s:ModifierGroup>
            <s:LegendModifier x:Name="legendModifier"/>
            <s:MouseWheelZoomModifier />
            <s:ZoomPanModifier  />
        </s:ModifierGroup>
    </s:SciChartSurface.ChartModifier>

    <!-- Defines the renderable series, which map 1:1 to data-series. R-Series may be styled -->
    <s:SciChartSurface.RenderableSeries>
        <s:FastLineRenderableSeries SeriesColor="Red" YAxisId="yAxis1"/>
    </s:SciChartSurface.RenderableSeries>

    <!-- Defines the XAxis -->
    <s:SciChartSurface.XAxis>
        <s:NumericAxis>
        </s:NumericAxis>
    </s:SciChartSurface.XAxis>

    <!-- Defines the YAxis -->
    <s:SciChartSurface.YAxes>
        <s:NumericAxis TickTextBrush="red"  AxisAlignment="Left" Id="yAxis1" >
          <s:NumericAxis.GrowBy>
            <s:DoubleRange Min="0.1" Max="0.1"/>
          </s:NumericAxis.GrowBy>
        </s:NumericAxis>
    </s:SciChartSurface.YAxes>
</s:SciChartSurface>

Note that the calculation to generate the YAxis range is as follows:

  1. Get data Min, Max of the axis
  2. If Min == Max, expand the range using GrowBy. Max = max+GrowBy.Max and Min = Min – GrowBy.Min

UPDATE: This issue was fixed in v2.0 or later of SciChart. It will now automatically expand the range around constant values even without a GrowBy.

Hope this helps!

0 votes

Hi Manish,

As modifiers can perform operations on the axes, its correct behaviour that ChartModifierBase receives an OnModifierMouseDown event when the mouse is clicked on an axis.

Internally, to check whether the mouse point is inside the main chart area (i.e. not on an axis), RubberBandXyZoomModifier has this code:

public override void OnModifierMouseDown(ModifierMouseArgs e)
{
    if (_isDragging || e.MouseButtons != MouseButtons.Left)
        return;

    var modifierSurfaceBounds = ModifierSurface.GetBoundsRelativeTo(RootGrid);
    bool isClickOnChart = modifierSurfaceBounds.Contains(e.MousePoint);

    // Exit if the mouse down was outside the bounds of the ModifierSurface
    if (!isClickOnChart)
        return;

    // ... 
}

It sounds like you are creating a custom modifier. If so please be aware that all mouse points that are sent to the OnModifierMouse* methods are relative to the parent SciChartSurface.RootGrid. If you wanted to get a mouse point relative to the chart area itself, you can use this code:

    // Translate the mouse point (which is in RootGrid coordinates) relative to the ModifierSurface
    // This accounts for any offset due to left Y-Axis
    var ptTrans = RootGrid.TranslatePoint(e.MousePoint, ModifierSurface);
Showing 1 - 20 of 5k results