示例

使用实体数据创建图表

在本示例中,我们将创建一个类似 AmCharts 示例中 3D Stacked Column Chart 的图表。图表类型为 SerialChart。这个图将从数据库获取数据,所以必须要定义 dataContainer 属性。

创建实体

我们创建一个名为 CountryGrowth 的实体,具有下列属性:

  • country,类型是 String

  • year2020,类型是 Double

  • year2021,类型是 Double

CountryGrowth 实体的源码如下:

@JmixEntity
public class CountryGrowth {
    @JmixProperty(mandatory = true)
    @JmixId
    @JmixGeneratedValue
    protected UUID id;

    @JmixProperty(mandatory = true)
    @InstanceName
    protected String country;

    @JmixProperty(mandatory = true)
    protected Double year2020;

    @JmixProperty(mandatory = true)
    protected Double year2021;

    public CountryGrowth() {
        this.id = UUID.randomUUID();
    }

    public UUID getId() {
        return id;
    }

    public void setId(UUID id) {
        this.id = id;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return country;
    }

    public void setYear2020(Double year2020) {
        this.year2020 = year2020;
    }

    public Double getYear2020() {
        return year2020;
    }

    public void setYear2021(Double year2021) {
        this.year2021 = year2021;
    }

    public Double getYear2021() {
        return year2021;
    }
}

这个类是非持久化实体。实例中包含一个国家 2020 和 2021 年的 GDP 增长率。

界面 XML 描述

创建一个空白界面,将其 XML 描述替换为以下内容:

<window xmlns="http://jmix.io/schema/ui/window"
        xmlns:chart="http://jmix.io/schema/ui/charts"> (1)
    <data>
        <collection id="countryGrowthDc"
                    class="charts.ex1.entity.CountryGrowth"/> (2)
    </data>
    <layout>
        <chart:serialChart id="column3d"
                           angle="30"
                           categoryField="country"
                           dataContainer="countryGrowthDc"
                           depth3D="60"
                           height="100%"
                           plotAreaFillAlphas="0.1"
                           startDuration="1"
                           theme="LIGHT"
                           width="100%"> (3)
            <chart:categoryAxis gridPosition="START"/> (4)
            <chart:valueAxes>(5)
                <chart:axis position="LEFT"
                            stackType="BOX_3D"
                            title="GDP growth rate"
                            unit="%"/>
            </chart:valueAxes>
            <chart:graphs>(6)
                <chart:graph balloonText="GDP grow in [[category]] (2020): &lt;strong&gt;
                                         [[value]]&lt;/strong&gt;"
                             fillAlphas="0.9"
                             lineAlpha="0.2"
                             title="2020"
                             type="COLUMN"
                             valueField="year2020" id="graph2020"/>
                <chart:graph balloonText="GDP grow in [[category]] (2021): &lt;strong&gt;
                                         [[value]]&lt;/strong&gt;"
                             fillAlphas="09."
                             lineAlpha="0.2"
                             title="2021"
                             type="COLUMN"
                             valueField="year2021" id="graph2021"/>
            </chart:graphs>
            <chart:export/>(7)
        </chart:serialChart>
    </layout>
</window>
1 window 根元素包含 chart 命名空间,支持在界面内使用图表。
2 图表从 CollectionContainer 定义的 countryGrowthDc 数据容器获取数据。名称和数值使用 CountryGrowth 实体的 countryyear2020year2021 属性来展示。
3 chart:serialChart 元素包含下列元素:
  • angle - 定义图表的角度。值可以从 090

  • balloonText - 定义鼠标悬停至某一列时,提示框内的文字。这里可以使用以下标签:[[value]][[title]][[persents]][[description]],以及 DataProvider 实例中列举的 DataItem 的键值,还能使用数据容器中实体属性的名称。如果要使用 html 标签,则需要转义。

  • depth3D - 图表的厚度。跟 angle 属性一起使用,可以创建 3D 效果。

  • plotAreaFillAlphas - 绘图区域的透明度。

  • startDuration - 动画的持续时间,单位是秒。

  • categoryField - DataProvider 实例中列举的 DataItem 包含的键值对的键值;用来为类目轴定义标签。

4 chart:categoryAxis - chart:serialChart 元素的内部元素,用于描述类目轴。
  • gridPosition 属性指定网格线的摆放位置,在类目轴单元的中间还是起始处。

