Track ball with multiple series and multiple axes; matching category and double axes

This problem has several pieces to it.

  1. I have a chart with several series in it. Most are of StackingLine type, but two are of Scatter type.
  2. There are two X axes in the chart. One is ValueType.Category, and one is ValueType.Double.
  3. The X axis of ValueType.Category is a string, displayed as a time: 12:36. It starts out as a number (such as 12.6) that gets converted to a string for display purposes. The x values for these series will almost never be exactly on the hour, and the exact time (down to the minute) is important. However, we don't want to use ValueType.DateTime, as we do not want dates represented, as this is aggregate data. Thus, the range of values 00:00 through 23:59 will always be the range of x values.
  4. The X axis of ValueType.Double is also representative of time. It shows just the hour of the relevant data, as it is summarized prior to display. The series using this axis will only have x values exactly on an hour, so the numbers 0-23 will always be the range of values.
  5. I am using a trackball to display data in a tooltip. This worked well when I had one X axis of type double. However, some of the data is needed to display as actual readable times instead of decimal values (12:36 instead of 12.6, for example), which is why the aforementioned move to ValueType.Category seemed like a good idea.
With this information in mind, I have a couple requirements that I can't seem to figure out how to achieve:
  1. I need the two axes to match up correctly (so, if there is a ValueType.Scatter datapoint that has an X value of 12:36, it should display a little bit further than the middle of the 12 and 13 X values in the other axis).
  2. I need the trackball to actually attach to both axes. Currently, it is only showing data for the ValueType.Scatter series.
I can alter and modify data as I receive it to prepare it for display.
Currently I am receiving 3 lists:
  1. A list with several properties of type decimal? (AverageDemand, AverageBids, AverageBookings, BidRate, BookRate, AverageLors, AverageRevenue, RPD), and one properties of type int (Hour).
  2. A list of decimal (for Scheduled)
  3. A list of decimal (for AdHoc)
The second two lists are converted to strings prior to being given to the chart. I originally attempted to keep these as part of the ValueType.Double axis, but display them as the time string format in the tooltip, but was unable to, as the Format property doesn't seem to allow alterations to the data prior to display.

If there is a way to alter the tooltip in such a way that the data can be converted from int to string using methods I define, that would be the preferred way of handling all of this.

Additionally, I am looking for resources about how to use several different Y axes, in order to have many ranges of data displaying simultaneously. (e.g., data that ranges from 0-100, data that ranges from 1-20, and data that ranges from 0-5000) This would need to be set up such that the smaller values are not flattened by the scaling of the graph.
This is a picture of what the graph looks like now. Note that the trackball latches onto the scatter data point, but it will not latch onto the data points of the line series.

The code for the chart is thus:

