Adding a TextBlock (or label) on the end of a Line

2019-07-29 09:54发布

问题:

I'm creating a User Control componente of type DataVisualization.Chart. Right now, he accepts 4 range limits (1 range minimum of accept value and 1 maximum, also 1 range minimum of warning value and 1 maximum warning range value). So far, so good. It works. But i need to add a TextBlock (or label, or TextBox, whatever) at the end of the LineSeries.

This is my actual LineSeries:

Now i need to make something like this:

Even if the result is not the same, i need to put a label or textblock at the end of every LineDataPoint.

Edit 1:

This is how my list of items is generated:

  /// <summary>
  /// Generate ItemsSource to use with chart component
  /// </summary>
  /// <returns>A observable collection items of ChartItems type</returns>
  public ObservableCollection<ChartItems> GenerateActualValues()
  {
      var itemsSource = ItemsSource as IEnumerable;
       if (itemsSource.IsNull())
          return null;

       // Get all values from ItemsSource to set Axis Y of Chart

       List<Double> listAxisY = new List<Double>();

       ObservableCollection<ChartItems> chartItems = new ObservableCollection<ChartItems>();

        foreach (ChartItems itemSource in itemsSource)
        {
            listAxisY.Add(itemSource.ValueY);

            chartItems.Add(new ChartItems { Name = itemSource.Name, ValueY = itemSource.ValueY, ValueXDouble = itemSource.ValueXDouble, ValueXDateTime = itemSource.ValueXDateTime, Color = itemSource.Color });
        }

        // Set minimum and maximum axis Y if automatic

        if (AutomaticAxisY)
        {
            Double? maxValue;
            Double? minValue;

            if (listAxisY.Count > 0)
            {
                if (GetMaxLimitValue1(this) > listAxisY.Max())
                    maxValue = GetMaxLimitValue1(this);
                else
                    maxValue = listAxisY.Max();

                if (GetMinLimitValue1(this) < listAxisY.Min())
                    minValue = GetMinLimitValue1(this);
                else
                    minValue = listAxisY.Min();
            }
            else
            {
                maxValue = GetMaxLimitValue1(this);
                minValue = GetMinLimitValue1(this);
            }

            Double? increment = (maxValue - minValue) * 0.05;

            MaximumAxisY = (maxValue + increment).ConvertNullDoubleToDouble();
            MinimumAxisY = (minValue - increment).ConvertNullDoubleToDouble();

            if (MaximumAxisY == MinimumAxisY)
            {
                MaximumAxisY += 1;
                MinimumAxisY -= 1;
            }
        }

        return chartItems;
    }

Basically, it generate a new ItemsSource to use in my Chart. Now, to generate those extra lines:

/// <summary>
/// Generate a ItemsSource using param option informed
/// </summary>
/// <param name="option">Min1, Max1, Min2, Max2 or Target Value</param>
/// <returns>Observable Collection of ChartItems</returns>
public ObservableCollection<ChartItems> GenerateLimitValues(String option)
{
    var itemsSource = ItemsSource as IEnumerable;

    if (itemsSource.IsNull())
        return null;

    Double? valueY = 0.0;

    ObservableCollection<ChartItems> chartItems = new ObservableCollection<ChartItems>();

    switch (option)
    {
        case "Min1":
            valueY = GetMinLimitValue1(this);
            break;

        case "Max1":
            valueY = GetMaxLimitValue1(this);
            break;

        case "Target":
            valueY = GetTargetValue(this);
            break;

        case "Min2":
            valueY = GetMinLimitValue2(this);
            break;

        case "Max2":
            valueY = GetMaxLimitValue2(this);
            break;
    }

    foreach (ChartItems itemSource in itemsSource)
        chartItems.Add(new ChartItems { Name = itemSource.Name, ValueY = valueY.ConvertNullDoubleToDouble(), ValueXDouble = itemSource.ValueXDouble, ValueXDateTime = itemSource.ValueXDateTime });

    return chartItems;
}

