65.9K
CodeProject 正在变化。 阅读更多。
Home

堆积条形图 Silverlight 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (8投票s)

2009年5月3日

CPOL

2分钟阅读

viewsIcon

44424

Silverlight 的堆积条形图控件。

Silverlight 工具包为开发者和设计师提供了大量标准中不包含的不同控件。 其中包括用于创建条形图、饼图和折线图等的图表控件。 我最近有一个需求是需要一个堆叠条形图,不幸的是,这是少数几种默认情况下不受支持的图表类型之一。 然而,在阅读了 Jafar Husain 发表的一系列文章并查看了工具包源代码后,我意识到我可以相对容易地自己创建一个堆叠条形图——尽管我对 Silverlight 的了解有限。 在这里,我必须向 Jafar 道歉——我通过他的博客向他寻求建议,他回复了一个非常全面的描述,说明了他将如何解决这个问题。 Jafar 的方法会产生一个更完整、更灵活的解决方案; 然而,对于我需要的东西来说,这有些过头了,而且我必须尽快完成一些工作,所以我采取了一种(可以说)更hacky的方法。

我采用的方法是创建一个新的 StackedColumnSeries,它是 System.Windows.Controls.DataVisualization.Charting.ColumnSeries 类的完整副本。 在这一点上,能够使用 ColumnSeries 作为 StackedColumnSeries 的基类将会很棒,但由于 ColumnSeriessealed 的,我不得不复制和粘贴。 为了使我的 ColumnSeries 副本能够编译,我还需要从工具包中复制和粘贴其他一些代码块,因为各种辅助类等是内部的。 如果您 下载代码,您会看到我清楚地注释了哪些代码是我复制的,因此,如果工具包团队将来稍微开放他们的代码,那么更改 StackedColumnSeries 类将会非常简单。

一旦 StackedColumnSeries 类编译并通过了与 ColumnSeries 相同的功能,我只需要更改两个方法才能使其像堆叠柱状图一样工作。 我更改的方法是 UpdateDataPoint(DataPoint dataPoint),以便可以渲染列,使其彼此堆叠,以及 GetValueMargins(IValueMarginConsumer consumer),以便我可以增加轴上显示的值的范围,考虑到现在需要显示的最大值是堆叠列的总和。

下面显示了这两个更改的方法——请参阅代码中的注释,了解我需要对从 ColumnSeries 复制的代码进行的更改。

