程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 登峰造極的UI - 使用數據模板創建折線圖

登峰造極的UI - 使用數據模板創建折線圖

編輯:關於.NET

盡管現在湧現出了許多各種各樣的高級計算機圖形(包括動畫和 3-D),但我認為,最重 要的永遠是使用條形圖、餅圖和折線構建的傳統圖表中基本直觀的數據表示形式。

雖然數據表可能看起來像是一堆雜亂的隨機數字,但圖形中隱藏的任何趨勢或有趣的信息 比在圖表中顯示更易於理解。

借助 Windows Presentation Foundation (WPF) 及其基於 Web 的分支 Silverlight,我 們發現了在標記(而不是代碼)中定義圖形畫面的優點。與代碼相比,可擴展應用程序標記 語言 (XAML) 更易於更改、體驗且易於作為工具使用,從而允許我們以交互方式定義我們的 畫面並試用可選方法。

事實上,完全在 XAML 中定義畫面非常有利,這樣,WPF 編程人員可以將大量時間用於專 門編寫代碼,從而使 XAML 功能更強大、更靈活。這種現象我稱之為“對 XAML 進行編碼” ,這也是 WPF 更改應用程序開發方法的幾種方式之一。

WPF 中許多功能最強大的技術都包括 ItemsControl,它是通常顯示同一類型的項目集合 的基本控件。(ItemsControl 派生的一種常見控件是 ListBox,可通過它進行導航、選擇和 顯示。)

ItemsControl 可以包含任何類型的對象,甚至是不具有固有的文本或直觀表示形式的業 務對象。神奇的組成之處是 DataTemplate(它幾乎始終在 XAML 中進行定義),它基於對象 的屬性為這些業務對象提供一種直觀的表示形式。

在三月的期刊中,我介紹了如何使用 ItemsControl 和 DataTemplate 在 XAML 中定義條 形圖和餅圖。起初,我曾打算在那篇文章中介紹折線圖,但折線圖的重要性(和難度)使我 不得不在整個專欄中專門介紹該主題。

折線圖問題

折線圖實際上是分散曲線的形式 — 在水平軸和垂直軸上各有一個變量的笛卡爾坐標系統 。折線圖的一個明顯的區別在於圖表上水平方向的值通常都是經過分類的。這些值通常是日 期或時間,也就是說,折線圖通常顯示變量隨時間的變化情況。

另一個明顯的區別在於單個數據點通過直線連接。雖然從表面上看直線是折線圖畫面的基 本組成部分,但實際上它是在 XAML 中繪制圖表過程中的重大破壞性因素。DataTemplate 介 紹如何在 ItemsControl 中呈現每個項,但是連接這些項要求訪問多個點,從理論上講, PointCollection 隨後可與 Polyline 元素結合使用。第一個重要事項是需要生成此 PointCollection,因為對折線圖數據執行預處理需要自定義類。

與其他圖形相比,折線圖更要求多加注意坐標軸。事實上,水平軸和垂直軸本身成為其他 ItemsControl 是有用的!然後,這兩個 ItemsControl 的其他 DataTemplate 可完全用於在 XAML 中定義軸刻度線和標簽的格式。

總之,首先是具有以下兩個屬性的數據項集合:這兩個屬性分別對應於水平軸和垂直軸。 要在 XAML 中繪制圖表,您需要從數據中獲取特定的項。首先,您需要找到每個數據項對應 的點對象(用於呈現各個數據點)。還需要所有數據項的 PointCollection(連接點的直線 )和兩個包含用於在 XAML 中呈現水平軸和垂直軸的充足信息的附加集合,其中包含標簽數 據以及用於定位標簽和刻度線的偏移量。

很顯然,要計算這些 Point 對象和偏移量需要某些信息:圖表的寬度和高度以及在水平 軸和垂直軸上制成圖表的數據的最大值和最小值。