5 chart:valueAxes - chart:serialChart 元素的内部元素,用于定义垂直轴。在这里,只使用了一个垂直轴,通过 chart:axis 元素描述。
  • position 属性定义数值轴跟图表的相对位置。

  • 设置 stackTypeBOX_3D 可以让图表列柱叠放展示。

6 chart:graphs - chart:serialChart 元素的内部元素,包含 chart:graph 元素集合;图形是通过 chart:graph 元素进行描述。
  • type 属性定义图形类型:线图、列图、步进线图,平滑线图,烛台图以及蜡烛图。

  • valueField 属性定义 DataProvider 实例中列举的 DataItem 包含的键值对的键值;用来为定义数值。

  • fillAlphas 属性定义填充色的透明度。

  • lineAlpha 属性定义线(或者列边框)的透明度。值域:0 - 1

7 chart:export - 可选元素,支持图表导出。

界面控制器

打开 Column3dChart 界面控制器,然后用下面的代码替换其内容:

@UiController("sample_Column3dChart")
@UiDescriptor("column3d-chart.xml")
public class Column3dChart extends Screen {

    @Autowired
    private CollectionContainer<CountryGrowth> countryGrowthDc;

    @Subscribe
    private void onInit(InitEvent event) {
        List<CountryGrowth> items = new ArrayList<>();
        items.add(countryGrowth("USA", 3.5, 4.2));
        items.add(countryGrowth("UK", 1.7, 3.1));
        items.add(countryGrowth("Canada", 2.8, 2.9));
        items.add(countryGrowth("Japan", 2.6, 2.3));
        items.add(countryGrowth("France", 1.4, 2.1));
        items.add(countryGrowth("Brazil", 2.6, 4.9));
        items.add(countryGrowth("Russia", 6.4, 7.2));
        items.add(countryGrowth("India", 8.0, 7.1));
        items.add(countryGrowth("China", 9.9, 10.1));
        countryGrowthDc.setItems(items);
    }
    private CountryGrowth countryGrowth(String country, double year2020, double year2021) {
        CountryGrowth cg = new CountryGrowth();
        cg.setCountry(country);
        cg.setYear2020(year2020);
        cg.setYear2021(year2021);
        return cg;
    }
}

onInit 方法为 countryGrowthDc 数据容器提供数据。

结果

我们在真实应用程序中看看创建的界面。

column3d chart
3D 列图

使用 DataProvider 提供数据创建图表

这个图表通过界面控制器中创建的 DataProvider 获取数据,所以并没有定义 dataContainer 属性。

界面 XML 描述

创建一个空白界面,将其 XML 描述替换为以下内容:

<window xmlns="http://jmix.io/schema/ui/window"
        xmlns:chart="http://jmix.io/schema/ui/charts"> (1)
    <layout>
        <chart:serialChart id="chart"
                           categoryField="year"
                           height="100%"
                           marginLeft="0"
                           marginTop="10"
                           plotAreaBorderAlpha="0"
                           width="100%">(2)
            <chart:chartCursor cursorAlpha="0"/>(3)
            <chart:legend equalWidths="false"
                          periodValueText="total: [[value.sum]]"
                          position="TOP"
                          valueAlign="LEFT"
                          valueWidth="100"/>(4)
            <chart:valueAxes>(5)
                <chart:axis gridAlpha="0.07"
                            position="LEFT"
                            stackType="REGULAR"
                            title="Traffic incidents"/>
            </chart:valueAxes>
            <chart:graphs>(6)
                <chart:graph fillAlphas="0.6"
                             hidden="true"
                             lineAlpha="0.4"
                             title="Cars"
                             valueField="cars"/>
                <chart:graph fillAlphas="0.6"
                             lineAlpha="0.4"
                             title="Motorcycles"
                             valueField="motorcycles"/>
                <chart:graph fillAlphas="0.6"
                             lineAlpha="0.4"
                             title="Bicycles"
                             valueField="bicycles"/>
            </chart:graphs>
            <chart:categoryAxis axisColor="#DADADA"
                                gridAlpha="0.07"
                                startOnAxis="true">(7)
            </chart:categoryAxis>
            <chart:export/>(8)
        </chart:serialChart>
    </layout>
</window>
1 window 根元素包含 chart 命名空间,支持在界面内使用图表。
2 chart:serialChart 元素。
3 chart:chartCursor - chart:serialChart 元素的内部元素,在图表中添加一个光标。光标跟随鼠标指针并且以提示窗的方式显示图表中相应点的数据。
  • cursorAlpha 属性定义光标线的透明度。

