Andrea86
Andrea86

Reputation: 149

How to change the color of the DataPoint label based on the chart bar size?

I added a new chart control (System.Windows.Forms.DataVisualiation.Charting) with ChartType Bar. As requirement the label text must be white and into the bar value. Therefore I set the BarLabelStyle=Right in the CustomProperties of the DataPoint objects and the LabelForeColor to White. See the below images.

enter image description here

The label in the 2nd gray bar is correctly shown.
The first bar instead is too small and the white text is shown out on the right side but is not visible.

enter image description here

However, when the bar is too short, the label text is positioned outside the bar and the text cannot be seen using white color. Is there a way to check when the label text is drawn outside the bar value so that I can change the color (e.g. black)?

Thanks.

Upvotes: 3

Views: 2389

Answers (1)

TaW
TaW

Reputation: 54433

Unfortunately MCChart has almost no capacities wrt to dynamic expressions.

To work around you can either..:

  • Code the ForeColor depending on the y-value your DataPoints has. Either right when you add them or in a function that loops over all points, whenever you call it.. - Depending on the Font, the axis range and the label text this could be some threshold number you have to determine.

Example:

int p = yourSeries.Points.AddXY(...);
yourSeries.Points[p].LabelForeColor = yourSeries.Points[p].YValues[0] < threshold ?
                                      Color.Black : Color.White;
  • Or you can cheat a little ;-)

You can set the LabelBackColor to have the same color as the Series, i.e. the bar itself. Here is is how to do that:

To access the Series.Color we have to call:

chart.ApplyPaletteColors();

Now we can set

yourSeries.LabelForeColor = Color.White;
yourSeries.LabelBackColor =  yourSeries.Color;

Example:

enter image description here


Update:

Since you can't use the cheat you will have to set the colors.

The challenge is to know just how much space each label's text needs compared to how much space the bars have. The former can be measured (TextRenderer.MeasureString()) and the latter can be extracted from the y-axis (Axis.ValueToPixelPosition()).

Here is a function to do that; it is a little more complicated than I had hoped for, mostly because it tries to be generic..

void LabelColors(Chart chart, ChartArea ca, Series s)
{
    if (chart.Series.Count <= 0 || chart.Series[0].Points.Count <= 0) return;
    Axis ay = ca.AxisY;

    // get the maximum & minimum values
    double maxyv = ay.Maximum;
    if (maxyv == double.NaN) maxyv = s.Points.Max(v => v.YValues[0]);
    double minyv = s.Points.Min(v => v.YValues[0]);

    // get the pixel positions of the minimum
    int y0x =  (int)ay.ValueToPixelPosition(0);

    for (int i = 0; i < s.Points.Count; i++)
    {
        DataPoint dp = s.Points[i];
        // pixel position of the bar right
        int vx = (int)ay.ValueToPixelPosition(dp.YValues[0]);
        // now we knowe the bar's width
        int barWidth = vx - y0x;
        // find out what the label text actauly is
        string t = dp.LabelFormat != "" ? 
                 String.Format(dp.LabelFormat, dp.YValues[0]) : dp.YValues[0].ToString();
        string text = dp.Label != "" ? dp.Label : t;
        // measure the (formatted) text
        SizeF rect = TextRenderer.MeasureText(text, dp.Font);
        Console.WriteLine(text);
        dp.LabelForeColor = barWidth < rect.Width ? Color.Black : Color.White;
    }
}

I may have overcomplicated the way to get at the text that should show; you certainly can decide if you can simplify for your case.

Note: You must call this function..

  • whenever your data may have changed
  • only after the axes of the chart have finished their layout (!)

The former point is obvious, the latter isn't. It means that you can't call the function right after adding your points! Instead you must do it at some later place or else the axis function needed to get the bar size will not work.

MSDN says it can only happen in a PaintXXX event; I found that all mouse events also work and then some..

To be save I'll put it in the PostPaint event:

private void chart_PostPaint(object sender, ChartPaintEventArgs e)
{
    LabelColors(chart, chart.ChartAreas[0], chart.Series[0]);
}

enter image description here

Upvotes: 4

Related Questions