但這還遠遠不夠。假設垂直軸上的最小值是 127,最大值是 232。在這種情況下,您可能 希望垂直軸實際上從 100 延長到 250,每 25 個單位一個刻度線。或者對於此特定圖形,您 可能希望始終包括 0,這樣垂直軸可從 0 延長到 250。又或許您希望最大值始終是 100 的 倍數,這樣該值便位於 0 到 300 之間。如果這些值的范圍為 -125 到 237,或許您希望 0 位於中間,因此該軸的范圍可能為 -300 到 300。

可能有許多不同的策略用於確定坐標軸顯示哪些值,然後這些策略會控制與各個數據項相 關聯的 Point 值的計算。這些策略可能各不相同,因此提供一個“插件”選項以根據需要為 特定圖表定義其他坐標軸策略非常有用。

初次嘗試

有時,編程失敗與編程成功一樣有意義。雖然我第一次嘗試創建可從 XAML 訪問的折線圖 類算不上是完全失敗,但確實為我指明了方向。

我知道,若要生成 Point 對象的集合,很顯然,我需要訪問 ItemsControl 中的項目集 合和控件的 ActualWidth 和 ActualHeight。鑒於以上原因,從我稱之為 LineChartItemsControl 的 ItemsControl 中派生一個類似乎很合乎邏輯。

LineChartItemsControl 定義了幾個新的讀/寫屬性:HorizontalAxisPropertyName 和 VerticalAxisPropertyName 提供了將制成圖表的項目的屬性名稱。其他四個新屬性提供了 LineChartItemsControl 以及水平軸和垂直軸的最大值和最小值。(這是一種非常簡單的處 理坐標軸的方法,我知道這種方法稍後需要提高。)

自定義控件還為 XAML 中的數據綁定定義了三個只讀依賴關系屬性:一個類型為 PointCollection 的 Points 屬性和兩個用於呈現坐標軸的 HorizontalAxisInfo 和 VerticalAxisInfo 屬性。

LineChartItemsControl 覆蓋了項目集合中發生更改時需要通知的 OnItemsSourceChanged 和 OnItemsChanged 方法,它還為 SizeChanged 事件安裝了一個處 理程序。然後非常簡單,整理出所有可用信息以計算這三個只讀依賴關系屬性。

然而,實際在 XAML 中使用 LineChartItemsControl 卻很亂。較容易的部分是呈現連接 的直線。此操作由 Polyline 元素及其 Points 屬性(此屬性已綁定到 LineChartItemsControl 的 Points 屬性)完成。但是,定義用於定位單個數據的 DataTemplate 則十分困難。DataTemplate 只能訪問一個特定數據項的屬性。通過綁定, DataTemplate 可以訪問 ItemsControl 本身,但您如何才能訪問與該特定數據項對應的定位 信息?

我的解決方案涉及 MultiBinding 中的 RenderTransform 集,該集包含 RelativeSource 綁定並引用了 BindingConverter。此解決方案非常復雜,以至於在我對它進行編碼的第二天 ,仍然不能真正明白它的工作原理!

此解決方案的復雜性使我明白,我需要一個完全不同的方法。

實際折線圖生成器

重新構思的解決方案是一個類,我稱之為 LineChartGenerator,因為它可以完全在 XAML 中生成用於定義圖表畫面所需的所有原始材料。輸入一個集合(實際業務對象),輸出四個 集合,其中一個用於數據點、一個用於繪制連接的直線、另外兩個用於水平軸和垂直軸。您 可以使用它在 XAML 中構建一個圖表,該圖表包含多個 ItemsControl(通常排列在 4 乘 4 的網格中,如果您還希望包含標題和其他標簽,則可以使用更大的網格),每個都有其自己 的 DataTemplate 以顯示這些集合。

讓我們來看一下實際的工作原理。(所有可下載源代碼都包含在一個名為 LineChartsWithDataTemplates 的 Visual Studio 項目中。此解決方案包含一個名為 LineChartLib 的 DLL 項目和三個演示程序。)