Now, this is where all the things work in my Chart:

/// <summary>
/// Generates series with values for chart
/// </summary>
public void RenderizeChart()
{
    while (this.chartView.Series.Count() - 1 >= 0)
        this.chartView.Series.Remove(this.chartView.Series[0]);

    DataPointSeries lineSeriesActualValue = null;
    DataPointSeries lineSeriesMaxValue1 = null;
    DataPointSeries lineSeriesMinValue1 = null;
    DataPointSeries lineSeriesMaxValue2 = null;
    DataPointSeries lineSeriesMinValue2 = null;
    DataPointSeries lineSeriesTarget = null;

    if (!ChartTypeSelectedItem.IsNull())

        switch ((ChartTypes)ChartTypeSelectedItem)
        {
            case ChartTypes.Bar:

                this.chartView.Series.Add(new BarSeries());
                lineSeriesActualValue = this.chartView.Series[0] as BarSeries;

                break;

            case ChartTypes.Columns:

                this.chartView.Series.Add(new ColumnSeries());
                lineSeriesActualValue = this.chartView.Series[0] as ColumnSeries;
                lineSeriesActualValue.DataPointStyle = (Style)this.Resources["ColumnDataPointStyle"];

                break;

            case ChartTypes.Pie:

                this.chartView.Series.Add(new PieSeries());
                lineSeriesActualValue = this.chartView.Series[0] as PieSeries;

                break;

            case ChartTypes.Lines:

                this.chartView.Series.Add(new LineSeries());
                lineSeriesActualValue = this.chartView.Series[0] as LineSeries;
                lineSeriesActualValue.Style = (Style)this.Resources["LineSeriesStyle"];

                if (!ShowPoints)
                {
                    // Brief explanation: if user wants to hide Data Points, it's necessary to get all Setters on 
                    // LineDataPointStyle inside xaml, clear previous style and add new Setter. 
                    // Otherwise, it will not work, will deny changes because its sealed.

                    Style style = (Style)this.Resources["LineDataPointStyle"];

                    List<Setter> setterList = new List<Setter>();

                    foreach (Setter setter in style.Setters)
                        setterList.Add(setter);

                    style = new Style();

                    foreach (var setter in setterList)
                        style.Setters.Add(setter);

                    style.Setters.Add(new Setter(LineSeries.TemplateProperty, null));

                    lineSeriesActualValue.DataPointStyle = style;
                }
                else

                    lineSeriesActualValue.DataPointStyle = (Style)this.Resources["LineDataPointStyle"];

                break;

            case ChartTypes.Area:

                this.chartView.Series.Add(new AreaSeries());
                lineSeriesActualValue = this.chartView.Series[0] as AreaSeries;

                break;

            default:

                break;
        }

    if (!lineSeriesActualValue.IsNull())
    {
        lineSeriesActualValue.IsSelectionEnabled = true;

        lineSeriesActualValue.DependentValuePath = FieldForDependentValue;
        lineSeriesActualValue.IndependentValuePath = FieldForIndependentValue;

        lineSeriesActualValue.ItemsSource = GenerateActualValues();

        // Adding a max limit to chart
        if (!ItemsSource.IsNull() && ((!GetMaxLimitValue1(this).IsNull()) && !GetMaxLimitValue1(this).Equals(0.0)))
        {
            this.chartView.Series.Add(new LineSeries());

            lineSeriesMaxValue1 = this.chartView.Series[1] as LineSeries;

            Style styleMaxLineSeries = new Style();
            styleMaxLineSeries.Setters.Add(new Setter(LineSeries.BackgroundProperty, new SolidColorBrush(Color.FromArgb(255, 234, 178, 15))));
            styleMaxLineSeries.Setters.Add(new Setter(LineSeries.TemplateProperty, null));

            lineSeriesMaxValue1.DataPointStyle = styleMaxLineSeries;

            lineSeriesMaxValue1.DependentValuePath = FieldForDependentValue;
            lineSeriesMaxValue1.IndependentValuePath = FieldForIndependentValue;

            lineSeriesMaxValue1.ItemsSource = GenerateLimitValues("Max1");

            if (this.chartView.Series.Contains(lineSeriesMaxValue1))
                this.chartView.Series.Remove(lineSeriesMaxValue1);

            this.chartView.Series.Add(lineSeriesMaxValue1);
        }

        // Adding a min limit to chart
        if (!ItemsSource.IsNull() && ((!GetMinLimitValue1(this).IsNull()) && !GetMinLimitValue1(this).Equals(0.0)))
        {
            this.chartView.Series.Add(new LineSeries());

            lineSeriesMinValue1 = this.chartView.Series[2] as LineSeries;

            Style styleMinLineSeries = new Style();
            styleMinLineSeries.Setters.Add(new Setter(LineSeries.BackgroundProperty, new SolidColorBrush(Color.FromArgb(255, 234, 178, 15))));
            styleMinLineSeries.Setters.Add(new Setter(LineSeries.TemplateProperty, null));

            lineSeriesMinValue1.DataPointStyle = styleMinLineSeries;

            lineSeriesMinValue1.DependentValuePath = FieldForDependentValue;
            lineSeriesMinValue1.IndependentValuePath = FieldForIndependentValue;

            lineSeriesMinValue1.ItemsSource = GenerateLimitValues("Min1");

            if (this.chartView.Series.Contains(lineSeriesMinValue1))
                this.chartView.Series.Remove(lineSeriesMinValue1);

            this.chartView.Series.Add(lineSeriesMinValue1);
        }

        // Adding a target value to chart
        if (!ItemsSource.IsNull() && ((!GetTargetValue(this).IsNull()) && !GetTargetValue(this).Equals(0.0)))
        {
            this.chartView.Series.Add(new LineSeries());

            lineSeriesTarget = this.chartView.Series[3] as LineSeries;

            Style styleTargetLineSeries = new Style();
            styleTargetLineSeries.Setters.Add(new Setter(LineSeries.BackgroundProperty, Brushes.Gray));
            styleTargetLineSeries.Setters.Add(new Setter(LineSeries.TemplateProperty, null));

            lineSeriesTarget.DataPointStyle = styleTargetLineSeries;

            lineSeriesTarget.DependentValuePath = FieldForDependentValue;
            lineSeriesTarget.IndependentValuePath = FieldForIndependentValue;

            lineSeriesTarget.ItemsSource = GenerateLimitValues("Target");

            if (this.chartView.Series.Contains(lineSeriesTarget))
                this.chartView.Series.Remove(lineSeriesTarget);

            this.chartView.Series.Add(lineSeriesTarget);
        }

        // Adding a max limit to chart
        if (!ItemsSource.IsNull() && ((!GetMaxLimitValue2(this).IsNull()) && !GetMaxLimitValue2(this).Equals(0.0)))
        {
            this.chartView.Series.Add(new LineSeries());

            lineSeriesMaxValue2 = this.chartView.Series[4] as LineSeries;

            Style styleMaxLineSeries = new Style();
            styleMaxLineSeries.Setters.Add(new Setter(LineSeries.BackgroundProperty, new SolidColorBrush(Color.FromArgb(255, 255, 0, 0))));
            styleMaxLineSeries.Setters.Add(new Setter(LineSeries.TemplateProperty, null));

            lineSeriesMaxValue2.DataPointStyle = styleMaxLineSeries;

            lineSeriesMaxValue2.DependentValuePath = FieldForDependentValue;
            lineSeriesMaxValue2.IndependentValuePath = FieldForIndependentValue;

            lineSeriesMaxValue2.ItemsSource = GenerateLimitValues("Max2");

            if (this.chartView.Series.Contains(lineSeriesMaxValue2))
                this.chartView.Series.Remove(lineSeriesMaxValue2);

            this.chartView.Series.Add(lineSeriesMaxValue2);
        }

        // Adding a min limit to chart
        if (!ItemsSource.IsNull() && ((!GetMinLimitValue2(this).IsNull()) && !GetMinLimitValue2(this).Equals(0.0)))
        {
            this.chartView.Series.Add(new LineSeries());

            lineSeriesMinValue2 = this.chartView.Series[5] as LineSeries;

            Style styleMinLineSeries = new Style();
            styleMinLineSeries.Setters.Add(new Setter(LineSeries.BackgroundProperty, new SolidColorBrush(Color.FromArgb(255, 255, 0, 0))));
            styleMinLineSeries.Setters.Add(new Setter(LineSeries.TemplateProperty, null));

            lineSeriesMinValue2.DataPointStyle = styleMinLineSeries;

            lineSeriesMinValue2.DependentValuePath = FieldForDependentValue;
            lineSeriesMinValue2.IndependentValuePath = FieldForIndependentValue;

            lineSeriesMinValue2.ItemsSource = GenerateLimitValues("Min2");

            if (this.chartView.Series.Contains(lineSeriesMinValue2))
                this.chartView.Series.Remove(lineSeriesMinValue2);

            this.chartView.Series.Add(lineSeriesMinValue2);
        }

        // Configure axis

        if (ItemsSource.IsNull() || (((IList)ItemsSource).Count == 0))
        {
            foreach (var actualAxis in this.chartView.ActualAxes)
                if (actualAxis.Orientation.Equals(AxisOrientation.Y))
                {
                    (actualAxis as LinearAxis).Maximum = null;
                    (actualAxis as LinearAxis).Minimum = null;

                    (actualAxis as LinearAxis).ShowGridLines = ShowGridLinesY;
                    (actualAxis as LinearAxis).Visibility = Visibility.Collapsed;
                }
                else if (actualAxis.Orientation.Equals(AxisOrientation.X))
                {
                    if (actualAxis is DateTimeAxis)
                    {
                        if (!FieldForIndependentValue.IsNullOrEmpty() && FieldForIndependentValue.Contains("DateTime"))
                        {
                            (actualAxis as DateTimeAxis).Maximum = null;
                            (actualAxis as DateTimeAxis).Minimum = null;

                            (actualAxis as DateTimeAxis).Visibility = Visibility.Collapsed;
                        }
                        else

                            (actualAxis as DateTimeAxis).Visibility = Visibility.Collapsed;
                    }
                    else
                    {
                        if (!FieldForIndependentValue.IsNullOrEmpty() && FieldForIndependentValue.Contains("Double"))
                        {
                            (actualAxis as LinearAxis).Maximum = null;
                            (actualAxis as LinearAxis).Minimum = null;

                            (actualAxis as LinearAxis).Visibility = Visibility.Collapsed;

                            (actualAxis as LinearAxis).ShowGridLines = ShowGridLinesX;
                        }
                        else

                            (actualAxis as LinearAxis).Visibility = Visibility.Collapsed;
                    }
                }
        }
        else if ((this.chartView.Axes.Count > 0) && ((!ItemsSource.IsNull()) && ((IList)ItemsSource).Count > 0))
        {
            foreach (var actualAxis in this.chartView.ActualAxes)
            {

                if (actualAxis.Orientation.Equals(AxisOrientation.Y))
                {
                    (actualAxis as LinearAxis).Maximum = null;
                    (actualAxis as LinearAxis).Minimum = null;

                    (actualAxis as LinearAxis).Maximum = MaximumAxisY;
                    (actualAxis as LinearAxis).Minimum = MinimumAxisY;

                    (actualAxis as LinearAxis).Visibility = Visibility.Visible;

                    (actualAxis as LinearAxis).ShowGridLines = ShowGridLinesY;
                }
                else if (actualAxis.Orientation.Equals(AxisOrientation.X))
                {
                    if (actualAxis is DateTimeAxis)
                    {
                        if (!FieldForIndependentValue.IsNullOrEmpty() && FieldForIndependentValue.Contains("DateTime"))
                        {
                            (actualAxis as DateTimeAxis).Maximum = null;
                            (actualAxis as DateTimeAxis).Minimum = null;

                            (actualAxis as DateTimeAxis).Maximum = ((IList<ChartItems>)ItemsSource).Select(s => s.ValueXDateTime).LastOrDefault();
                            (actualAxis as DateTimeAxis).Minimum = ((IList<ChartItems>)ItemsSource).Select(s => s.ValueXDateTime).FirstOrDefault();

                            (actualAxis as DateTimeAxis).ShowGridLines = ShowGridLinesX;

                            (actualAxis as DateTimeAxis).Visibility = Visibility.Visible;
                        }
                        else

                            (actualAxis as DateTimeAxis).Visibility = Visibility.Collapsed;
                    }
                    else
                    {
                        if (!FieldForIndependentValue.IsNullOrEmpty() && FieldForIndependentValue.Contains("Double"))
                        {
                            (actualAxis as LinearAxis).Maximum = null;
                            (actualAxis as LinearAxis).Minimum = null;

                            if (IntervalAxisX > 0)
                                (actualAxis as LinearAxis).Interval = IntervalAxisX;

                            (actualAxis as LinearAxis).Maximum = ((IList<ChartItems>)ItemsSource).Select(s => s.ValueXDouble).LastOrDefault() + 0.5;
                            (actualAxis as LinearAxis).Minimum = ((IList<ChartItems>)ItemsSource).Select(s => s.ValueXDouble).FirstOrDefault() - 0.5;

                            (actualAxis as LinearAxis).Visibility = Visibility.Visible;

                            (actualAxis as LinearAxis).ShowGridLines = ShowGridLinesX;


                        }
                        else
                        (actualAxis as LinearAxis).Visibility = Visibility.Collapsed;
                    }
                }
            }
        }
    }
}