4 chart:legend - chart:serialChart 元素的内部元素,定义图例。
  • position 属性定义图例相对于图表的位置。

  • equalWidths 属性指定是否每个图例条目要跟最宽的那个等宽。

  • periodValueText 属性定义在图例中作为值部分显示的文字,当用户的鼠标没有悬停在任何数据点时显示。这个文字标签需要由两部分构成 - 字段名称(值/开盘/收盘/最高/最低)以及该时间段需要显示的值 - 开盘/收盘/最高/最低/总和/平均/计数。

  • valueAlign 属性定义值文字的对齐方式。可能值:leftright

  • valueWidth 属性定义值文字的宽度。

5 chart:valueAxes - chart:serialChart 元素的内部元素,定义垂直轴元素。在这里,只使用了一个垂直轴,通过 chart:axis 元素描述。
  • position 属性定义数值轴跟图表的相对位置。

  • title 属性定义数值轴标题。

  • 设置 stackTypeREGULAR 可以让图表展示滚动值,将该属性设置为 none 则显示非滚动值。

  • gridAlpha 定义网格线的透明度。

6 chart:graphs - chart:serialChart 元素的内部元素,包含 chart:graph 元素集合;图形是通过 chart:graph 元素进行描述。
  • type 属性定义图形类型:线图、列图、步进线图,平滑线图,烛台图以及蜡烛图。

  • valueField 属性定义 DataProvider 实例中列举的 DataItem 包含的键值对的键值;用来为定义数值。

  • fillAlphas 属性定义填充色的透明度。

  • lineAlpha 属性定义线(或者列边框)的透明度。值域:0 - 1

  • hidden 属性指定图形是否隐藏

7 chart:categoryAxis - chart:serialChart 元素的内部元素,描述类目轴。
  • 设置 startOnAxistrue 会导致直接挨着数值轴开始绘制图表。该属性的默认值是 false,此时,在数值轴和图表之间会有小间隔。

  • gridAlpha 定义网格线的透明度。

  • axisColor 属性定义轴的颜色。

8 chart:export - 可选元素,支持图表导出。

界面控制器

打开 StackedareaChart 界面控制器,然后用下面的代码替换其内容:

@UiController("sample_StackedareaChart")
@UiDescriptor("stackedarea-chart.xml")
public class StackedareaChart extends Screen {
    @Autowired
    private SerialChart chart;

    @Subscribe
    private void onInit(InitEvent event) {
        ListDataProvider dataProvider = new ListDataProvider();
        dataProvider.addItem(transportCount(2003, 1587, 650, 121));
        dataProvider.addItem(transportCount(2004, 1567, 683, 146));
        dataProvider.addItem(transportCount(2005, 1617, 691, 138));
        dataProvider.addItem(transportCount(2006, 1630, 642, 127));
        dataProvider.addItem(transportCount(2007, 1660, 699, 105));
        dataProvider.addItem(transportCount(2008, 1683, 721, 109));
        dataProvider.addItem(transportCount(2009, 1691, 737, 112));
        dataProvider.addItem(transportCount(2010, 1298, 680, 101));
        dataProvider.addItem(transportCount(2011, 1275, 664, 97));
        dataProvider.addItem(transportCount(2012, 1246, 648, 93));
        dataProvider.addItem(transportCount(2013, 1318, 697, 111));
        dataProvider.addItem(transportCount(2014, 1213, 633, 87));
        dataProvider.addItem(transportCount(2015, 1199, 621, 79));
        dataProvider.addItem(transportCount(2016, 1110, 210, 81));
        dataProvider.addItem(transportCount(2017, 1165, 232, 75));
        dataProvider.addItem(transportCount(2018, 1145, 219, 88));
        dataProvider.addItem(transportCount(2019, 1163, 201, 82));
        dataProvider.addItem(transportCount(2020, 1180, 285, 87));
        dataProvider.addItem(transportCount(2021, 1159, 277, 71));

        chart.setDataProvider(dataProvider);
    }

    private DataItem transportCount(int year, int cars, int motorcycles, int bicycles) {
        MapDataItem item = new MapDataItem();
        item.add("year", year);
        item.add("cars", cars);
        item.add("motorcycles", motorcycles);
        item.add("bicycles", bicycles);
        return item;
    }
}

onInit 方法将数据作为滚动值提交给了图表。这种类型的图表展示各部分在总和中的占比。

结果