PopulationLineChart 項目包含一個名為 CensusDatum 的結構,該結構定義兩個名為 Year 和 Population 的整數類型的屬性。CensusData 類派生自 CensusDatum 類型的 ObservableCollection 並使用美國從 1790 年(人口為 3,929,214)到 2000 年(人口為 281,421,906)十年一度的人口普查數據填充該集合。如圖 1 顯示的是生成的圖表。

圖 1 PopulationLineChart 顯示

此圖表的所有 XAML 都在 PopulationLineChart 項目的 Window1.xaml 文件中。圖 2 顯 示的是此文件的“資源”部分。LineChartGenerator 具有其自己的 ItemsSource 屬性,在 此示例中,該屬性被設置為 CensusData 對象。還需要在此處設置 Width 和 Height 屬性。 (我知道此處不是設置這些值的最佳位置,而且對 WPF 中的首選布局方法也不是非常有益, 但是我想不出更好的解決方案。)這些值表示圖表(不包括水平軸和垂直軸)的內部尺寸。

圖 2 PopulationLineChart 的“資源”部分

<Window.Resources>
   <src:CensusData x:Key="censusData" />

   <charts:LineChartGenerator 
       x:Key="generator"
       ItemsSource="{Binding Source={StaticResource censusData}}"
       Width="300"
       Height="200">

     <charts:LineChartGenerator.HorizontalAxis>
       <charts:AutoAxis PropertyName="Year" />
     </charts:LineChartGenerator.HorizontalAxis>

     <charts:LineChartGenerator.VerticalAxis>
       <charts:IncrementAxis PropertyName="Population"
                  Increment="50000000"
                  IsFlipped="True" />
     </charts:LineChartGenerator.VerticalAxis>
   </charts:LineChartGenerator>
</Window.Resources>

LineChartGenerator 也具有兩個 AxisStrategy 類型的屬性,名稱分別為 HorizontalAxis 和 VerticalAxis。AxisStrategy 是一個定義多個屬性(包括 PropertyName)的抽象類,您可以在其中指明希望將其繪制在此坐標軸上的數據對象的屬性 。根據 WPF 的坐標系統,從左到右、從上到下,值逐漸遞增。您幾乎總是希望將垂直軸上的 IsFlipped 屬性設置為 True,以便值從下到上逐漸遞增。

從 AxisStrategy 派生的其中一個類是 IncrementAxis,該類定義一個名為 Increment 的屬性。借助 IncrementAxis 策略,您可以指定刻度線之間的增量。將最大值和最小值設置 為增量的倍數。我已對人口規模使用了 IncrementAxis。

從 AxisStrategy 派生的另一個類是 AutoAxis,該類沒有定義其自己的附加屬性。我已 對水平軸使用此類:它的唯一用途就是在坐標軸上使用實際值。(我還沒有介紹的另一個明 顯的 AxisStrategy 派生類是 ExplicitAxis,您可以在其中提供坐標軸上顯示的值列表。)

LineChartGenerator 類定義了兩個只讀依賴關系屬性。第一個是 PointCollection 類型 的名為 Points 的屬性;使用此屬性繪制連接各個點的直線:

<Polyline Points="{Binding Source={StaticResource generator},
               Path=Points}"
      Stroke="Blue" />

第二個 LineChartGenerator 屬性名為 ItemPoints,類型為 ItemPointCollection。 ItemPoint 具有兩個屬性,名稱分別為 Item 和 Point。Item 是集合中的原始對象,在此具 體示例中,Item 是類型為 CensusDatum 的對象。Point 是一個點,該點是項目在圖形中的 顯示位置。

圖 3 顯示的是表示圖表主體的 ItemsControl。請注意,其 ItemsSource 綁定到 LineChartGenerator 的 ItemPoints 屬性。ItemsPanel 模板是一個網格,ItemTemplate 是 一個包含 EllipseGeometry 和 ToolTip 的路徑。EllipseGeometry 的 Center 屬性綁定到 ItemPoint 對象的 Point 屬性,而 ToolTip 訪問 Item 屬性的 Year 和 Population 屬性 。

