Flutter图表绘制插件k_chart_plus的使用

Flutter图表绘制插件k_chart_plus的使用

k_chart_plus 是一个功能强大的Flutter图表绘制插件,支持拖拽、缩放、长按、滑动等交互,并且易于使用。本文将详细介绍如何在Flutter项目中使用该插件。

特性

  • 支持拖拽、缩放、长按、滑动。
  • 提供K线图和深度图两种图表类型。
  • 支持多主题配置(暗黑模式/亮色模式)。

示例效果

Example1 Example2
Example1 Example2

安装

首先,在 pubspec.yaml 文件中添加 k_chart_plus 依赖:

dependencies:
  k_chart_plus: ^1.0.2

如果你不想支持选择多个次要状态,可以使用以下方式添加依赖:

dependencies:
  k_chart_plus:
    git:
      url: https://github.com/TrangLeQuynh/k_chart_plus
      ref: single # branch name

使用

数据计算

当你更改数据时,必须调用以下函数:

DataUtil.calculate(datas); // 可选参数:n是BOLL N日收盘价,k是BOLL参数

使用K线图

KChartWidget(
    chartStyle, // 必填项,用于样式设置
    chartColors,// 必填项,用于样式设置
    datas,// 必填项,数据必须是一个有序列表(历史=>现在)
    mBaseHeight: 360, // 图表高度(不包含成交量和次级视图)
    isLine: isLine,// 决定是否为K线或分时图
    mainState: _mainState,// 决定主视图显示的内容
    secondaryStateLi: _secondaryStateLi,// 决定次级视图显示的内容
    fixedLength: 2,// 显示的小数位数
    timeFormat: TimeFormat.YEAR_MONTH_DAY,
    onLoadMore: (bool a) {},// 当数据滚动到末尾时调用。a为true表示用户向右拉动到底部,a为false表示用户向左拉动到底部。
    maDayList: [5,10,20],// MA显示天数,此参数必须等于DataUtil.calculate中的maDayList
    volHidden: false,// 隐藏成交量
    showNowPrice: true,// 显示当前价格
    isOnDrag: (isDrag){},// true表示正在拖拽。拖拽时不加载数据。
    isTrendLine: false, // 设置为true后,可以通过长按并移动手指来使用趋势线。
    xFrontPadding: 100 // 前端填充
),

使用深度图

DepthChart(_bids, _asks, chartColors) // 注意:数据必须是一个有序列表

暗黑模式/亮色模式

你可以通过 ChartColor 来设置图表的颜色,确保根据你的主题配置灵活调整UI。

late ThemeData themeData = Theme.of(context);
late ChartColors chartColors = ChartColors(
  bgColor: themeData.colorScheme.background,
  defaultTextColor: themeData.textTheme.labelMedium?.color ?? Colors.grey,
  gridColor: themeData.dividerColor,
  hCrossColor: themeData.textTheme.bodyMedium?.color ?? Colors.white,
  vCrossColor: themeData.disabledColor.withOpacity(.1),
  crossTextColor: themeData.textTheme.bodyMedium?.color ?? Colors.white,
  selectBorderColor: themeData.textTheme.bodyMedium?.color ?? Colors.black54,
  selectFillColor: themeData.colorScheme.background,
  infoWindowTitleColor: themeData.textTheme.labelMedium?.color ?? Colors.grey,
  infoWindowNormalColor: themeData.textTheme.bodyMedium?.color ?? Colors.white,
);

应用到K线图中:

KChartWidget(
    data,
    ChartStyle(),
    ChartColors().init(), /// 自定义图表颜色
    chartTranslations: ChartTranslations(
        date: 'Date',
        open: 'Open',
        high: 'High',
        low: 'Low',
        close: 'Close',
        changeAmount: 'Change',
        change: 'Change%',
        amount: 'Amount',
        vol: 'Volume',
    ),
    mBaseHeight: 360,
    isTrendLine: false,
    mainState: mainState,
    secondaryStateLi: secondaryStates,
    fixedLength: 2,
    timeFormat: TimeFormat.YEAR_MONTH_DAY,
);

示例代码