我们在真实应用程序中看看创建的界面。

stackedarea chart
堆积面积图

带增量数据更新的图表

这种图表从数据容器中获取数据,并且数据会自动更新。当数据容器中添加了新数据时,图表不会完全刷新:数据点每两秒添加一次。此方法非常有用,比如,用来创建自动更新的仪表盘。

在此例中,我们将使用 Order 实体来显示新订单金额的动态。

  1. 我们创建 Order 实体,具有下列属性:

    • date,类型为 Date

    • amount,类型为 BigDecimal

    • description,类型为 String

  2. 然后创建一个名为 orders-history 的新界面。

  3. 界面 XML 描述中的内容用下列代码替换:

    <window xmlns="http://jmix.io/schema/ui/window"
            xmlns:chart="http://jmix.io/schema/ui/charts">(1)
        <data>
            <collection id="ordersDc" class="charts.ex1.entity.Order"/>(2)
        </data>
        <facets>
            <timer id="updateChartTimer" delay="2000" repeating="true" autostart="true"/>(3)
        </facets>
        <layout>
            <chart:serialChart id="orderHistoryChart"
                               categoryField="date"
                               dataContainer="ordersDc"
                               width="100%">(4)
                <chart:graphs>
                    <chart:graph valueField="amount"/>(5)
                </chart:graphs>
            </chart:serialChart>
        </layout>
    </window>
    1 window 根元素包含 chart 命名空间,支持在界面内使用图表。
    2 我们不用加载器为数据容器加载数据,而是通过代码创建数据,因此,这里不需要创建加载器。
    3 我们用 timer(定时器) 更新图表,定时器中,我们往服务端发送 HTTP 请求。为定时器设置下列属性:
    • 定时器 id

    • 数据 2 秒更新一次,因此设置 delay 属性为 2000 毫秒.The data should be updated every 2 seconds, so set delay to 2000 milliseconds.

    • 设置 repeatingautostart 属性为 true,启用重复执行和自启动。

    4 serialChart 元素绑定了数据容器。设置 date 属性为类目轴。
    5 设置 amount 属性为值轴。
  4. 切换至 OrdersHistory 控制器。代码如下:

    @UiController("sample_OrdersHistory")
    @UiDescriptor("orders-history.xml")
    public class OrdersHistory extends Screen {
    
        @Autowired
        private Metadata metadata; (1)
        @Autowired
        private TimeSource timeSource;
        @Autowired
        private CollectionContainer<Order> ordersDc;
    
        private final Random random = new Random(42);
    
        @Subscribe
        private void onInit(InitEvent event) { (2)
            Order initialValue = metadata.create(Order.class);
            initialValue.setAmount(new BigDecimal(random.nextInt(1000) + 100));
            initialValue.setDate(timeSource.currentTimestamp());
    
            ordersDc.getMutableItems().add(initialValue); (3)
        }
    
        @Subscribe("updateChartTimer")
        private void onTimerTick(Timer.TimerActionEvent event) { (4)
            Order orderHistory = metadata.create(Order.class);
            orderHistory.setAmount(new BigDecimal(random.nextInt(1000) + 100));
            orderHistory.setDate(timeSource.currentTimestamp());
            ordersDc.getMutableItems().add(orderHistory);
            System.out.println("get");
        }
    }
    1 注入必要的依赖:timeSourcemetadataOrder 实体的数据容器。
    2 onInit() 方法中初始化图表。我们生成一个新的 Order 实例,使用随机 amount 值。
    3 使用 getMutableItems().add() 方法将新实例添加到集合数据容器中。
    4 订阅 TimerActionEvent。用创建随机 Order 实例的相同逻辑,每次触发事件时,都创建一个新的实例。
  5. 此时,图表已经能正常运行,但是数据容器中的实例数量会快速增加,所以我们需要限制显示的条目数量。我们在控制器中添加一些代码:

    • 创建一个 Order 的队列。

      private Queue<Order> itemsQueue = new LinkedList<>();
    • 将下列代码添加至 onTimerTick 方法的最后:

      itemsQueue.add(orderHistory); (1)
      
      if (itemsQueue.size() > 10) { (2)
          ordersDc.getMutableItems().remove(0);
          System.out.println("in");
      }
      1 每次触发定时器事件时,生成的实例都会添加到 itemsQueue 的顶部。
      2 当队列大小超过 10 个时,删掉最早创建的实体。

结果