圖 3 PopulationLineChart 的主 ItemsControl

<ItemsControl ItemsSource="{Binding Source={StaticResource  generator},
                   Path=ItemPoints}">
   <ItemsControl.ItemsPanel>
     <ItemsPanelTemplate>
       <Grid IsItemsHost="True" />
     </ItemsPanelTemplate>
   </ItemsControl.ItemsPanel>

   <ItemsControl.ItemTemplate>
     <DataTemplate>
       <Path Fill="Red" RenderTransform="2 0 0 2 0 0">
         <Path.Data>
           <EllipseGeometry Center="{Binding Point}"
                    RadiusX="4"
                    RadiusY="4"
                    Transform="0.5 0 0 0.5 0  0" />
         </Path.Data>
         <Path.ToolTip>
           <StackPanel Orientation="Horizontal">
             <TextBlock Text="{Binding Item.Year}" />
             <TextBlock Text="{Binding Item.Population,
               StringFormat=’: {0:N0}’}" />
           </StackPanel>
         </Path.ToolTip>
       </Path>
     </DataTemplate>
   </ItemsControl.ItemTemplate>
</ItemsControl>

您可能想了解 EllipseGeometry 對象上的 Transform 集,它是針對 Path 元素上的 RenderTransform 屬性集的偏移量。這是一台組裝電腦:但是沒有它,只能部分裁剪最右側 的橢圓,而且我無法使用 ClipToBounds 對其進行修復。

Polyline 和此主 ItemsControl 共享同一單個單元格 Grid,其 Width 和 Height 綁定 到 LineChartGenerator 中的值:

<Grid Width="{Binding Source=
{StaticResource generator}, Path=Width}"
    Height="{Binding Source=
{StaticResource generator}, Path=Height}">

在此示例中,Polyline 位於 ItemsControl 下方。

AxisStrategy 類定義其自己的只讀依賴關系屬性 AxisItems,這是一個類型為 AxisItem 的對象集合,具有兩個屬性,名稱分別為 Item 和 Offset。該集合供各個坐標軸的 ItemsControl 使用。盡管 Item 屬性被定義為 Object 類型,但實際上,其類型和與該坐標 軸相關聯的屬性型相同。偏移量是指到頂部或左側的距離。

如圖 4 顯示的是水平軸的 ItemsControl;垂直軸與水平軸類似。ItemsControl 的 ItemsSource 屬性綁定到 LineChartGenerator 的 HorizontalAxis 屬性的 AxisItems 屬性 。因此,使用 AxisItem 類型的對象填充該 ItemsControl。TextBlock 的 Text 屬性綁定到 Items 屬性,而 Offset 屬性用於轉換沿坐標軸的刻度線和文本。

圖 4 PopulationLineChart 的水平軸上的標記

<ItemsControl Grid.Row="2"
        Grid.Column="1"
        Margin="4 0"
        ItemsSource="{Binding Source={StaticResource generator},
                   Path=HorizontalAxis.AxisItems}">
   <ItemsControl.ItemsPanel>
     <ItemsPanelTemplate>
       <Grid IsItemsHost="True" />
     </ItemsPanelTemplate>
   </ItemsControl.ItemsPanel>

   <ItemsControl.ItemTemplate>
     <DataTemplate>
       <StackPanel>
         <Line Y2="10" Stroke="Black" />
         <TextBlock Text="{Binding Item}"
               FontSize="8"
               LayoutTransform="0 -1 1 0 0 0"
               RenderTransform="1 0 0 1 -6 1"/>

         <StackPanel.RenderTransform>
           <TranslateTransform X="{Binding Offset}" />
         </StackPanel.RenderTransform>
       </StackPanel>
     </DataTemplate>
   </ItemsControl.ItemTemplate>
</ItemsControl>