下面是一个完整的示例代码,展示了如何在Flutter项目中使用 k_chart_plus 插件:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:k_chart_plus/k_chart_plus.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.deepPurple,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, this.title}) : super(key: key);

  final String? title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<KLineEntity>? datas;
  bool showLoading = true;
  bool _volHidden = false;
  MainState _mainState = MainState.MA;
  final List<SecondaryState> _secondaryStateLi = [];
  List<DepthEntity>? _bids, _asks;

  ChartStyle chartStyle = ChartStyle();
  ChartColors chartColors = ChartColors();

  [@override](/user/override)
  void initState() {
    super.initState();
    getData('1day');
    rootBundle.loadString('assets/depth.json').then((result) {
      final parseJson = json.decode(result);
      final tick = parseJson['tick'] as Map<String, dynamic>;
      final List<DepthEntity> bids = (tick['bids'] as List<dynamic>)
          .map<DepthEntity>((item) => DepthEntity(item[0] as double, item[1] as double))
          .toList();
      final List<DepthEntity> asks = (tick['asks'] as List<dynamic>)
          .map<DepthEntity>((item) => DepthEntity(item[0] as double, item[1] as double))
          .toList();
      initDepth(bids, asks);
    });
  }

  void initDepth(List<DepthEntity>? bids, List<DepthEntity>? asks) {
    if (bids == null || asks == null || bids.isEmpty || asks.isEmpty) return;
    _bids = [];
    _asks = [];
    double amount = 0.0;
    bids.sort((left, right) => left.price.compareTo(right.price));
    for (var item in bids.reversed) {
      amount += item.vol;
      item.vol = amount;
      _bids!.insert(0, item);
    }

    amount = 0.0;
    asks.sort((left, right) => left.price.compareTo(right.price));
    for (var item in asks) {
      amount += item.vol;
      item.vol = amount;
      _asks!.add(item);
    }
    setState(() {});
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        shrinkWrap: true,
        children: <Widget>[
          const SafeArea(bottom: false, child: SizedBox(height: 10)),
          Stack(children: <Widget>[
            KChartWidget(
              datas,
              chartStyle,
              chartColors,
              mBaseHeight: 360,
              isTrendLine: false,
              mainState: _mainState,
              volHidden: _volHidden,
              secondaryStateLi: _secondaryStateLi.toSet(),
              fixedLength: 2,
              timeFormat: TimeFormat.YEAR_MONTH_DAY,
            ),
            if (showLoading)
              Container(
                width: double.infinity,
                height: 450,
                alignment: Alignment.center,
                child: const CircularProgressIndicator(),
              ),
          ]),
          _buildTitle(context, 'VOL'),
          buildVolButton(),
          _buildTitle(context, 'Main State'),
          buildMainButtons(),
          _buildTitle(context, 'Secondary State'),
          buildSecondButtons(),
          const SizedBox(height: 30),
          if (_bids != null && _asks != null)
            Container(
              color: Colors.white,
              height: 320,
              width: double.infinity,
              child: DepthChart(
                _bids!,
                _asks!,
                chartColors,
              ),
            )
        ],
      ),
    );
  }

  Widget _buildTitle(BuildContext context, String title) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 20, 12, 15),
      child: Text(
        title,
        style: Theme.of(context).textTheme.bodyMedium?.copyWith(
              fontWeight: FontWeight.w600,
            ),
      ),
    );
  }

  Widget buildVolButton() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Align(
        alignment: Alignment.centerLeft,
        child: _buildButton(
            context: context,
            title: 'VOL',
            isActive: !_volHidden,
            onPress: () {
              _volHidden = !_volHidden;
              setState(() {});
            }),
      ),
    );
  }

  Widget buildMainButtons() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Wrap(
        alignment: WrapAlignment.start,
        spacing: 10,
        runSpacing: 10,
        children: MainState.values.map((e) {
          return _buildButton(
            context: context,
            title: e.name,
            isActive: _mainState == e,
            onPress: () => _mainState = e,
          );
        }).toList(),
      ),
    );
  }

  Widget buildSecondButtons() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Wrap(
        alignment: WrapAlignment.start,
        spacing: 10,
        runSpacing: 5,
        children: SecondaryState.values.map((e) {
          bool isActive = _secondaryStateLi.contains(e);
          return _buildButton(
            context: context,
            title: e.name,
            isActive: isActive,
            onPress: () {
              if (isActive) {
                _secondaryStateLi.remove(e);
              } else {
                _secondaryStateLi.add(e);
              }
            },
          );
        }).toList(),
      ),
    );
  }

  Widget _buildButton({
    required BuildContext context,
    required String title,
    required bool isActive,
    required Function onPress,
  }) {
    late Color? bgColor, txtColor;
    if (isActive) {
      bgColor = Theme.of(context).primaryColor.withOpacity(.15);
      txtColor = Theme.of(context).primaryColor;
    } else {
      bgColor = Colors.transparent;
      txtColor =
          Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(.75);
    }
    return InkWell(
      onTap: () {
        onPress();
        setState(() {});
      },
      child: Container(
        decoration: BoxDecoration(
          color: bgColor,
          borderRadius: BorderRadius.circular(6),
        ),
        constraints: const BoxConstraints(minWidth: 60),
        padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
        child: Text(
          title,
          style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                color: txtColor,
              ),
          textAlign: TextAlign.center,
        ),
      ),
    );
  }

  void getData(String period) {
    final Future<String> future = getChatDataFromInternet(period);
    future.then((String result) {
      solveChatData(result);
    }).catchError((_) {
      showLoading = false;
      setState(() {});
      debugPrint('### datas error $_');
    });
  }

  Future<String> getChatDataFromInternet(String? period) async {
    var url =
        'https://api.huobi.br.com/market/history/kline?period=${period ?? '1day'}&size=300&symbol=btcusdt';
    final response = await http.get(Uri.parse(url));
    if (response.statusCode == 200) {
      return response.body;
    } else {
      debugPrint('Failed getting IP address');
      throw Exception('Failed to load data');
    }
  }

  void solveChatData(String result) {
    final Map parseJson = json.decode(result) as Map<dynamic, dynamic>;
    final list = parseJson['data'] as List<dynamic>;
    datas = list
        .map((item) => KLineEntity.fromJson(item as Map<String, dynamic>))
        .toList()
        .reversed
        .toList()
        .cast<KLineEntity>();
    DataUtil.calculate(datas!);
    showLoading = false;
    setState(() {});
  }
}

