WPF/Silverlight 的核心優勢在於 分離關注點:UI 結構用 XAML,邏輯用 Code-behind。ItemsControl + DataTemplate 是這套哲學的集大成者——任何集合資料,透過 DataTemplate 即可在標記中定義「每一筆資料長什麼樣」。
本文挑戰最複雜的 折線圖:需連接離散點、座標軸動態刻度、多種資料型別 (數值/日期時間)。目標:零代碼繪圖邏輯,純 XAML 組合出專業級折線圖。
Polyline 需要 PointCollection (所有點) — 如何跨項存取?LineChartGenerator — 純 XAML 可用的資料轉換引擎輸入:ItemsSource (業務物件集合) + Width/Height + HorizontalAxis/VerticalAxis 策略
輸出四個可繫結集合:
Points : PointCollection → 供 Polyline 繪製連線ItemPoints : ItemPointCollection (含 Item 原始物件 + Point 座標) → 供資料點 DataTemplate 定位HorizontalAxis.AxisItems : AxisItemCollection (含 Item 標籤值 + Offset 偏移) → 供 X 軸 ItemsControlVerticalAxis.AxisItems : 同理 → 供 Y 軸 ItemsControl佈局:4x4 Grid,四個 ItemsControl 分佔儲存格,共享寬高繫結確保對齊。
struct CensusDatum { int Year; int Population; }
class CensusData : ObservableCollection<CensusDatum> { ... } // 1790-2010 十年一次資料
<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>
AutoAxis:直接使用資料實際值 (適合年份)IncrementAxis:固定刻距 (5000 萬),IsFlipped="True" 讓 Y 軸由下往上增長 (符合直覺)<Grid Width="{Binding Source={StaticResource generator}, Path=Width}"
Height="{Binding Source={StaticResource generator}, Path=Height}">
<!-- 連線 -->
<Polyline Points="{Binding Source={StaticResource generator}, Path=Points}"
Stroke="Blue" />
<!-- 資料點 -->
<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>
</Grid>
關鍵細節:EllipseGeometry.Transform 抵銷 Path.RenderTransform 縮放,避免邊緣被裁切。
<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>

IConvertible + DateTime 特例DateTime 皆實作 IConvertible → 可統一轉 double 計算DateTime 無法直接 ToDouble → 用 Ticks (long) 做中介,雙向轉換精確到毫秒XmlDataProvider 來源) → 先取字串,嘗試 DateTime.TryParseExact (格式 "R","s","u","o"),再轉數值abstract class AxisStrategy : DependencyObject
{
string PropertyName { get; set; } // 對應資料屬性
double Margin { get; set; } // 極值外擴比例
bool IncludeZero { get; set; } // 強制含零
bool IsSymmetricAroundZero { get; set; } // 對稱正負極值
ObservableCollection<AxisItem> AxisItems { get; } // 輸出集合
protected abstract void CalculateAxisItems(Type propertyType, ref double min, ref double max);
}
AutoAxis:無參數,直接用資料實際極值IncrementAxis:固定 Increment,極值對齊刻距倍數AdaptableIncrementAxis:Increments (預設 1,2,5) + MaximumItems (預設 10) → 自動挑選最佳刻距,不超過最大標籤數ExplicitAxis:開發者直接提供標籤值清單
XmlDataProvider 内嵌 12 月銷售資料LineChartGenerator 分別繫結 @Date (X軸) + Widgets/Doodads (Y軸)AdaptableIncrementAxis IncludeZero="True" 自動適應雙線範圍CanonicalSpline (樣條曲線) 取代直線 PolylineStringFormat="MMMM" 顯示完整月份名AdaptableDateTimeAxis 自適應時間軸資料來源:國家氣象服務 API,每小時紐約中央公園溫度。
| 時間範圍 | 嘗試刻距序列 |
|---|---|
| 秒級 | 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 年... |