<TabItem>

    <ChildContent>

         <TabHeader Text="Performance Grid"></TabHeader>

    </ChildContent>

    <ContentTemplate>

        <div class="mt-lg-4">

            <SfChart Width="100%" Height="100%">

                <ChartEvents OnLegendClick="OnLegendClick"></ChartEvents>

                <ChartPrimaryXAxis ValueType="Syncfusion.Blazor.Charts.ValueType.Double" Minimum="0" Maximum="23" Interval="1">

                <ChartCrosshairSettings Enable="true" LineType="LineType.Vertical"></ChartCrosshairSettings>

                </ChartPrimaryXAxis>

                <ChartPrimaryYAxis ValueType="Syncfusion.Blazor.Charts.ValueType.Double" Interval="@GraphInterval">


                </ChartPrimaryYAxis>

                <ChartAxes>

                    @*<ChartAxis Name="XAxis" ValueType="Syncfusion.Blazor.Charts.ValueType.Double" Interval="@GraphInterval"></ChartAxis>*@

                    <ChartAxis Name="XAxis" ValueType="Syncfusion.Blazor.Charts.ValueType.Category" OpposedPosition="false"/>

                </ChartAxes>

                <ChartCrosshairSettings Enable="true" LineType="LineType.Vertical"></ChartCrosshairSettings>

                <ChartTooltipSettings Enable="true" Shared="true"Format="${series.name} : ${point.x} : ${point.y}"></ChartTooltipSettings>

                <ChartArea>

                    <ChartAreaBorder Width="0"></ChartAreaBorder>

                </ChartArea>

                <ChartSeriesCollection>

                    @*XName="Hour"*@

                    <ChartSeries XName="Hour" DataSource="@performanceGraphData" Visible="@GraphAverageDemandVisible"YName="AverageDemand" Type="ChartSeriesType.StackingLine" Name="Demand" Width="3">

                        <ChartMarker Visible="true"></ChartMarker>

                    </ChartSeries>

                    <ChartSeries XName="Hour" DataSource="@performanceGraphData" Visible="@GraphAverageBidsVisible"YName="AverageBids" Type="ChartSeriesType.StackingLine" Name="Bids" Width="3">

                        <ChartMarker Visible="true"></ChartMarker>

                    </ChartSeries>

                    <ChartSeries XName="Hour" DataSource="@performanceGraphData" Visible="@GraphAverageBookingsVisible" YName="AverageBookings" Type="ChartSeriesType.StackingLine" Name="Bookings" Width="3">

                        <ChartMarker Visible="true"></ChartMarker>

                    </ChartSeries>

                    <ChartSeries XName="Hour" DataSource="@performanceGraphData" Visible="@GraphBidRateVisible" YName="BidRate" Type="ChartSeriesType.StackingLine" Name="Bid Rate" Width="3">

                        <ChartMarker Visible="true"></ChartMarker>

                    </ChartSeries>

                    <ChartSeries XName="Hour" DataSource="@performanceGraphData" Visible="@GraphBookRateVisible" YName="BookRate" Type="ChartSeriesType.StackingLine" Name="Book Rate" Width="3">

                        <ChartMarker Visible="true"></ChartMarker>

                    </ChartSeries>

                    <ChartSeries XName="Hour" DataSource="@performanceGraphData" Visible="@GraphAverageRevenueVisible" YName="AverageRevenue" Type="ChartSeriesType.StackingLine" Name="Revenue" Width="3">

                        <ChartMarker Visible="true"></ChartMarker>

                    </ChartSeries>

                    <ChartSeries XName="Hour" DataSource="@performanceGraphData" Visible="@GraphRPDVisible" YName="RPD" Type="ChartSeriesType.StackingLine" Name="RPD" Width="3">

                        <ChartMarker Visible="true"></ChartMarker>

                    </ChartSeries>

                    @*XAxisName="XAxis"*@

                    <ChartSeries XName="Hour" DataSource="@scheduleStartTimes" Visible="@GraphScheduledVisible" YName="Height" Type="ChartSeriesType.Scatter" Name="Scheduled" XAxisName="XAxis">

                        <ChartMarker Visible="true" Shape="ChartShape.Circle" Height="15" Width="15"></ChartMarker>

                    </ChartSeries>

                    @*XAxisName="XAxis"*@

                    <ChartSeries XName="Hour" DataSource="@adHocStartTimes" Visible="@GraphAdHocVisible" YName="Height" Type="ChartSeriesType.Scatter" Name="AdHoc" XAxisName="XAxis">

                        <ChartMarker Visible="true" Shape="ChartShape.Diamond" Height="15" Width="15"></ChartMarker>

                    </ChartSeries>

                </ChartSeriesCollection>

            </SfChart>

        </div>

    </ContentTemplate>

</TabItem>

As you can see, this is all put inside of a tab element. I have also zipped the relevant code files for easier reading. The code I have added in the zip is not a solution, but it should serve as enough of a reference to facilitate understanding of how the code I have described works.


Attachment: Blazor_Forum_Archive_c56db0d0.zip

3 Replies 1 reply marked as answer

DG Durga Gopalakrishnan Syncfusion Team October 14, 2021 03:35 PM UTC

Hi Adam, 

Greetings from Syncfusion. 

We have analyzed all your queries. Please check with the below suggestions to achieve your required scenario. 

# 1 : the Format property doesn't seem to allow alterations to the data prior to display 

We suggest you to use SharedTooltipRender event to customize the tooltip text. 

public void TooltipRender(SharedTooltipRenderEventArgs args) 
    { 
        List<String> tmpList = args.Text.ToList(); 
    } 