更多关于Flutter图表绘制插件k_chart_plus的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter图表绘制插件k_chart_plus的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用k_chart_plus插件来绘制图表的示例代码。k_chart_plus是一个强大的Flutter图表绘制插件,支持多种类型的图表,如折线图、柱状图、饼图等。

首先,你需要在你的pubspec.yaml文件中添加k_chart_plus依赖:

dependencies:
  flutter:
    sdk: flutter
  k_chart_plus: ^最新版本号  # 请替换为实际的最新版本号

然后运行flutter pub get来安装依赖。

接下来是一个简单的示例,展示如何使用k_chart_plus绘制一个基本的折线图:

import 'package:flutter/material.dart';
import 'package:k_chart_plus/k_chart_plus.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('k_chart_plus Demo'),
        ),
        body: Center(
          child: LineChartDemo(),
        ),
      ),
    );
  }
}

class LineChartDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 数据准备
    final List<double> xData = [1, 2, 3, 4, 5];
    final List<double> yData = [2, 3, 5, 7, 11];

    // 配置图表
    final LineChartData lineChartData = LineChartData(
      linesData: [
        LineChartLineData(
          label: 'Line 1',
          data: List.generate(xData.length, (index) {
            return LineChartDataPoint(xData[index], yData[index]);
          }),
          color: Colors.blue,
          strokeWidth: 2,
        ),
      ],
      title: 'Line Chart Demo',
      description: 'This is a simple line chart example.',
      xAxisTitle: 'X Axis',
      yAxisTitle: 'Y Axis',
      grid: GridData(show: true),
      animationDuration: Duration(milliseconds: 1000),
    );

    // 构建图表
    return LineChart(lineChartData);
  }
}

在这个示例中:

  1. 数据准备:我们定义了两个列表xDatayData,分别存储X轴和Y轴的数据。
  2. 配置图表:使用LineChartData类来配置图表的各种属性,如线条数据、标题、描述、轴标题、网格线以及动画持续时间。
  3. 构建图表:使用LineChart组件来渲染配置好的图表数据。

运行这个示例,你将会看到一个简单的折线图。你可以根据需要进一步自定义图表的样式和数据。k_chart_plus插件提供了丰富的配置选项,可以满足大多数图表绘制需求。

希望这个示例对你有所帮助!如果有更多问题,欢迎继续提问。

回到顶部