私は現在、 ZedGraphライブラリに基づくトレンド ソリューションを開発しており、 TeaFilesを使用して、データベースからの大量のデータをキャッシュしています。
TeaHouse ソリューションの背後にある技術の種類は正確にはわかりません。しかし、TeaFile からの膨大な量のデータから 2 つの日付の間にある一連のポイントを表示するアプローチも使用しました。
ZedGraph ライブラリには、データ ポイントFilteredPointList
の自動デシメーションを実行するオブジェクトがあります。表示する日付の範囲と、表示するポイントの最大量を選択できる方法が含まれています。通常、これはビューの実際の幅に対応します。SetBounds
(FilteredPointList
元のソース コード)は、XY データを含む double の 2 つの配列を使用します。TeaFilePointList
T を DateTime と double プロパティを含む構造体と見なして、配列を TeaFile オブジェクトで置き換えることにより、このクラスを に簡単に適合させることができます。
実装は最適ではありませんが、私はこの方法で始めました。後でこのコードを更新して、TeaFile の MemoryMappedFile 機能を含めることができます。この方法ではるかに高速になります。
public class TeaFilePointList : IPointList
{
TeaFile<point> tf;
private int _maxPts = -1;
private int _minBoundIndex = -1;
private int _maxBoundIndex = -1;
struct point
{
public TeaTime.Time x;
public double y;
}
public TeaFilePointList(DateTime[] x, double[] y)
{
tf = TeaFile<point>.Create(Path.GetRandomFileName() + ".tea");
for (var i = 0; i < x.Length; i++)
tf.Write(new point() { x = x[i], y = y[i] });
}
public void SetBounds(double min, double max, int maxPts)
{
_maxPts = maxPts;
// find the index of the start and end of the bounded range
var xmin = (DateTime)new XDate(min);
var xmax = (DateTime)new XDate(max);
int first = tf.BinarySearch(xmin, item => (DateTime)item.x);
int last = tf.BinarySearch(xmax, item => (DateTime)item.x);
// Make sure the bounded indices are legitimate
// if BinarySearch() doesn't find the value, it returns the bitwise
// complement of the index of the 1st element larger than the sought value
if (first < 0)
{
if (first == -1)
first = 0;
else
first = ~(first + 1);
}
if (last < 0)
last = ~last;
_minBoundIndex = first;
_maxBoundIndex = last;
}
public int Count
{
get
{
int arraySize = (int)tf.Count;
// Is the filter active?
if (_minBoundIndex >= 0 && _maxBoundIndex >= 0 && _maxPts > 0)
{
// get the number of points within the filter bounds
int boundSize = _maxBoundIndex - _minBoundIndex + 1;
// limit the point count to the filter bounds
if (boundSize < arraySize)
arraySize = boundSize;
// limit the point count to the declared max points
if (arraySize > _maxPts)
arraySize = _maxPts;
}
return arraySize;
}
}
public PointPair this[int index]
{
get
{
if (_minBoundIndex >= 0 && _maxBoundIndex >= 0 && _maxPts >= 0)
{
// get number of points in bounded range
int nPts = _maxBoundIndex - _minBoundIndex + 1;
if (nPts > _maxPts)
{
// if we're skipping points, then calculate the new index
index = _minBoundIndex + (int)((double)index * (double)nPts / (double)_maxPts);
}
else
{
// otherwise, index is just offset by the start of the bounded range
index += _minBoundIndex;
}
}
double xVal, yVal;
if (index >= 0 && index < tf.Count)
xVal = new XDate(tf.Items[index].x);
else
xVal = PointPair.Missing;
if (index >= 0 && index < tf.Count)
yVal = tf.Items[index].y;
else
yVal = PointPair.Missing;
return new PointPair(xVal, yVal, PointPair.Missing, null);
}
}
public object Clone()
{
throw new NotImplementedException(); // I'm lazy...
}
public void Close()
{
tf.Close();
tf.Dispose();
File.Delete(tf.Name);
}
}
最も困難な部分は、DateTime を使用してレコードをすばやく検索するために、TeaFile の BinarySearch を実装することでした。逆コンパイラを使用して Array.BinarySearch の実装を調べ、次の拡張機能を記述しました。
public static int BinarySearch<T, U>(this TeaFile<T> tf, U target, Func<T, U> indexer) where T : struct
{
var lo = 0;
var hi = (int)tf.Count - 1;
var comp = Comparer<U>.Default;
while(lo <= hi)
{
var median = lo + (hi - lo >> 1);
var num = comp.Compare(indexer(tf.Items[median]), target);
if (num == 0)
return median;
if (num < 0)
lo = median + 1;
else
hi = median - 1;
}
return ~lo;
}
ZedGraph がニーズに合わない場合でも、少なくともアイデアは得られました。FilteredPointList クラスで使用されるデシメーション アルゴリズムは非常に優れており、別の方法でニーズに合わせて調整できます。