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

使用 StackedColumnSeries 图表进行分组

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (5投票s)

2011年8月8日

CPOL

3分钟阅读

viewsIcon

38679

downloadIcon

1922

为 Silverlight Toolkit 的 StackedColumnSeries 图表添加分组支持。

Demo Chart

引言

我一直在为客户构建许多仪表板,最近升级到了 Silverlight 4 及其对应的 Toolkit 版本。我直接使用了图表的堆叠功能,并发现了一个非常令人惊讶的事情。如果你添加多个 StackedColumnSeries,只会显示一列。

令人惊讶的原因是,非堆叠版本的 ColumnSeries 可以很好地进行分组。

ColumnSeries

由于将堆叠列分组(或像 Excel 那样称之为聚类)对于在不损失粒度的情况下比较信息组非常有用,因此我决定添加这个缺失的功能。事实证明这是一个极其简单的更改,但提供了一个简洁的升级。

图表架构

一个 Chart 由一个或多个序列组成。通常,每个序列将是一条线或一列等。图表系统的设计非常灵活,因此自定义序列基本上可以是任何它想成为的东西。为了具有这种灵活性,图表会查看序列定义以帮助确定轴的详细信息和范围。然后,它会遍历定义的序列并让它们绘制自己。

像放置在同一容器中的所有控件一样,它们按照声明的顺序绘制。因此,当有多个序列想要占据相同的屏幕区域时,只能看到最后一个。

堆叠序列框架为设置添加了一个新的抽象层。与其只是将 ColumnSeries 添加到图表中,不如添加 StackedColumnSeries。在其中,您可以定义实际的序列,这些序列在每个类别中堆叠在一起。

然后,这个 StackedColumnSeries 管理所有定义的序列和 DataItem,并配置它们的绘制位置。由于它的实现将每一列都扩展到分配给每个类别的整个宽度,因此如果有多个声明,则只能看到一个。

升级

由于 StackedColumnSeries 类处理 DataItem 的定位,所以我从它继承,并覆盖了 UpdateDataItemPlacement 方法。我基本上从 Toolkit 的源代码中复制了这个方法以及所需的私有和内部依赖项。然后更新了分配给每个 DataItem 的位置和宽度。

第一部分是找出将有多少组以及正在配置哪个序列。

var numSeries = this.SeriesHost.Series.Count;
int index = this.SeriesHost.Series.IndexOf(this);

然后根据该内容更新渲染细节

width = width / numSeries;
leftCoordinate = leftCoordinate + (width * index);

Using the Code

通过我的升级,您所要做的就是在图表中定义多个 GroupedStackedColumnSeries。每个序列定义一个单独的堆叠值列。

下面的设置创建了两组堆叠列。比较每个类别的净值(已实现和预计)与总值(已实现和预计)。

<chartingToolkit:Chart x:Name="chart" BorderThickness="0" >
    <chartingToolkit:Chart.Series>
        <sdk:GroupedStackedColumnSeries >
            <chartingToolkit:SeriesDefinition Title="Net (Projected)" 
            IndependentValueBinding="{Binding Name}" DependentValueBinding="{Binding Value}" />
            <chartingToolkit:SeriesDefinition Title="Net (Realized)" 
            IndependentValueBinding="{Binding Name}" DependentValueBinding="{Binding Value}" />                               
        <sdk:GroupedStackedColumnSeries >
            <chartingToolkit:SeriesDefinition Title="Gross (Projected)" 
            IndependentValueBinding="{Binding Name}" DependentValueBinding="{Binding Value}"  />
            <chartingToolkit:SeriesDefinition Title="Gross (Realized)" 
            IndependentValueBinding="{Binding Name}" DependentValueBinding="{Binding Value}" />
        </sdk:GroupedStackedColumnSeries>
    </chartingToolkit:Chart.Series>
    <chartingToolkit:Chart.Axes>
        <chartingToolkit:LinearAxis Orientation="Y" 
             Minimum="0" Location="Left" Title="Sales"  />
        <chartingToolkit:CategoryAxis Title="Category" Orientation="X"/>
    </chartingToolkit:Chart.Axes>
</chartingToolkit:Chart>

通过填充所有 SeriesDefinitionsItemSource 加载数据。我认为所需的数据结构是一个键/值对数组,每个类别项或该列堆栈中的每一层对应一个条目。

StackedColumnSeries series = chart.Series[0] as StackedColumnSeries;
series.SeriesDefinitions[0].ItemsSource = e.Result.Result.RealizedNet;
series.SeriesDefinitions[1].ItemsSource = e.Result.Result.ProjectedNet;
     
series = chart.Series[1] as StackedColumnSeries;
series.SeriesDefinitions[0].ItemsSource = e.Result.Result.RealizedGross;
series.SeriesDefinitions[1].ItemsSource = e.Result.Result.ProjectedGross;

关注点

我还添加了一些选择逻辑,以确保一次只能从任何序列中选择一个 DataPoint,即使它们位于不同的列中。这只是为了改善用户对这些分组的体验。

**不要设置背景 - 因为序列层叠在彼此的顶部,所以最后一个具有背景的序列将隐藏所有先前的序列。

历史

  • 2011 年 8 月 6 日 - 原始文章。
© . All rights reserved.