# 2 : how to use several different Y axes, in order to have many ranges of data displaying simultaneously 

You can use ChartAxes property to display multiple x and y axis for chart.  

<ChartAxes> 
       <ChartAxis Name="YAxis1" ValueType="Syncfusion.Blazor.Charts.ValueType.Double" OpposedPosition=true></ChartAxis> 
      <ChartAxis Name="YAxis2" ValueType="Syncfusion.Blazor.Charts.ValueType.Double"></ChartAxis> 
</ChartAxes> 

# 3 : the trackball latches onto the scatter data point, but it will not latch onto the data points of the line series 

We suggest you to use shared tooltip to display all series points in a tooltip. This will display only the nearby points of other series while hovering over the particular point. 

 


Kindly revert us if you have any concerns. 

Regards, 
Durga G 


Marked as answer

AS Adam Schubach replied to Durga Gopalakrishnan October 14, 2021 05:42 PM UTC

Hello Durga,

This worked really well! I really appreciate the prompt response.


I ended up putting using only 1 X axis, and keeping all hour records as decimals.

This is the event handler I used:


public void TooltipRender(SharedTooltipRenderEventArgs args)

{

    List tmpList = args.Text.ToList();

     var adHoc = args.Data.Find(p => p.SeriesName == "AdHoc");

     var innerText = decimal.Parse(args.HeaderText.Substring(3, args.HeaderText.Length - 7)).ToTimeString();

     args.HeaderText = $"{innerText}";

     if (adHoc != null)

     {

          var adHocIx = args.Text.FindIndex(s => s.Contains("AdHoc"));

          var adHocText = $"AdHoc: {innerText}";

          args.Text[adHocIx] = adHocText;

     }

     var sched = args.Data.Find(p => p.SeriesName == "Scheduled");

     if (sched != null)

     {

         var schedIx = args.Text.FindIndex(s => s.Contains("Scheduled"));

         var schedText = $"Scheduled: {innerText}";

         args.Text[schedIx] = schedText;

     }

}

This is a screenshot of how the sample looks afterwards (I also changed it from stacking line to just line):

EDIT: For anyone looking at this, I created a decimal-to-time-formatted-string converter:

public static class DecimalTimeToStringConverter

{

    /// <summary>

    /// Converts a decimal into the form hh:mm. Invalid values return an empty string.

    /// </summary>

    /// <param name="input">The decimal being converted</param>

    /// <returns>A time string in the form hh:mm</returns>

    public static string ToTimeString(this decimal input)

    {

        if (input >= 24)

            return "";

        return $"{((int)input < 10 ? $"0{(int)input}" : (int)input)}:{((int)(input % 1.0m * 60) < 10 ? $"0{(int)(input % 1.0m * 60)}" : (int)(input % 1.0m * 60))}";

    }


    /// <summary>

    /// Converts a decimal into the form hh:mm. Invalid values return an empty string.

    /// </summary>

    /// <param name="input">The decimal being converted</param>

    /// <returns>A time string in the form hh:mm</returns>

    public static string ToTimeString(this decimal? input)

    {

        if (input is null)

            return "";

        if (input >= 24)

            return "";

        return $"{((int)input < 10 ? $"0{(int)input}" : (int)input)}:{((int)(input % 1.0m * 60) < 10 ? $"0{(int)(input % 1.0m * 60)}" : (int)(input % 1.0m * 60))}";

    }


    /// <summary>

    /// Converts a time string in the form hh:mm to a decimal. Invalid values return null.

    /// </summary>

    /// <param name="input">The string being converted</param>

    /// <returns>A decimal representative of the time that was converted</returns>

    public static decimal? FromTimeString(this string input)

    }

        var regex = new Regex(@"^[0-9]{1,2}:[0-9]{2}$");

        if (!regex.IsMatch(input))

        return null;

        return decimal.Parse(input.Substring(0, 2)) + (decimal.Parse(input.Substring(3, 2)) / 60m);

    }

}




DG Durga Gopalakrishnan Syncfusion Team October 15, 2021 03:16 PM UTC

Hi Adam, 

Thanks for an update. Please get back to us if you need any further assistance. We are always happy in assisting you. 

Regards, 
Durga G

Loader.
Up arrow icon