因為這三個 ItemsControl 僅位於 Grid 的三個單元格中,所以設計 XAML 布局的人負責 確保他們能夠正確對齊。應用到這些控件的任何邊框或邊距或填充必須保持一致。圖 4 中 ItemsControl 的水平邊距為 4;的垂直軸上的 ItemsControl 的垂直邊距為 4。我選擇這些 值是為了與單個單元格 Grid 四周的邊框的 BorderThickness 和 Padding 對應,該單個單 元格包括 Polyline 和圖表本身:

<Border Grid.Row="1"
     Grid.Column="1"
     Background="Yellow"
     BorderBrush="Black"
     BorderThickness="1"
     Padding="3">

數據類型一致性

LineChartGenerator 類本身並沒有多大意義。它假設 ItemsSource 集合已經過排序,主 要用於確保當 ItemsSource 屬性更改時更新所有內容。如果將該集合設置為 ItemsSource 會實現 CollectionChanged,則當對該集合添加項目或刪除項目時也將更新該圖表。如果該 集合中的項目實現 InotifyPropertyChanged,則當項目本身發生更改時也會更新該圖表。

實際上,大多數實際工作仍在 AxisStrategy 和派生類中進行。這些 AxisStrategy 派生 類是您設置為 LineChartGenerator 的 HorizontalAxis 和 VerticalAxis 屬性的類。

AxisStrategy 本身定義重要的 PropertyName 屬性,該屬性表明制成圖表的對象的哪些 屬性與該坐標軸相關聯。AxisStrategy 使用反射訪問集合中對象的特定屬性。但是,僅訪問 該屬性是不夠的。AxisStrategy(及其派生類)需要計算此屬性的值以獲取 Point 對象和刻 度偏移量。這些計算包括乘法和除法。

計算的必要性強烈表明制成圖表的屬性必須是數值類型:整數或浮點。然而,通常在折線 圖的水平軸上使用的相當常見的數據類型根本不是數字,而是日期或時間。在 Microsoft .NET Framework 中,我們談論的是類型為 DateTime 的對象。

所有數值數據類型和 DateTime 有什麼共同點?它們都實現 IConvertible 接口,這就意 味著它們都包含許多用於互相轉換的方法並且都可以使用靜態 Convert 類中的同名方法。因 此,要求制成圖表的屬性實現 Iconvertible 對我來說似乎很合理。然後,AxisStrategy( 及其派生類)可以直接將屬性值轉換成雙精度以進行必要的計算。

但是,我很快發現實際上無法使用 ToDouble 方法或 Convert.ToDouble 靜態方法將類型 為 DateTime 的屬性轉換成雙精度。這就意味著確實必須使用特殊邏輯處理類型為 DateTime 的屬性,這幸好不是一個大問題。由 DateTime 定義的 Ticks 屬性是一個 64 位整數,可轉 換成雙精度;雙精度可以通過首先轉換為一個 64 位整數,然後將該值傳遞給 DateTime 構 造函數來轉換回 DateTime。小型實驗表明往返轉換精確到毫秒。

AxisStrategy 包括一個 Recalculate 方法,該方法遍歷其父項的 ItemsSource 集合中 的所有項、將每個對象的指定屬性轉換為雙精度並確定最大值和最小值。AxisStrategy 定義 可能影響這兩個值的三個屬性:Margin 屬性(允許最大值和最小值稍微超出實際值的范圍) ;IncludeZero 屬性(使坐標軸始終包含零值,即使所有值都大於零或小於零); IsSymmetricAroundZero 屬性,表示坐標軸上的最大值應為正數、最小值應為負數,但它們 的絕對值應相同。

經過這些調整後,AxisStrategy 調用抽象的 CalculateAxisItems 方法:

protected abstract void CalculateAxisItems(Type propertyType, ref  double minValue, ref double maxValue);

第一個參數是與該坐標軸對應的屬性的類型。從 AxisStrategy 派生的任何類必須實現此 方法並借此機會定義構成其 AxisItems 集合的項目和偏移量。

CalculateAxisItems 很可能還會設置新的最大值和最小值。CalculateAxisItems 返回後 ,AxisStrategy 使用這些值以及圖表的寬度和高度計算所有項的 Point 值。

