How to show label on YAxis every N value?

Viewed 223

I'm using winform chart to display data, I want to show labels but the chart is very crowded.

enter image description here

Is there a way to skip, and show label only every N value?

chart series added:

var lastTrades = new Series("LastTrade") { ChartType = SeriesChartType.Line, XValueType = ChartValueType.DateTime, MarkerStyle = MarkerStyle.Circle };
lastTrades.IsValueShownAsLabel = true;

I was shooting in the dark with these properties with no luck:

    lastTrades.SmartLabelStyle.Enabled = true;
    lastTrades.SmartLabelStyle.IsOverlappedHidden = true;

Suggestions are appreciated.

3 Answers

A Series has a DataPointCollection in property Series.Points.

DataPointCollection is a ChartElementCollection<DataPoint>, which implements ICollection<DataPoint>.

Every DataPoint is derived from DataPointCustomProperties which has a Boolean property DataPointCustomProperties.IsValueShownAsLabel.

So all you have to do, is to extend one of these collection classes with a method that sets this property to true for every Nth element.

One of the problem is, that there is no guarantee about the order of the DataPoints in the collection. So just setting the property for every Nth element them could lead to a bunch of DataPoints that are very near each other that have a label.

Example: Every 10 th element a label

 Index   Position 
   0     [0, 0] label
   1     [1, 0]
   2     [2, 0]
  ...
   8     [10, 0]
   9     [0.001, 0] label
  10     [0, 1]
  11     [0, 2]
  ...    
  19     [0, 0.001] label
  ...
  29     [0.001, 0.001] label
  ...

A better solution would be to have only one point per rectangle have a label.

So given a sequence of DataPoints, you make groups of DataPoints that are in the same rectangle. After that you select only one DataPoint per rectangle, lets say the most lower left one gets a label. Of course you want to specify the size of the rectangle.

One of the problems is that every DataPoint may have several Y-values, and that all Y-value will have a label. Let's assume that you have only one Y per DataPoint. Let's call this [X, Y] value the Position of the DataPoint. X and Y are doubles.

Now suppose we have rectangles with Width 3 and Height 2. Then horizontally, the rectangles are in the intervals x= [0; 3), [3; 6), [6, 9), etc. and vertical [0; 2), [2, 4), etc.

If your DataPoint has position[7, 3], then it is in the 3nd rectangle horizontal, 2nd rectangle vertical, or zero-indexed: in rectangle with index [2, 1].

The formulas: to calculate the X-index: (int)(position.X / Width) and for Y: (int)(position.Y / height)

double width = ...
double height = ...
Series mySeries = ...

var dataPointsWithLabel = mySeries.Points

    // first calculate the (X, Y) Position of the DataPoint.
    // Remember the original DataPoint
    .Select(dataPoint => new
    {
        Position = new
        {
            X = dataPoint.XValue,
            Y = dataPoint.YValues.FirstOrDefault(),
        },

        Value = dataPoint,
    })

Now use Width and Height to calculate the rectangle index remember the original position and the original DataPoint, we need it later.

    .Select(dataPoint => new
    {
        Position = dataPoint.Position

        RectangleIndex = new
        {
            IndexX = (int)(dataPoint.Position.X / Width),
            IndexY = (int)(dataPoint.Position.Y / Height),
        }

        Value = dataPoint,
    })

make groups with same RectangleIndex:

    .GroupBy(dataPoint => dataPoint.RectangleIndex,

        // parameter resultSelector: take each rectangleIndex
        // and all dataPoints that have an (X, Y) position in this rectangle
        // to make one new:
        (rectangleIndex, dataPointsInThisRectangle) => new
        {
            // todo: implement
        });

Now from all DataPoints in this group, we want to select only the one that should have a label. All other DataPoints will have no label.

So we need to decide which DataPoint in this rectangle do we want? The center one? The leftmost one? You decide!

For simplicity I select the one with the smallest X, and if we have two with the same X, I select the one with the smallest Y. So I order by X then by Y, and take the first one.

(rectangleIndex, dataPointsInThisRectangle) =>

    // Order by X, then by Y
    .OrderBy(dataPoint => DataPoint.Position.X)
    .ThenBy(dataPoint => DataPoint.Position.Y)

    // Select only the Value (which contains the original DataPoint)
    .Select(dataPoint => dataPoint.Value)

    // and keep only the first one:
    .FirstOrDefault(),

So from your series, we selected the DataPoints, we assumed that every DataPoint has only one Y value. We calculated the (X, Y) Position of it. We calculated in which rectangle this DataPoint would be.

We made groups of DataPoints with that are in the same Rectangle. All DataPoints in one group were ordered by X position and by Y position. From the ordered result we took the first DataPoint.

So per Rectangle we have only one DataPoint. This DataPoint is the only DataPoint that will have a label.

foreach (var dataPoint in dataPointsWithLabel)
{
    dataPoint.IsValueShownAsLabel = true;
}

something like this would help:

lastTrades.IsValueShownAsLabel = false;

var skipSequence = 4

for (var idx = 0; idx < lastTrades.Points.Count; idx = idx + skipSequence)
        lastTrades.Points[idx].Label = "correct label";

switch off the automatic labeling and label the values by hand with a for-loop:

lastTrades.IsValueShownAsLabel = true;
int stepSize = 5;

for (int i = 0; i < lastTrades.Points.Count; i+=stepSize)
{
    lastTrades.Points[i].Label = lastTrades.Points[i].YValues.First().ToString();
}

Here is some test code:

LineGraph.Series.Add("Line1");
LineGraph.Series["Line1"].ChartType = SeriesChartType.Line;
LineGraph.Series["Line1"].IsValueShownAsLabel = false;

for (int i = 0; i < 20; i++)
{
    LineGraph.Series["Line1"].Points.AddY(i);
}

var ser = LineGraph.Series["Line1"];

int stepSize = 5;

for (int i = 0; i < ser.Points.Count; i+=stepSize)
{
    ser.Points[i].Label = ser.Points[i].YValues.First().ToString();
}

And here is the result:

enter image description here

Related