zhaoyadi 3 年 前
コミット
7434341c05

+ 6 - 0
.run/main.run.xml

@@ -0,0 +1,6 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="main" type="FlutterRunConfigurationType" factoryName="Flutter">
+    <option name="filePath" value="$PROJECT_DIR$/example/lib/main.dart" />
+    <method v="2" />
+  </configuration>
+</component>

+ 9 - 73
example/lib/main.dart

@@ -1,4 +1,5 @@
 import 'package:flutter/material.dart';
+import 'package:luojigou_thinking_core/luojigou_thinking_core.dart';
 
 void main() {
   runApp(const MyApp());
@@ -13,15 +14,6 @@ class MyApp extends StatelessWidget {
     return MaterialApp(
       title: 'Flutter Demo',
       theme: ThemeData(
-        // This is the theme of your application.
-        //
-        // Try running your application with "flutter run". You'll see the
-        // application has a blue toolbar. Then, without quitting the app, try
-        // changing the primarySwatch below to Colors.green and then invoke
-        // "hot reload" (press "r" in the console where you ran "flutter run",
-        // or simply save your changes to "hot reload" in a Flutter IDE).
-        // Notice that the counter didn't reset back to zero; the application
-        // is not restarted.
         primarySwatch: Colors.blue,
       ),
       home: const MyHomePage(title: 'Flutter Demo Home Page'),
@@ -32,15 +24,6 @@ class MyApp extends StatelessWidget {
 class MyHomePage extends StatefulWidget {
   const MyHomePage({Key? key, required this.title}) : super(key: key);
 
-  // This widget is the home page of your application. It is stateful, meaning
-  // that it has a State object (defined below) that contains fields that affect
-  // how it looks.
-
-  // This class is the configuration for the state. It holds the values (in this
-  // case the title) provided by the parent (in this case the App widget) and
-  // used by the build method of the State. Fields in a Widget subclass are
-  // always marked "final".
-
   final String title;
 
   @override
@@ -48,68 +31,21 @@ class MyHomePage extends StatefulWidget {
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  int _counter = 0;
-
-  void _incrementCounter() {
-    setState(() {
-      // This call to setState tells the Flutter framework that something has
-      // changed in this State, which causes it to rerun the build method below
-      // so that the display can reflect the updated values. If we changed
-      // _counter without calling setState(), then the build method would not be
-      // called again, and so nothing would appear to happen.
-      _counter++;
-    });
-  }
 
   @override
   Widget build(BuildContext context) {
-    // This method is rerun every time setState is called, for instance as done
-    // by the _incrementCounter method above.
-    //
-    // The Flutter framework has been optimized to make rerunning build methods
-    // fast, so that you can just rebuild anything that needs updating rather
-    // than having to individually change instances of widgets.
     return Scaffold(
       appBar: AppBar(
-        // Here we take the value from the MyHomePage object that was created by
-        // the App.build method, and use it to set our appbar title.
-        title: Text(widget.title),
+        title: Text(widget.title)
       ),
-      body: Center(
-        // Center is a layout widget. It takes a single child and positions it
-        // in the middle of the parent.
-        child: Column(
-          // Column is also a layout widget. It takes a list of children and
-          // arranges them vertically. By default, it sizes itself to fit its
-          // children horizontally, and tries to be as tall as its parent.
-          //
-          // Invoke "debug painting" (press "p" in the console, choose the
-          // "Toggle Debug Paint" action from the Flutter Inspector in Android
-          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
-          // to see the wireframe for each widget.
-          //
-          // Column has various properties to control how it sizes itself and
-          // how it positions its children. Here we use mainAxisAlignment to
-          // center the children vertically; the main axis here is the vertical
-          // axis because Columns are vertical (the cross axis would be
-          // horizontal).
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: <Widget>[
-            const Text(
-              'You have pushed the button this many times:',
-            ),
-            Text(
-              '$_counter',
-              style: Theme.of(context).textTheme.headline4,
-            ),
-          ],
-        ),
+      backgroundColor: const Color(0xFF7293F7),
+      body: const Center(
+        child: SizedBox(
+          width: 150,
+          height: 150,
+          child: RadarChart(),
+        )
       ),
-      floatingActionButton: FloatingActionButton(
-        onPressed: _incrementCounter,
-        tooltip: 'Increment',
-        child: const Icon(Icons.add),
-      ), // This trailing comma makes auto-formatting nicer for build methods.
     );
   }
 }

+ 8 - 1
lib/luojigou_thinking_core.dart

@@ -6,4 +6,11 @@ import 'data.dart';
 import 'model.dart';
 import 'page.dart';
 import 'view.dart';
-import 'widget.dart';
+import 'widget.dart';
+
+export 'dao.dart';
+export 'data.dart';
+export 'model.dart';
+export 'page.dart';
+export 'view.dart';
+export 'widget.dart';

+ 282 - 0
lib/src/widget/radar_chart.dart

@@ -0,0 +1,282 @@
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'dart:math' as math;
+
+class DimensionInfo {
+  final int maxValue;
+  final String title;
+
+  DimensionInfo({
+    required this.maxValue,
+    required this.title,
+  });
+}
+
+class RadarChart extends LeafRenderObjectWidget {
+  const RadarChart({Key? key}) : super(key: key);
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderRadarChart();
+  }
+}
+
+class _RenderRadarChart extends RenderBox {
+  @override
+  void performLayout() {
+    size = constraints.constrain(Size.infinite);
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.constrain(Size.infinite);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    super.paint(context, offset);
+    var canvas = context.canvas;
+    ChartPainter painter = RadarPainter();
+    painter._size = size;
+    canvas.save();
+    canvas.translate(offset.dx, offset.dy);
+    int count = canvas.getSaveCount();
+    painter.paintBackground(canvas);
+    painter.paintExtraInfo(canvas);
+    painter.paintDataInfo(canvas);
+    painter.paintForeground(canvas);
+    assert(count == canvas.getSaveCount(),
+        "Canvas save() or saveLayer() call times isn't equals restore()");
+    canvas.restore();
+  }
+}
+
+abstract class ChartPainter {
+  late Size _size;
+
+  Size get size => _size;
+
+  void paintBackground(Canvas canvas);
+
+  void paintExtraInfo(Canvas canvas);
+
+  void paintDataInfo(Canvas canvas);
+
+  void paintForeground(Canvas canvas);
+
+  Path dashPath(Path path) {
+    final PathMetrics pms = path.computeMetrics();
+    const double partLength = 4;
+
+    final newPath = Path();
+
+    for (var pm in pms) {
+      final int count = pm.length ~/ partLength;
+      for (int i = 0; i < count; i += 2) {
+        newPath.addPath(
+            pm.extractPath(partLength * i, partLength * (i + 1)), Offset.zero);
+      }
+    }
+    return newPath;
+  }
+}
+
+class RadarPainter extends ChartPainter {
+  int maxDimension = 3;
+
+  double circle = 2 * math.pi;
+
+  double get maxRadius => size.shortestSide / 2;
+
+  List<String> titles = ["早期", "中期", "后期"];
+
+  // List<String> title2s = ["早期一二三", "中期四五六", "后期七八九", "晚期三六九", "初期二五八"];
+  List<String> title2s = ["早期一二三", "中期四五六", "后期七八九", "晚期"];
+  // List<String> title2s = ["早期一", "中期四五", "后期七八九"];
+  List<List<int>> data = [
+    [0, 0, 0, 0],
+    [0, 0, 0, 0],
+    [0, 0, 0, 0],
+    [0, 0, 1, 3],
+    [3, 1, 1, 1],
+  ];
+
+  List<Color> colors = [
+    Colors.orange,
+    Colors.indigo,
+    Colors.yellowAccent,
+    Colors.deepPurpleAccent,
+    Colors.pink,
+  ];
+
+  @override
+  void paintBackground(Canvas canvas) {
+    canvas.save();
+    canvas.translate(size.width / 2, size.height / 2);
+    final paint = Paint()
+      ..color = Colors.white
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = 0.5;
+
+    for (int i = 0; i < maxDimension; i++) {
+      var radius = maxRadius / maxDimension * i;
+      var path = Path()
+        ..addOval(Rect.fromCircle(center: Offset.zero, radius: radius));
+      canvas.drawPath(dashPath(path), paint);
+      // canvas.drawCircle(Offset.zero, radius, paint);
+    }
+
+    canvas.drawCircle(Offset.zero, maxRadius, paint..strokeWidth = 1);
+    canvas.restore();
+  }
+
+  @override
+  void paintDataInfo(Canvas canvas) {
+    canvas.save();
+    canvas.translate(size.width / 2, size.height / 2);
+    double strokeWidth = 0;
+
+    for (int i = 0; i < data.length; i++) {
+      var path = Path();
+      strokeWidth = 2 * (i + 1);
+      for (int j = 0; j < data[i].length; j++) {
+        var radius = circle / data[i].length * (j) - math.pi / 2;
+
+        if (j == 0) {
+          path.moveTo(
+            data[i][j] == 0
+                ? strokeWidth * math.cos(radius)
+                : maxRadius * data[i][j] / maxDimension * math.cos(radius),
+            data[i][j] == 0
+                ? strokeWidth * math.sin(radius)
+                : maxRadius * data[i][j] / maxDimension * math.sin(radius),
+          );
+        } else {
+          path.lineTo(
+            data[i][j] == 0
+                ? strokeWidth * math.cos(radius)
+                : maxRadius * data[i][j] / maxDimension * math.cos(radius),
+            data[i][j] == 0
+                ? strokeWidth * math.sin(radius)
+                : maxRadius * data[i][j] / maxDimension * math.sin(radius),
+          );
+        }
+      }
+      path.close();
+      canvas.drawPath(
+          path,
+          Paint()
+            ..color = colors[i].withOpacity(0.2 + 0.8 * ((i + 1) / data.length))
+            ..style = PaintingStyle.stroke
+            ..strokeWidth = 2);
+    }
+
+    canvas.restore();
+  }
+
+  @override
+  void paintExtraInfo(Canvas canvas) {
+    canvas.save();
+    canvas.translate(size.width / 2, size.height / 2);
+
+    for (int i = 0; i < maxDimension; i++) {
+      canvas.save();
+
+      var radius = maxRadius / maxDimension * (i + 1);
+
+      canvas.translate(
+          radius * math.cos(math.pi / 4), radius * math.sin(math.pi / 4));
+      canvas.drawCircle(Offset.zero, 4, Paint()..color = Colors.red);
+
+      TextPainter tp = TextPainter();
+      tp.textDirection = TextDirection.ltr;
+      tp.text = TextSpan(
+        text: titles[i],
+        style: const TextStyle(
+            color: Color(0xFFB9BEE6),
+            fontSize: 7,
+            height: 10 / 7,
+            backgroundColor: Colors.green),
+      );
+      tp.layout(maxWidth: double.infinity);
+
+      var tpSize = tp.size;
+
+      canvas.translate(-tpSize.width / 2, -tpSize.height / 2);
+
+      // canvas.drawRect(
+      //   Rect.fromCenter(
+      //     center: Offset.zero,
+      //     width: tpSize.width + 15,
+      //     height: tpSize.height + 15,
+      //   ),
+      //   Paint()..color = Colors.green,
+      // );
+
+      tp.paint(canvas, Offset.zero);
+
+      canvas.restore();
+    }
+    canvas.restore();
+
+    canvas.save();
+    canvas.translate(size.width / 2, size.height / 2);
+
+    for (int i = 0; i < title2s.length; i++) {
+      canvas.save();
+
+      /// 计算文字大小
+      TextPainter tp = TextPainter();
+      tp.textDirection = TextDirection.ltr;
+      tp.text = TextSpan(
+        text: title2s[i],
+        style: const TextStyle(
+            color: Color(0xFFB9BEE6),
+            fontSize: 7,
+            height: 10 / 7,
+            backgroundColor: Colors.red),
+      );
+      tp.layout(maxWidth: double.infinity);
+
+      /// 旋转角度
+      var radius = circle / title2s.length * i - math.pi / 2;
+
+      canvas.drawLine(
+        Offset.zero,
+        Offset(maxRadius * math.cos(radius), maxRadius * math.sin(radius)),
+        Paint()
+          ..color = Colors.yellow
+          ..strokeWidth = 1,
+      );
+
+      double textRadius = math.sqrt(
+        math.pow(tp.width / 2 * math.cos(radius), 2) +
+            math.pow(tp.height / 2 * math.sin(radius), 2),
+      );
+
+      canvas.translate(
+        (maxRadius + 10 + textRadius) * math.cos(radius),
+        (maxRadius + 10 + textRadius) * math.sin(radius),
+      );
+
+      // canvas.drawCircle(Offset.zero, 14, Paint()..color = Colors.blue);
+
+      var tpSize = tp.size;
+
+      canvas.translate(
+        -tpSize.width / 2,
+        -tpSize.height / 2,
+      );
+      //
+      tp.paint(canvas, Offset.zero);
+      canvas.restore();
+    }
+    canvas.restore();
+  }
+
+  @override
+  void paintForeground(Canvas canvas) {
+    // TODO: implement paintForeground
+  }
+}

+ 1 - 0
lib/widget.dart

@@ -0,0 +1 @@
+export 'src/widget/radar_chart.dart';