Examples

Creating Chart with Data from Entity

In this example, we will create a chart similar to 3D Stacked Column Chart from AmCharts demos. The type of the chart will be SerialChart. This chart will retrieve data from a database, so the dataContainer attribute has to be defined.

Creating Entity

Let’s create an entity with the CountryGrowth name and the following attributes:

  • country of the String type

  • year2020 of the Double type

  • year2021 of the Double type

The source code of the CountryGrowth entity will look like this:

@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;
    }
}

This class describes a non-persistent entity. An instance of this class contains the number of the country’s GDP growth rate for 2020 and 2021 years.

Screen XML Descriptor

Create a new blank screen and replace the code in the XML descriptor with the following:

<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 The root window element contains the chart namespace to enable using charts in the screen.
2 The chart retrieves data from the countryGrowthDc data container defined in the CollectionContainer. Names and values are displayed using the country, year2020 and year2021 attributes of the CountryGrowth entity.
3 The chart:serialChart component contains the following attributes:
  • angle – defines the chart angle. Values can be from 0 to 90.

  • balloonText – defines a text for the tooltip that appears when hovering over a column. You can use the following tags: [[value]], [[title]], [[persents]], [[description]], as well as keys from the DataItem listed in the DataProvider instance, or names of the entity attributes from the data container. To use html tags you should escape them.

  • depth3D – chart thickness. When it is used in combination with the angle attribute, it helps to create a 3D effect.

  • plotAreaFillAlphas – opacity of the plot area.

  • startDuration – duration of the animation in seconds.

  • categoryField – a key from the set of pairs contained in the DataItem objects listed in the DataProvider instance. This key is used to determine the labels for the category axis.

4 chart:categoryAxis – an element of the chart:serialChart component that describes the category axis.
  • The gridPosition attribute specifies if a grid line is placed in the center of a cell or at the beginning of a cell.

5 chart:valueAxes – an element of the chart:serialChart component that defines vertical value axes. In our case, only one vertical axis is used. The axis is described by the chart:axis element.
  • The position attribute defines the position of the value axis relative to the chart.

  • Setting stackType to BOX_3D makes the chart display columns one behind the other.

6 chart:graphs – an element of the chart:serialChart component that contains a collection of chart:graph elements. The graph is described by the chart:graph element.
  • The type attribute defines the type of the graph and can be: line, column, step line, smoothed line, OHLC, and candlestick.

  • The valueField attribute defines a key from the list of pairs contained in the DataItem objects listed in the DataProvider instance; this key is used to determine the value.

  • The fillAlphas attribute defines the opacity of fill.

  • The lineAlpha attribute defines the opacity of the line (or column border). The value range is 01.

7 chart:export – an optional element that enables chart export.

Screen Controller

Open the Column3dChart screen controller and replace its content with the following code:

@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;
    }
}

The onInit method populates the countryGrowthDc data container with the data.

Result

Now let’s see how the created screen looks in the actual application.

column3d chart
Column 3D Chart

Creating Chart with Data from DataProvider

This chart retrieves data through the DataProvider created in the controller, so the dataContainer attribute is not defined.

Screen XML Descriptor

Create a new blank screen and replace the code in the XML descriptor with the following:

<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 The root window element contains the chart namespace to enable using charts in the screen.
2 The chart:serialChart component.
3 chart:chartCursor – an element of the chart:serialChart component that adds a cursor to the chart. The cursor follows the mouse pointer and shows a tooltip with the value of the corresponding point on a chart.
  • The cursorAlpha attribute defines the opacity of the cursor line.

4 chart:legend – an element of the chart:serialChart component that defines the chart legend.
  • The position attribute defines the location of the legend relative to the chart.

  • The equalWidths attribute specifies if each legend entry should be equal to the widest entry.

  • The periodValueText attribute defines the text that will be displayed in the value portion of the legend when the user is not hovering above any data point. The tags should be made out of two parts – the name of a field (value / open / close / high / low) and the value of the period you want to be show – open / close / high / low / sum / average / count.

  • The valueAlign attribute defines the alignment of the value text. Possible values are left and right.

  • The valueWidth attribute defines the width of the value text.

5 chart:valueAxes – an element of the chart:serialChart component that defines vertical value axes. In our case, only one vertical axis is used. The axis is described by the chart:axis element.
  • The position attribute defines the position of the value axis relative to the chart.

  • The title attribute defines the title of the value axis.

  • Setting stackType to REGULAR makes the chart display a rolling value. Setting this attribute to none refers to a non-rolling value.

  • The gridAlpha defines the opacity of grid lines.