Any help will be appreciated.

Best Regards, Gustavo.

回答1:

As it has turned out, it is not difficult to add custom elements to the chart. You should retrieve the chart area, calculate positions of your elements and just add them as you add to any panel.

Here is the complete example:

private Canvas canvas;

public MainWindow()
{
    InitializeComponent();

    chart.Loaded += this.OnChartLoaded;
}

private void OnChartLoaded(object sender, RoutedEventArgs e)
{
    var chartArea = (Panel)chart.GetType().GetProperty("ChartArea", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(chart, null);

    // create a canvas to which all text blocks will be added
    this.canvas = new Canvas();
    chartArea.Children.Add(this.canvas);
}

public void AddAllLineLabels() 
{
    // add a red label
    double value = 15;
    var text = new TextBlock() { Text = value.ToString(), Foreground = Brushes.Red };
    AddTextToCanvas(canvas, text, value);

    // add a green label
    value = 19;
    text = new TextBlock() { Text = value.ToString(), Foreground = Brushes.Green };
    AddTextToCanvas(canvas, text, value);
}

private void AddTextToCanvas(Canvas canvas, TextBlock text, double value)
{
    var valuesAxis = chart.ActualAxes.OfType<LinearAxis>().FirstOrDefault(ax => ax.Orientation == AxisOrientation.Y);

    var min = valuesAxis.ActualMinimum.Value;
    var max = valuesAxis.ActualMaximum.Value;

    var maxPixels = valuesAxis.ActualHeight;
    var valuePixels = (value - min) / (max - min) * maxPixels; // from the bottom edge to the value in pixels

    Canvas.SetRight(text, 5); // 5 is a padding from the right edge, you can use any number
    Canvas.SetBottom(text, valuePixels);
    canvas.Children.Add(text);
}

If you call the AddAllLineLabels method, it will display the red number 15 and green number 19.