/// <summary>
/// Updates each point.
/// </summary>
/// <param name="dataPoint">The data point to update.</param>
protected override void UpdateDataPoint(DataPoint dataPoint)
{
    if (SeriesHost == null || PlotArea == null)
    {
        return;
    }

    object category = dataPoint.IndependentValue ?? 
          (this.ActiveDataPoints.IndexOf(dataPoint) + 1);
    Range<UnitValue> coordinateRange = 
                           GetCategoryRange(category);
    if (!coordinateRange.HasData)
    {
        return;
    }
    else if (coordinateRange.Maximum.Unit != Unit.Pixels || 
             coordinateRange.Minimum.Unit != Unit.Pixels)
    {
        throw new InvalidOperationException("This Series " + 
                  "Does Not Support Radial Axes");
    }

    double minimum = (double)coordinateRange.Minimum.Value;
    double maximum = (double)coordinateRange.Maximum.Value;

    double plotAreaHeight = ActualDependentRangeAxis.GetPlotAreaCoordinate(
                            ActualDependentRangeAxis.Range.Maximum).Value.Value;
    IEnumerable<StackedColumnSeries> columnSeries = 
      SeriesHost.Series.OfType<StackedColumnSeries>().Where(series => 
      series.ActualIndependentAxis == ActualIndependentAxis);
    int numberOfSeries = columnSeries.Count();
    double coordinateRangeWidth = (maximum - minimum);
    double segmentWidth = coordinateRangeWidth * 0.8;

    // For stacked chart, no need to scale
    // the width of the columns if there are more series
    double columnWidth = segmentWidth; // / numberOfSeries;
    int seriesIndex = columnSeries.IndexOf(this);

    double dataPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(
           ValueHelper.ToDouble(dataPoint.ActualDependentValue)).Value.Value;
    double zeroPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(
                        ActualDependentRangeAxis.Origin).Value.Value;

    // Need to shift the columns up to take account of the other
    // columns that are already rendered, to make them
    // appear stacked
    int dataPointIndex = ActiveDataPoints.IndexOf(dataPoint);

    for (int i = numberOfSeries - 1; i > seriesIndex; i--)
    {
        StackedColumnSeries prevSeries = columnSeries.ElementAt<StackedColumnSeries>(i);

        if (prevSeries.ActiveDataPointCount >= dataPointIndex + 1)
        {
            double yOffset = ActualDependentRangeAxis.GetPlotAreaCoordinate(
                   ValueHelper.ToDouble(prevSeries.ActiveDataPoints.ElementAt<DataPoint>(
                   dataPointIndex).ActualDependentValue)).Value.Value;

            dataPointY += yOffset;
            zeroPointY += yOffset;
        }
    }

    // No offset for stacked bar charts so that all the columns line up
    double offset = 0; /*seriesIndex * Math.Round(columnWidth) + 
                         coordinateRangeWidth * 0.1;*/
    double dataPointX = minimum + offset;

    if (GetIsDataPointGrouped(category))
    {
        // Multiple DataPoints share this category;
        // offset and overlap them appropriately
        IGrouping<object, DataPoint> categoryGrouping = 
                                     GetDataPointGroup(category);
        int index = categoryGrouping.IndexOf(dataPoint);
        dataPointX += (index * (columnWidth * 0.2)) / 
                      (categoryGrouping.Count() - 1);
        columnWidth *= 0.8;
        Canvas.SetZIndex(dataPoint, -index);
    }

    if (ValueHelper.CanGraph(dataPointY) && 
        ValueHelper.CanGraph(dataPointX) && 
        ValueHelper.CanGraph(zeroPointY))
    {
        // Remember, the coordinate 0,0 is in the top left hand corner,
        // therefore the "top" y coordinate is going to
        // be a smaller value than the bottom.
        double left = Math.Round(dataPointX);
        double width = Math.Round(columnWidth);

        double top = Math.Round(plotAreaHeight - 
                     Math.Max(dataPointY, zeroPointY) + 0.5);
        double bottom = Math.Round(plotAreaHeight - 
                        Math.Min(dataPointY, zeroPointY) + 0.5);
        double height = bottom - top + 1;

        Canvas.SetLeft(dataPoint, left);
        Canvas.SetTop(dataPoint, top);
        dataPoint.Width = width;
        dataPoint.Height = height;
    }
}

/// <summary>
/// Returns the value margins for a given axis.
/// </summary>
/// <param name="consumer">The axis to retrieve the value margins for.
/// </param>
/// <returns>A sequence of value margins.</returns>
protected override IEnumerable<ValueMargin> 
                   GetValueMargins(IValueMarginConsumer consumer)
{
    double dependentValueMargin = this.ActualHeight / 10;
    IAxis axis = consumer as IAxis;
    if (axis != null && ActiveDataPoints.Any())
    {
        Func<DataPoint, IComparable> selector = null;
        if (axis == InternalActualIndependentAxis)
        {
            selector = (dataPoint) => (IComparable)dataPoint.ActualIndependentValue;

            DataPoint minimumPoint = ActiveDataPoints.MinOrNull(selector);
            DataPoint maximumPoint = ActiveDataPoints.MaxOrNull(selector);

            double minimumMargin = minimumPoint.GetMargin(axis);
            yield return new ValueMargin(selector(minimumPoint), 
                             minimumMargin, minimumMargin);

            double maximumMargin = maximumPoint.GetMargin(axis);
            yield return new ValueMargin(selector(maximumPoint), 
                             maximumMargin, maximumMargin);
        }
        else if (axis == InternalActualDependentAxis)
        {
            // Some up the total value of the current set of columns
            double valueTotal = 0.0;

            foreach (DataPoint dataPoint in 
                     ActiveDataPoints.AsEnumerable<DataPoint>())
            {
                valueTotal += (double)dataPoint.ActualDependentValue;
            }
            // The minimum value always needs to be 0 for stacked column charts
            yield return new ValueMargin(0.0, dependentValueMargin, 
                                         dependentValueMargin);
            yield return new ValueMargin(valueTotal, dependentValueMargin, 
                                         dependentValueMargin);
        }
    }
    else
    {
        yield break;
    }
}

运行此代码并将一些数据绑定到图表的结果如下所示(请注意,我添加了一条系列线)

Staked Bar Chart

© . All rights reserved.