XML 數據源

除了處理數值屬性和類型為 DateTime 的屬性外,AxisStategy 還必須處理 ItemsSource 集合中的項目為 XmlNode 類型時的情況。這是當 ItemsSource 綁定到 XmlDataProvider 時 該集合中包含的內容,或引用外部 XAML 文件或是 XAML 文件中的 XAML 數據島。

AxisStrategy 與 DataTemplates 使用相同的約定:名稱本身指的是 XML 元素,前面帶 有 @ 符號的名稱是 XML 屬性。AxisStrategy 將這些值作為字符串獲取。以防這些字符串實 際上是日期或時間,AxisStrategy 在將它們轉換成雙精度之前首先嘗試將這些字符串轉換成 DateTime 對象。DateTime.TryParseExact 用於執行這項操作且僅針對固定文化格式規范: “R”、“s”、“u”和“o”。

SalesByMonth 項目演示繪制 XML 數據關系圖和一些其他功能。Window1.xaml 文件包含 XmlDataProvider 和兩個名為 Widgets 和 Doodads 的產品在 12 個月內的虛構銷售數據:

<XmlDataProvider x:Key="sales"
          XPath="YearSales">
   <x:XData>
     <YearSales xmlns="">
       <MonthSales Date="2009-01-01T00:00:00">
         <Widgets>13</Widgets>
         <Doodads>285</Doodads>
       </MonthSales>

  ...

       <MonthSales Date="2009-12-01T00:00:00">
         <Widgets>29</Widgets>
         <Doodads>160</Doodads>
       </MonthSales>
     </YearSales>
   </x:XData>
</XmlDataProvider>

Resources 部分還包含這兩種產品的兩個非常類似的 LineChartGenerator 對象。下面是 Widgets 的一個對象:

<charts:LineChartGenerator 
         x:Key="widgetsGenerator" 
        ItemsSource= 
         "{Binding Source={StaticResource sales}, 
                    XPath=MonthSales}" 
        Width="250"  Height="150"> 
  <charts:LineChartGenerator.HorizontalAxis> 
    <charts:AutoAxis PropertyName="@Date" /> 
   </charts:LineChartGenerator.HorizontalAxis> 
   
   <charts:LineChartGenerator.VerticalAxis> 
     <charts:AdaptableIncrementAxis 
    PropertyName="Widgets" 
     IncludeZero="True" 
    IsFlipped="True" /> 
   </charts:LineChartGenerator.VerticalAxis> 
</charts:LineChartGenerator>

請注意,水平軸與日期的 XML 屬性 相關聯。垂直軸的類型為 AdaptableIncrementAxis,派生自 AxisStrategy 並定義兩個其他 屬性:

•       Increments 屬性,類型為 DoubleCollection

•       MaximumItems 屬性,類型為 int

Increments 集合的默認值是 1、2 和 5,MaximumItems 屬性的默認值是 10。 SalesByMonth 項目僅使用這些默認值。AdaptableIncrementAxis 確定刻度線之間的最佳增 量,因此坐標軸項目數不會超過 MaximumItems。按照默認設置,首先測試增量值 1、2 和 5 ,接著測試 0、20 和 50,然後是 100、200 和 500,依此類推。也將按相反方向進行測試 :測試增量 0.5、0.2、0.1 等等。

當然,您可以使用其他值填充 AdaptableIncrementAxis 的 Increments 屬性。如果您希 望增量始終是 10 的倍數,則只需使用單個值 1。1、2 和 5 的備選項 1、2.5 和 5 可能更 適合某些情況。

在坐標軸的數值不可預測,特別是在圖表包含總大小動態更改或不斷增加的數據時, AdaptableIncrementAxis(或您自己的發明中與之類似的內容)可能是最佳選擇。由於 AdaptableIncrementAxis 的 Increments 屬性的類型為 DoubleCollection,因此它不適用 於 DateTime 值。我將在本專欄的後面部分介紹一個備用的 DateTime。

