|
|
@@ -32,17 +32,18 @@ class CurvePlot extends StatefulWidget {
|
|
|
this.yMax,
|
|
|
this.showControls = true,
|
|
|
this.equalAspect = false,
|
|
|
- this.onViewChanged,
|
|
|
+ this.xWindow,
|
|
|
+ this.yWindow,
|
|
|
});
|
|
|
|
|
|
final List<CurveSeries> series;
|
|
|
|
|
|
- /// 横向窗口变了就报一声,让调用方有机会**重新采样**。
|
|
|
+ /// 由调用方**定死**的显示窗口(给了就不自动定界、也不用下面的滑条)。
|
|
|
///
|
|
|
- /// 采样区间是有限的(函数图像只有 [-10, 10]),滑动条一拉出去曲线就断了 ——
|
|
|
- /// 用户实测反馈"调整滑动条后图像不会自动延展"。谁采的样谁负责延展,
|
|
|
- /// 所以这里只报信,不自己采。
|
|
|
- final void Function(double x0, double x1)? onViewChanged;
|
|
|
+ /// 函数绘图工具把两个范围滑动条搬到了「计算」上方:采样区间和显示窗口
|
|
|
+ /// 都由那里决定,图自己就不该再有一套滑条了(两套会互相打架)。
|
|
|
+ final (double, double)? xWindow;
|
|
|
+ final (double, double)? yWindow;
|
|
|
|
|
|
/// 纵轴的**初始**窗口(不给就按数据自动定界)。之后由滑动条接管。
|
|
|
final double? yMin;
|
|
|
@@ -74,7 +75,11 @@ class _Bounds {
|
|
|
/// 抽成纯函数是为了能直接测 —— "圆是不是圆的"靠截图看像素太难回归了。
|
|
|
/// 同跨度意味着同样的单位长度在两轴上对应同样多的像素。
|
|
|
(double, double, double, double) equalizeAspect(
|
|
|
- double x0, double x1, double y0, double y1) {
|
|
|
+ double x0,
|
|
|
+ double x1,
|
|
|
+ double y0,
|
|
|
+ double y1,
|
|
|
+) {
|
|
|
// 退化成一点(或非法值)时给个最小跨度,否则返回的还是零跨度
|
|
|
var span = math.max(x1 - x0, y1 - y0);
|
|
|
if (!span.isFinite || span <= 0) span = 1;
|
|
|
@@ -148,10 +153,10 @@ class _CurvePlotState extends State<CurvePlot> {
|
|
|
}
|
|
|
|
|
|
void _reset() => setState(() {
|
|
|
- _x0 = _x1 = null;
|
|
|
- _y0 = widget.yMin ?? _dataY?.min;
|
|
|
- _y1 = widget.yMax ?? _dataY?.max;
|
|
|
- });
|
|
|
+ _x0 = _x1 = null;
|
|
|
+ _y0 = widget.yMin ?? _dataY?.min;
|
|
|
+ _y1 = widget.yMax ?? _dataY?.max;
|
|
|
+ });
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
@@ -161,8 +166,10 @@ class _CurvePlotState extends State<CurvePlot> {
|
|
|
return const Center(child: Text('没有可绘制的有限点'));
|
|
|
}
|
|
|
|
|
|
- final x0 = _x0 ?? dx.min, x1 = _x1 ?? dx.max;
|
|
|
- final y0 = _y0 ?? dy.min, y1 = _y1 ?? dy.max;
|
|
|
+ final x0 = widget.xWindow?.$1 ?? _x0 ?? dx.min;
|
|
|
+ final x1 = widget.xWindow?.$2 ?? _x1 ?? dx.max;
|
|
|
+ final y0 = widget.yWindow?.$1 ?? _y0 ?? dy.min;
|
|
|
+ final y1 = widget.yWindow?.$2 ?? _y1 ?? dy.max;
|
|
|
|
|
|
return Column(
|
|
|
children: [
|
|
|
@@ -170,39 +177,61 @@ class _CurvePlotState extends State<CurvePlot> {
|
|
|
child: ClipRect(
|
|
|
child: CustomPaint(
|
|
|
painter: _CurvePainter(
|
|
|
- widget.series, x0, x1, y0, y1, widget.equalAspect),
|
|
|
+ widget.series,
|
|
|
+ x0,
|
|
|
+ x1,
|
|
|
+ y0,
|
|
|
+ y1,
|
|
|
+ widget.equalAspect,
|
|
|
+ ),
|
|
|
child: const SizedBox.expand(),
|
|
|
),
|
|
|
),
|
|
|
),
|
|
|
- if (widget.showControls) _controls(dx, dy, x0, x1, y0, y1),
|
|
|
+ if (widget.showControls &&
|
|
|
+ widget.xWindow == null &&
|
|
|
+ widget.yWindow == null)
|
|
|
+ _controls(dx, dy, x0, x1, y0, y1),
|
|
|
],
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget _controls(_Bounds dx, _Bounds dy,
|
|
|
- double x0, double x1, double y0, double y1) {
|
|
|
+ Widget _controls(
|
|
|
+ _Bounds dx,
|
|
|
+ _Bounds dy,
|
|
|
+ double x0,
|
|
|
+ double x1,
|
|
|
+ double y0,
|
|
|
+ double y1,
|
|
|
+ ) {
|
|
|
// 滑条可调范围比数据范围宽一倍,这样既能放大也能缩小
|
|
|
final xLo = dx.min - dx.span, xHi = dx.max + dx.span;
|
|
|
final yLo = dy.min - dy.span, yHi = dy.max + dy.span;
|
|
|
|
|
|
return Column(
|
|
|
children: [
|
|
|
- _slider('横向', xLo, xHi, x0, x1, (v) {
|
|
|
- setState(() {
|
|
|
+ _slider(
|
|
|
+ '横向',
|
|
|
+ xLo,
|
|
|
+ xHi,
|
|
|
+ x0,
|
|
|
+ x1,
|
|
|
+ (v) => setState(() {
|
|
|
_x0 = v.start;
|
|
|
_x1 = v.end;
|
|
|
- });
|
|
|
- widget.onViewChanged?.call(v.start, v.end);
|
|
|
- }),
|
|
|
- _slider('纵向', yLo, yHi, y0, y1, (v) {
|
|
|
- setState(() {
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ _slider(
|
|
|
+ '纵向',
|
|
|
+ yLo,
|
|
|
+ yHi,
|
|
|
+ y0,
|
|
|
+ y1,
|
|
|
+ (v) => setState(() {
|
|
|
_y0 = v.start;
|
|
|
_y1 = v.end;
|
|
|
- });
|
|
|
- // 纵向变化不影响 x 方向要不要补采样,但一起报出去省得调用方还要判断
|
|
|
- widget.onViewChanged?.call(x0, x1);
|
|
|
- }),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
Align(
|
|
|
alignment: Alignment.centerRight,
|
|
|
child: TextButton.icon(
|
|
|
@@ -215,8 +244,14 @@ class _CurvePlotState extends State<CurvePlot> {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget _slider(String label, double lo, double hi, double start, double end,
|
|
|
- void Function(RangeValues) onChanged) {
|
|
|
+ Widget _slider(
|
|
|
+ String label,
|
|
|
+ double lo,
|
|
|
+ double hi,
|
|
|
+ double start,
|
|
|
+ double end,
|
|
|
+ void Function(RangeValues) onChanged,
|
|
|
+ ) {
|
|
|
return Row(
|
|
|
children: [
|
|
|
SizedBox(
|
|
|
@@ -249,7 +284,13 @@ class _CurvePlotState extends State<CurvePlot> {
|
|
|
|
|
|
class _CurvePainter extends CustomPainter {
|
|
|
_CurvePainter(
|
|
|
- this.series, this.x0, this.x1, this.y0, this.y1, this.equalAspect);
|
|
|
+ this.series,
|
|
|
+ this.x0,
|
|
|
+ this.x1,
|
|
|
+ this.y0,
|
|
|
+ this.y1,
|
|
|
+ this.equalAspect,
|
|
|
+ );
|
|
|
|
|
|
final List<CurveSeries> series;
|
|
|
final double x0, x1, y0, y1;
|
|
|
@@ -336,8 +377,12 @@ class _CurvePainter extends CustomPainter {
|
|
|
if (series.length > 1) _legend(canvas, plot);
|
|
|
}
|
|
|
|
|
|
- void _grid(Canvas canvas, Rect plot, double Function(double) sx,
|
|
|
- double Function(double) sy) {
|
|
|
+ void _grid(
|
|
|
+ Canvas canvas,
|
|
|
+ Rect plot,
|
|
|
+ double Function(double) sx,
|
|
|
+ double Function(double) sy,
|
|
|
+ ) {
|
|
|
final paint = Paint()
|
|
|
..color = const Color(0xFFE8E8EE)
|
|
|
..strokeWidth = 1;
|
|
|
@@ -358,8 +403,12 @@ class _CurvePainter extends CustomPainter {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- void _axisLabels(Canvas canvas, Rect plot, double Function(double) sx,
|
|
|
- double Function(double) sy) {
|
|
|
+ void _axisLabels(
|
|
|
+ Canvas canvas,
|
|
|
+ Rect plot,
|
|
|
+ double Function(double) sx,
|
|
|
+ double Function(double) sy,
|
|
|
+ ) {
|
|
|
const c = Color(0xFF757575);
|
|
|
final stepX = _niceStep(x1 - x0);
|
|
|
for (var x = (x0 / stepX).ceil() * stepX; x <= x1; x += stepX) {
|
|
|
@@ -382,8 +431,13 @@ class _CurvePainter extends CustomPainter {
|
|
|
Rect.fromLTWH(plot.right - 108, y + 3, 14, 3),
|
|
|
Paint()..color = s.color,
|
|
|
);
|
|
|
- _text(canvas, s.label, Offset(plot.right - 88, y - 3),
|
|
|
- const Color(0xFF616161), 10);
|
|
|
+ _text(
|
|
|
+ canvas,
|
|
|
+ s.label,
|
|
|
+ Offset(plot.right - 88, y - 3),
|
|
|
+ const Color(0xFF616161),
|
|
|
+ 10,
|
|
|
+ );
|
|
|
y += 14;
|
|
|
}
|
|
|
}
|