所有数据都会以增量的方式发送到浏览器。如果在 Chrome 开发者控制台打开 Network 标签页,则会看到我们的网页每 2 秒向后端发送一个 HTTP 请求,并且后端会响应这个请求送回非常小的 JSON 消息。JSON 包含对 amount 值的一个 addremove 操作。所以,并没有重新发送所有数据。

update data
图表每次只显示 10 条记录

事件

我们将在 上面示例 中创建的界面中添加对图形条目点击事件的处理。在界面控制器中加入如下代码:

@Autowired
private SerialChart column3d; (1)

@Autowired
private Notifications notifications; (2)

@Subscribe
private void onInit(InitEvent event) {
    column3d.addGraphItemClickListener(graphItemClickEvent -> (3)
            notifications.create()
                    .withCaption(itemClickEventInfo(graphItemClickEvent))
                    .withContentMode(ContentMode.HTML)
                    .show());
    // ...
}
private String itemClickEventInfo(Chart.GraphItemClickEvent event) { (4)
    CountryGrowth countryGrowth = (CountryGrowth) event.getEntityNN(); (5)
    return String.format("GDP grow in %s (%s): %.1f%%",
            countryGrowth.getCountry(),
            event.getGraphId().substring(5),
            "graph2020".equals(event.getGraphId()) ? countryGrowth.getYear2020() : countryGrowth.getYear2021());
}
1 注入图表
2 注入 Notifications,以便显示消息。
3 onInit 方法的最后添加一个监听器。
4 获取图表元素信息的方法。
5 SerialChart 组件绑定了数据容器,因此 getEntityNN() 方法返回点击的条目。

如需查看结果,打开界面并点击图表中一列。

using events
处理图形条目点击事件的图表

使用 JSON 配置

需要配置图表,除了使用 XML 属性之外,还可以使用 AmCharts 文档 介绍的自定义 JSON。

例如,我们有 serialChart

<window xmlns="http://jmix.io/schema/ui/window"
        xmlns:chart="http://jmix.io/schema/ui/charts">
    <layout>
        <chart:serialChart id="serialChart">
            <chart:valueAxes>
                <chart:axis axisAlpha="0" position="LEFT" title="Incidents"/>
            </chart:valueAxes>
            <chart:graphs>
                <chart:graph id="g1" bullet="ROUND" type="COLUMN" valueField="value"/>
            </chart:graphs>
            <chart:categoryAxis position="TOP" title="Time" labelsEnabled="false"/>
        </chart:serialChart>
    </layout>
</window>

这个图有些数据:

@UiController("sample_SerialChartJsonTitle")
@UiDescriptor("serial-chart-json-title.xml")
public class SerialChartJsonTitle extends Screen {
    @Autowired
    private SerialChart serialChart;

    @Subscribe
    private void onInit(InitEvent event) {
        ListDataProvider serialChartDataProvider = new ListDataProvider();
        int[] serialChartData = {5, 7, 6, 9, 7, 8, 5, 6, 4, 6, 5, 7, 4, 5, 3, 4, 2, 0};

        for (int i = 0; i < serialChartData.length; i++) {
            serialChartDataProvider.addItem(graphData(serialChartData[i]));
        }

        serialChart.setDataProvider(serialChartDataProvider);
    }

    private DataItem graphData(int value) {
        MapDataItem item = new MapDataItem();
        item.add("value", value);
        return item;
    }
}

现在可以改变图表的配置。例如,给图表添加一个标题。将下列代码添加至 onInit 方法的末尾:

serialChart.setNativeJson("{\n" +
        " \"titles\": [\n" +
        " {\n" +
        " \"size\": 15,\n" +
        " \"text\": \"Chart Title\"\n" +
        " }\n" +
        " ]\n" +
        "}");
json title
JSON 标题的序列图

也可以在 XML 设置 JSON 配置:

 <chart:serialChart id="serialChart">
     <chart:nativeJson>
         <![CDATA[
{
    "titles": [
        {
            "size": 15,
            "text": "Chart Title"
        }
    ]
}
]]>
     </chart:nativeJson>
     <chart:valueAxes>
         <chart:axis axisAlpha="0" position="LEFT" title="Incidents"/>
     </chart:valueAxes>
     <chart:graphs>
         <chart:graph id="g1" bullet="ROUND" type="COLUMN" valueField="value"/>
     </chart:graphs>
     <chart:categoryAxis position="TOP" title="Time" labelsEnabled="false"/>
 </chart:serialChart>