SalesByMonth 項目中的 XAML 文件為這兩種產品定義了兩個 LineChartGenerator 對象 ,它允許使用復合圖表,如圖 5 中所示。

圖 5 SalesByMonth 顯示

創建復合圖表的此選項不要求在組成 LineChartLib 的類中執行任何特殊操作。此代碼的 唯一用途是生成可在 XAML 中靈活處理的集合。

為了適應所有標簽和坐標軸,整個圖表呈現在由 4 行、5 列組成的網格中,這 5 個列中 包含 5 個 ItemsControl — 其中兩個用於圖表本身中數據項的兩個集合、兩個用於左側和 右側的坐標軸比例、還有一個用於水平軸。

在 XAML 中采用彩色編碼區分這兩種產品非常簡單。但是另請注意,使用三角形和方形數 據點進一步區分這兩種產品。三角形項通過此 DataTemplate 呈現:

<DataTemplate>
   <Path Fill="Blue"
      Data="M 0 -4 L 4 4 -4 4Z">
     <Path.RenderTransform>
       <TranslateTransform X="{Binding Point.X}"
                 Y="{Binding Point.Y}" />
     </Path.RenderTransform>
   </Path>
</DataTemplate>

在實際的例子中,您可能使用實際與這兩種產品相關聯的形狀或甚至使用更小的位圖。

在此示例中,連接點的線不是標准 Polyline 元素,而是名為 CanonicalSpline 的自定 義 Shape 派生類。(規范樣條,也稱為重要樣條,是 Windows 窗體的一部分,但不屬於 WPF。曲線連接每兩個點,該曲線在算法上取決於這兩個點四周的其他兩個點。)也可以寫入 其他自定義類以達到此目的,也許是在點上執行最小的方塊插值並顯示結果的類。

LineChartChartGenerator 的 HorizontalAxis.AxisItems 屬性是一個 ObservableCollection,類型為 DateTime,這就意味著可使用 Binding 類的 StringFormat 功能和標准的日期/時間格式字符串對這些項進行格式化。

水平軸的 DataTemplate 使用“MMMM”格式字符串顯示整個月份名稱:

<DataTemplate>
   <StackPanel HorizontalAlignment="Left">
     <Line Y2="10" Stroke="Black" />
     <TextBlock Text="{Binding Item, StringFormat=MMMM}"
           RenderTransform="1 0 0 1 -4 -4">
       <TextBlock.LayoutTransform>
         <RotateTransform Angle="45" />
       </TextBlock.LayoutTransform>
     </TextBlock>

     <StackPanel.RenderTransform>
       <TranslateTransform X="{Binding Offset}" />
     </StackPanel.RenderTransform>
   </StackPanel>
</DataTemplate>

日期和時間

由於在折線圖的水平軸上使用 DateTime 對象很常見,因此有必要花費精力對專門用於處 理這些對象的 AxisStrategy 進行編碼。某些折線圖積累數據(例如,股票價格或環境讀數 ),或許幾乎每小時添加一個新項,最好使自我適應的 AxisStrategy 取決於已繪制成圖表 的項目中 DateTime 值的范圍。

我使用 calledAdaptableDateTimeAxis 類進行嘗試,該類的目的是適應從幾秒到幾年的 大范圍時間內的 DateTime 數據。

AdaptableDateTimeAxis 包含 1 個 MaximumItems 屬性(默認設置為 10)和 6 個集合 ,名稱分別為 SecondIncrements、MinuteIncrements、HourIncrements、DayIncrements、 MonthIncrements 和 YearIncrements。此類系統地嘗試找出刻度點之間的增量,以便項目數 不會超過 MaximumItems。按照默認設置,AdaptableDateTimeAxis 將測試增量值 1 秒、2 秒、5 秒、15 秒和 30 秒,接著是 1 分鐘、2 分鐘、5 分鐘、15 分鐘和 30 分鐘,然後是 1 小時、2 小時、4 小時、6 小時和 12 小時、1 天、2 天、5 天和 10 天以及 1 個月、2 個月、4 個月和 6 個月。達到年後,它將嘗試測試增量值 1 年、2 年和 5 年,然後是 10 年、20 年和 50 年等等。

