示例
使用实体数据创建图表
在本示例中,我们将创建一个类似 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): <strong>
[[value]]</strong>"
fillAlphas="0.9"
lineAlpha="0.2"
title="2020"
type="COLUMN"
valueField="year2020" id="graph2020"/>
<chart:graph balloonText="GDP grow in [[category]] (2021): <strong>
[[value]]</strong>"
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 实体的 country 、year2020 和 year2021 属性来展示。 |
3 | chart:serialChart 元素包含下列元素:
|
4 | chart:categoryAxis - chart:serialChart 元素的内部元素,用于描述类目轴。
|
5 | chart:valueAxes - chart:serialChart 元素的内部元素,用于定义垂直轴。在这里,只使用了一个垂直轴,通过 chart:axis 元素描述。
|
6 | chart:graphs - chart:serialChart 元素的内部元素,包含 chart:graph 元素集合;图形是通过 chart:graph 元素进行描述。
|
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
数据容器提供数据。
使用 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 元素的内部元素,在图表中添加一个光标。光标跟随鼠标指针并且以提示窗的方式显示图表中相应点的数据。
|
4 | chart:legend - chart:serialChart 元素的内部元素,定义图例。
|
5 | chart:valueAxes - chart:serialChart 元素的内部元素,定义垂直轴元素。在这里,只使用了一个垂直轴,通过 chart:axis 元素描述。
|
6 | chart:graphs - chart:serialChart 元素的内部元素,包含 chart:graph 元素集合;图形是通过 chart:graph 元素进行描述。
|
7 | chart:categoryAxis - chart:serialChart 元素的内部元素,描述类目轴。
|
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
方法将数据作为滚动值提交给了图表。这种类型的图表展示各部分在总和中的占比。
带增量数据更新的图表
这种图表从数据容器中获取数据,并且数据会自动更新。当数据容器中添加了新数据时,图表不会完全刷新:数据点每两秒添加一次。此方法非常有用,比如,用来创建自动更新的仪表盘。
在此例中,我们将使用 Order
实体来显示新订单金额的动态。
-
我们创建
Order
实体,具有下列属性:-
date
,类型为Date
。 -
amount
,类型为BigDecimal
。 -
description
,类型为String
。
-
-
然后创建一个名为
orders-history
的新界面。 -
界面 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 setdelay
to2000
milliseconds. -
设置
repeating
和autostart
属性为true
,启用重复执行和自启动。
4 serialChart
元素绑定了数据容器。设置date
属性为类目轴。5 设置 amount
属性为值轴。 -
-
切换至
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 注入必要的依赖: timeSource
、metadata
和Order
实体的数据容器。2 在 onInit()
方法中初始化图表。我们生成一个新的Order
实例,使用随机 amount 值。3 使用 getMutableItems().add()
方法将新实例添加到集合数据容器中。4 订阅 TimerActionEvent
。用创建随机Order
实例的相同逻辑,每次触发事件时,都创建一个新的实例。 -
此时,图表已经能正常运行,但是数据容器中的实例数量会快速增加,所以我们需要限制显示的条目数量。我们在控制器中添加一些代码:
-
创建一个
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 个时,删掉最早创建的实体。
-
事件
我们将在 上面示例 中创建的界面中添加对图形条目点击事件的处理。在界面控制器中加入如下代码:
@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() 方法返回点击的条目。 |
如需查看结果,打开界面并点击图表中一列。
使用 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" +
"}");
也可以在 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>