6 chart:graphs – an element of the chart:serialChart component that contains a collection of chart:graph elements. The graph is described by the chart:graph element.
  • The type attribute defines the type of the graph and can be: line, column, step line, smoothed line, OHLC, and candlestick.

  • The valueField attribute defines a key from the list of pairs contained in the DataItem objects listed in the DataProvider instance; this key is used to determine the value.

  • The fillAlphas attribute defines the opacity of fill.

  • The lineAlpha attribute defines the opacity of the line (or column border). The value range is 01.

  • The hidden attribute specifies whether the graph is hidden.

7 chart:categoryAxis – an element of the chart:serialChart component that describes the category axis.
  • Setting startOnAxis to true causes drawing the chart right from the value axis. The default value for this attribute is false. In this case, there will be a small gap between the value axis and the chart.

  • The gridAlpha attribute defines the opacity of grid lines.

  • The axisColor attribute defines axis color.

8 chart:export – an optional element that enables chart export.

Screen Controller

Open the StackedareaChart screen controller and replace its content with the following code:

@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;
    }
}

The onInit method submits data to the chart as a rolling value. This type of chart shows the ratio of separate parts to their total value.

Result

Now let’s see how the created screen looks in the actual application.

stackedarea chart
Stacked Area Chart

Creating Chart with Incremental Data Update

This chart will retrieve data from a data container, and this data will be updated automatically. The chart is not refreshed completely when new data is added to the data container: data points are added on the fly every 2 seconds. This approach will be useful, for example, for creating dynamically updated dashboards.

In this example, we will show the dynamic of new orders amounts based on the Order entity.

  1. Let’s create the Order entity with the following attributes:

    • date of the Date type

    • amount of the BigDecimal type

    • description of the String type

  2. Then create a new screen and call it orders-history.

  3. Replace the code in the XML descriptor with the following:

    <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 The root window element contains the chart namespace to enable using charts in the screen.
    2 We will not load the data from the container in this example. Instead, the sample data will be generated on the fly, so you don’t need to create a loader.
    3 We will update the chart on the fly using a timer – a facet that will send HTTP requests to the server side. Set the following properties:
    • The timer id.

    • The data should be updated every 2 seconds, so set delay to 2000 milliseconds.

    • Select repeating and autostart checkboxes.

    4 The serialChart component has a data container bound to it. Set the date property for the category axis.
    5 Set the amount property for the value axis.
  4. Switch to the OrdersHistory controller. Its code looks like this:

    @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 Inject necessary dependencies: metadata, timeSource, and the data container for the Order entity.
    2 Initializes the chart in the onInit() method. We will generate a new Order instance with a random amount value.
    3 The new instance is added to the collection data container using the getMutableItems().add() method.
    4 Subscribes to TimerActionEvent. Using the same logic for creating a random Order instance, we will generate a new Order instance each time the timer event is fired.
  5. At this stage, the chart is already functional, but the data container size will increase rapidly, so we need to limit the number of items to be displayed. Let’s add some code to the controller:

    • Create a Queue of orders.

      private Queue<Order> itemsQueue = new LinkedList<>();
    • Add the following code to the end of the onTimerTick method:

      itemsQueue.add(orderHistory); (1)
      
      if (itemsQueue.size() > 10) { (2)
          ordersDc.getMutableItems().remove(0);
          System.out.println("in");
      }
      1 Each time the timer event is fired, the generated item is added to the top of the itemsQueue.
      2 When the queue size exceeds 10 items, the oldest item is excluded.

Result

All the data is sent to the browser incrementally. If you open the Chrome developer console on the Network tab, you will see that every 2 seconds, our web page sends an HTTP request to the backend, and in response to that, the backend sends a small JSON message. The JSON contains one add and one remove operation for the amount value. Thus, we do not re-send all the data.

update data
The chart shows only 10 records at a time

Using Events

Let’s consider the use of events. We will add handling of a graph item click to the screen created in the example above. Add the following code to the screen controller:

@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 Injects the chart.
2 Injects Notifications in order to show messages.
3 Adds a listener at the bottom of the onInit method.
4 A method for getting the information about the element of the chart.
5 The SerialChart component is bound to the data container, so the getEntityNN() method gets the item clicked.

To see the results, open the screen and click one of the columns.

using events
Chart that handles graph item click event

Configuration using JSON

In order to configure a chart, in addition to assigning XML attributes, you can use a custom JSON described in the AmCharts documentation.

For example, we have a 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>

This chart has some data:

@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;
    }
}

And now we can change the chart’s configuration. As an example, let’s add a title. Add the following code at the end of the onInit method:

serialChart.setNativeJson("{\n" +
        " \"titles\": [\n" +
        " {\n" +
        " \"size\": 15,\n" +
        " \"text\": \"Chart Title\"\n" +
        " }\n" +
        " ]\n" +
        "}");
json title
Serial chart with JSON title

You can also set JSON configuration in the XML descriptor:

 <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>