AdaptableDateTimeAxis 還定義只讀依賴關系屬性,名稱為 DateTimeInterval(也是具 有秒、分鐘、小時等成員的枚舉的名稱),這表明坐標軸增量的單位由該類確定。此屬性允 許根據增量在 XAML 中定義更改 DateTime 格式的 DataTrigger。圖 6 顯示的是執行此類格 式選擇的 DataTemplate 示例。

圖 6 TemperatureHistory 的水平軸上的 DataTemplate

<DataTemplate>
   <StackPanel HorizontalAlignment="Left">
     <Line Y2="10" Stroke="Black" />

     <TextBlock Name="txtblk"
           RenderTransform="1 0 0 1 -4 -4">
       <TextBlock.LayoutTransform>
         <RotateTransform Angle="45" />
       </TextBlock.LayoutTransform>
     </TextBlock>

     <StackPanel.RenderTransform>
       <TranslateTransform X="{Binding Offset}" />
     </StackPanel.RenderTransform>
   </StackPanel>

   <DataTemplate.Triggers>
     <DataTrigger Binding="{Binding Source={StaticResource generator},
                     Path=HorizontalAxis.
                     DateTimeInterval}"
            Value="Second">
       <Setter TargetName="txtblk" Property="Text"
          Value="{Binding Item, StringFormat=h:mm:ss d MMM yy}"  />
     </DataTrigger>
     <DataTrigger Binding="{Binding Source={StaticResource generator},
                     Path=HorizontalAxis.
                     DateTimeInterval}"
            Value="Minute">
       <Setter TargetName="txtblk" Property="Text"
           Value="{Binding Item, StringFormat=h:mm d MMM  yy}" />
     </DataTrigger>
     <DataTrigger Binding="{Binding Source={StaticResource generator},
                     Path=HorizontalAxis.
                     DateTimeInterval}"
            Value="Hour">
       <Setter TargetName="txtblk" Property="Text"
          Value="{Binding Item, StringFormat=’h tt, d MMM  yy’}" />
     </DataTrigger>
     <DataTrigger Binding="{Binding Source={StaticResource generator},
                     Path=HorizontalAxis.
                     DateTimeInterval}"
            Value="Day">
       <Setter TargetName="txtblk" Property="Text"
           Value="{Binding Item, StringFormat=d}" />
     </DataTrigger>
     <DataTrigger Binding="{Binding Source={StaticResource generator},
                     Path=HorizontalAxis.
                     DateTimeInterval}"
            Value="Month">
       <Setter TargetName="txtblk" Property="Text"
           Value="{Binding Item, StringFormat=MMM yy}" />
     </DataTrigger>
     <DataTrigger Binding="{Binding Source={StaticResource generator},
                     Path=HorizontalAxis.
                     DateTimeInterval}"
            Value="Year">
       <Setter TargetName="txtblk" Property="Text"
           Value="{Binding Item, StringFormat=MMMM}" />
     </DataTrigger>
   </DataTemplate.Triggers>
</DataTemplate>

該模板來源於 TemperatureHistory 項目,該項目訪問國家氣象服務的網站獲取紐約市中 央公園的每小時溫度讀數。圖 7 顯示的是程序運行幾小時後的 TemperatureHistory 顯示; 圖 8 顯示的是程序運行幾天後的 TemperatureHistory 顯示。

圖 7 幾小時後的 TemperatureHistory 顯示

圖 8 幾天後的 TemperatureHistory 顯示

當然,我的折線圖類並不完全靈活—例如,當前沒有獨立繪制與文本標簽沒有關聯的刻度 線的方法—但我認為它們引入了一個可行且有效的方法,為完全在 XAML 中定義折線圖畫面 提供了足夠的信息。

下載代碼示例: http://code.msdn.microsoft.com/mag201001Charts

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved