All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.flow.component.charts.Chart Maven / Gradle / Ivy

There is a newer version: 24.5.4
Show newest version
/**
 * Copyright 2000-2024 Vaadin Ltd.
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 * See {@literal } for the full
 * license.
 */
package com.vaadin.flow.component.charts;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.HasTheme;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.charts.events.ChartAddSeriesEvent;
import com.vaadin.flow.component.charts.events.ChartAfterPrintEvent;
import com.vaadin.flow.component.charts.events.ChartBeforePrintEvent;
import com.vaadin.flow.component.charts.events.ChartClickEvent;
import com.vaadin.flow.component.charts.events.ChartDrillupAllEvent;
import com.vaadin.flow.component.charts.events.ChartDrillupEvent;
import com.vaadin.flow.component.charts.events.ChartLoadEvent;
import com.vaadin.flow.component.charts.events.ChartRedrawEvent;
import com.vaadin.flow.component.charts.events.ChartSelectionEvent;
import com.vaadin.flow.component.charts.events.DrilldownEvent;
import com.vaadin.flow.component.charts.events.PointClickEvent;
import com.vaadin.flow.component.charts.events.PointLegendItemClickEvent;
import com.vaadin.flow.component.charts.events.PointMouseOutEvent;
import com.vaadin.flow.component.charts.events.PointMouseOverEvent;
import com.vaadin.flow.component.charts.events.PointRemoveEvent;
import com.vaadin.flow.component.charts.events.PointSelectEvent;
import com.vaadin.flow.component.charts.events.PointUnselectEvent;
import com.vaadin.flow.component.charts.events.PointUpdateEvent;
import com.vaadin.flow.component.charts.events.SeriesAfterAnimateEvent;
import com.vaadin.flow.component.charts.events.SeriesCheckboxClickEvent;
import com.vaadin.flow.component.charts.events.SeriesClickEvent;
import com.vaadin.flow.component.charts.events.SeriesHideEvent;
import com.vaadin.flow.component.charts.events.SeriesLegendItemClickEvent;
import com.vaadin.flow.component.charts.events.SeriesMouseOutEvent;
import com.vaadin.flow.component.charts.events.SeriesMouseOverEvent;
import com.vaadin.flow.component.charts.events.SeriesShowEvent;
import com.vaadin.flow.component.charts.events.XAxesExtremesSetEvent;
import com.vaadin.flow.component.charts.events.YAxesExtremesSetEvent;
import com.vaadin.flow.component.charts.events.internal.ConfigurationChangeListener;
import com.vaadin.flow.component.charts.model.AbstractConfigurationObject;
import com.vaadin.flow.component.charts.model.ChartType;
import com.vaadin.flow.component.charts.model.Configuration;
import com.vaadin.flow.component.charts.model.DataSeries;
import com.vaadin.flow.component.charts.model.DataSeriesItem;
import com.vaadin.flow.component.charts.model.DrilldownCallback;
import com.vaadin.flow.component.charts.model.DrilldownCallback.DrilldownDetails;
import com.vaadin.flow.component.charts.model.PlotOptionsTimeline;
import com.vaadin.flow.component.charts.model.Series;
import com.vaadin.flow.component.charts.util.ChartSerialization;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.shared.Registration;

import elemental.json.JsonObject;
import elemental.json.JsonValue;
import elemental.json.impl.JreJsonFactory;

/**
 * Vaadin Charts is a feature-rich interactive charting library for Vaadin. It
 * provides multiple different chart types for visualizing one- or
 * two-dimensional tabular data, or scatter data with free X and Y values. You
 * can configure all the chart elements with a powerful API as well as the
 * visual style using CSS. The built-in functionalities allow the user to
 * interact with the chart elements in various ways, and you can define custom
 * interaction with events.
 * 

* The Chart is a regular Vaadin component, which you can add to any Vaadin * layout. * * @author Vaadin Ltd */ @Tag("vaadin-chart") @NpmPackage(value = "@vaadin/polymer-legacy-adapter", version = "24.4.9") @JsModule("@vaadin/polymer-legacy-adapter/style-modules.js") @NpmPackage(value = "@vaadin/charts", version = "24.4.9") @JsModule("@vaadin/charts/src/vaadin-chart.js") public class Chart extends Component implements HasStyle, HasSize, HasTheme { private Configuration configuration; private Registration configurationUpdateRegistration; private transient JreJsonFactory jsonFactory = new JreJsonFactory(); private final ConfigurationChangeListener changeListener = new ProxyChangeForwarder( this); private final static List TIMELINE_NOT_SUPPORTED = Arrays.asList( ChartType.PIE, ChartType.GAUGE, ChartType.SOLIDGAUGE, ChartType.PYRAMID, ChartType.FUNNEL, ChartType.ORGANIZATION); private DrillCallbackHandler drillCallbackHandler; private DrilldownCallback drilldownCallback; /** * Creates a new chart with default configuration */ public Chart() { configuration = new Configuration(); } /** * Creates a new chart with the given type * * @param type * @see #Chart() */ public Chart(ChartType type) { this(); getConfiguration().getChart().setType(type); } static String wrapJSExpressionInTryCatchWrapper(String expression) { return String.format("const f = function(){return %s;}.bind(this);" + "return Vaadin.Flow.tryCatchWrapper(f, 'Vaadin Charts', 'vaadin-charts-flow')();", expression); } @Override protected void onAttach(AttachEvent attachEvent) { super.onAttach(attachEvent); beforeClientResponse(attachEvent.getUI(), false); } private void beforeClientResponse(UI ui, boolean resetConfiguration) { if (configurationUpdateRegistration != null) { configurationUpdateRegistration.remove(); } configurationUpdateRegistration = ui.beforeClientResponse(this, context -> { drawChart(resetConfiguration); if (configuration != null) { // Start listening to data series events once the chart // has been // drawn. configuration.addChangeListener(changeListener); } configurationUpdateRegistration = null; }); } JreJsonFactory getJsonFactory() { if (jsonFactory == null) { jsonFactory = new JreJsonFactory(); } return jsonFactory; } /** * Draws a chart using the current configuration. * * @see #drawChart(boolean) */ public void drawChart() { drawChart(false); } /** * Draws a chart using the current configuration. * *

* The chart takes the current configuration from * {@link #getConfiguration()}. *

* *

* Note that if you modify the underlying {@link Series} directly, the chart * will automatically be updated. *

* *

* Note that you don't need to call this method if {@link Configuration} is * ready before element is attached. *

* * @param resetConfiguration * defines whether the chart should be redrawn or not * @see #getConfiguration() */ public void drawChart(boolean resetConfiguration) { validateTimelineAndConfiguration(); final JsonObject configurationNode = getJsonFactory() .parse(ChartSerialization.toJSON(configuration)); getElement().callJsFunction("updateConfiguration", configurationNode, resetConfiguration); } /** * Determines if the chart is in timeline mode or in normal mode. The * following chart types do not support timeline mode: *
    *
  • ChartType.PIE
  • *
  • ChartType.GAUGE
  • *
  • ChartType.SOLIDGAUGE
  • *
  • ChartType.PYRAMID
  • *
  • ChartType.FUNNEL
  • *
  • ChartType.ORGANIZATION
  • *
* Enabling timeline mode in these unsupported chart types results in an * IllegalArgumentException *

* Note: for Timeline chart type see {@link ChartType.TIMELINE} and * {@link PlotOptionsTimeline}. * * @param timeline * true for timeline chart */ public void setTimeline(Boolean timeline) { getElement().setProperty("timeline", timeline); } private void validateTimelineAndConfiguration() { if (getElement().getProperty("timeline", false)) { final ChartType type = getConfiguration().getChart().getType(); if (TIMELINE_NOT_SUPPORTED.contains(type)) { throw new IllegalArgumentException( "Timeline is not supported for chart type '" + type + "'"); } } } /** * The series or point visibility is toggled by default if user clicks the * legend item that corresponds to the series or point. Calling * setVisibilityTogglingDisabled( true) will disable this * behavior. * * @param disabled */ public void setVisibilityTogglingDisabled(boolean disabled) { getElement().setProperty("_visibilityTogglingDisabled", disabled); } /** * @return the chart configuration that is used for this chart */ public Configuration getConfiguration() { return configuration; } /** * @param configuration * new configuration for this chart. */ public void setConfiguration(Configuration configuration) { if (this.configuration != null) { // unbound old configuration this.configuration.removeChangeListener(changeListener); } this.configuration = configuration; if (getElement().getNode().isAttached()) { getUI().ifPresent(ui -> beforeClientResponse(ui, true)); } } public DrilldownCallback getDrilldownCallback() { return drilldownCallback; } public void setDrilldownCallback(DrilldownCallback drilldownCallback) { this.drilldownCallback = drilldownCallback; updateDrillHandler(); } private void updateDrillHandler() { final boolean hasCallback = this.getDrilldownCallback() != null; if (hasCallback && this.drillCallbackHandler == null) { this.drillCallbackHandler = new DrillCallbackHandler(); this.drillCallbackHandler.register(); } else if (!hasCallback && this.drillCallbackHandler != null && this.drillCallbackHandler.canBeUnregistered()) { this.drillCallbackHandler.unregister(); this.drillCallbackHandler = null; } } /** * Adds a chart add series listener, which will be notified after a new * series is added to the chart * * @param listener */ public Registration addChartAddSeriesListener( ComponentEventListener listener) { return addListener(ChartAddSeriesEvent.class, listener); } /** * Adds a chart after print listener, which will be notified after the chart * is printed using the print menu * * @param listener */ public Registration addChartAfterPrintListener( ComponentEventListener listener) { return addListener(ChartAfterPrintEvent.class, listener); } /** * Adds a chart before print listener, which will be notified before the * chart is printed using the print menu * * @param listener */ public Registration addChartBeforePrintListener( ComponentEventListener listener) { return addListener(ChartBeforePrintEvent.class, listener); } /** * Adds chart click listener, which will be notified of clicks on the chart * area * * @param listener */ public Registration addChartClickListener( ComponentEventListener listener) { return addListener(ChartClickEvent.class, listener); } /** * Adds chart drillup listener, which will be notified of clicks on the * 'Back to previous series' button. * * @param listener */ public Registration addChartDrillupListener( ComponentEventListener listener) { return addListener(ChartDrillupEvent.class, listener); } /** * Adds chart drillupall listener, which will be notified after all the * series have been drilled up in a chart with multiple drilldown series. * * @param listener */ public Registration addChartDrillupAllListener( ComponentEventListener listener) { return addListener(ChartDrillupAllEvent.class, listener); } /** * Adds a chart load listener, which will be notified after a chart is * loaded * * @param listener */ public Registration addChartLoadListener( ComponentEventListener listener) { return addListener(ChartLoadEvent.class, listener); } /** * Adds a chart redraw listener, which will be notified after a chart is * redrawn * * @param listener */ public Registration addChartRedrawListener( ComponentEventListener listener) { return addListener(ChartRedrawEvent.class, listener); } /** * Adds checkbox click listener, which will be notified when user has * clicked a checkbox in the legend * * @param listener */ public Registration addCheckBoxClickListener( ComponentEventListener listener) { return addListener(SeriesCheckboxClickEvent.class, listener); } /** * Sets the Chart drilldown handler that's responsible for returning the * drilldown series for each drilldown callback when doing async drilldown * * @param listener * @see DataSeries#addItemWithDrilldown(DataSeriesItem) addItemWithDrilldown * to find out how to enable async drilldown */ public Registration addDrilldownListener( ComponentEventListener listener) { return addListener(DrilldownEvent.class, listener); } /** * Adds a chart selection listener * *

* Note that if a chart selection listener is set, default action for * selection is prevented. Most commonly this means that client side zoom * doesn't work and you are responsible for setting the zoom, etc in the * listener implementation. * * @param listener */ public Registration addChartSelectionListener( ComponentEventListener listener) { return addListener(ChartSelectionEvent.class, listener); } /** * Adds a series legend item click listener, which will be notified of * clicks on the legend's items corresponding to a Series *

* Note that by default, clicking on a legend item toggles the visibility of * its associated series. To disable this behavior call * setVisibilityTogglingDisabled(true) * * @param listener * @see #setVisibilityTogglingDisabled(boolean) */ public Registration addSeriesLegendItemClickListener( ComponentEventListener listener) { return addListener(SeriesLegendItemClickEvent.class, listener); } /** * Adds a point legend item click listener, which will be notified of clicks * on the legend's items corresponding to a Point *

* Note that by default, clicking on a legend item toggles the visibility of * its associated point. To disable this behavior call * setVisibilityTogglingDisabled(true) * * @param listener * @see #setVisibilityTogglingDisabled(boolean) */ public Registration addPointLegendItemClickListener( ComponentEventListener listener) { return addListener(PointLegendItemClickEvent.class, listener); } /** * Adds a series after animate listener, which will be notified after a * series is animated * * @param listener */ public Registration addSeriesAfterAnimateListener( ComponentEventListener listener) { return addListener(SeriesAfterAnimateEvent.class, listener); } /** * Adds a series click listener, which will be notified of clicks on the * series in the chart * * @param listener */ public Registration addSeriesClickListener( ComponentEventListener listener) { return addListener(SeriesClickEvent.class, listener); } /** * Adds a series hide listener, which will be notified when a series is * hidden * * @param listener */ public Registration addSeriesHideListener( ComponentEventListener listener) { return addListener(SeriesHideEvent.class, listener); } /** * Adds a point mouse out listener, which will be notified when the mouse * exits the neighborhood of a series * * @param listener */ public Registration addSeriesMouseOutListener( ComponentEventListener listener) { return addListener(SeriesMouseOutEvent.class, listener); } /** * Adds a point mouse out listener, which will be notified when the mouse * enters the neighborhood of a series * * @param listener */ public Registration addSeriesMouseOverListener( ComponentEventListener listener) { return addListener(SeriesMouseOverEvent.class, listener); } /** * Adds a series show listener, which will be notified when a series is * shown * * @param listener */ public Registration addSeriesShowListener( ComponentEventListener listener) { return addListener(SeriesShowEvent.class, listener); } /** * Adds a point click listener, which will be notified of clicks on the * points, bars or columns in the chart * * @param listener */ public Registration addPointClickListener( ComponentEventListener listener) { return addListener(PointClickEvent.class, listener); } /** * Adds a point mouse out listener, which will be notified when the mouse * exits the neighborhood of a data point * * @param listener */ public Registration addPointMouseOutListener( ComponentEventListener listener) { return addListener(PointMouseOutEvent.class, listener); } /** * Adds a point mouse over listener, which will be notified when the mouse * enters the neighborhood of a data point * * @param listener */ public Registration addPointMouseOverListener( ComponentEventListener listener) { return addListener(PointMouseOverEvent.class, listener); } /** * Adds a point remove listener, which will be notified when a data point is * removed. * * @param listener */ public Registration addPointRemoveListener( ComponentEventListener listener) { return addListener(PointRemoveEvent.class, listener); } /** * Adds a point select listener, which will be notified when a data point is * selected. * * @param listener */ public Registration addPointSelectListener( ComponentEventListener listener) { return addListener(PointSelectEvent.class, listener); } /** * Adds a point unselect listener, which will be notified when a data point * is unselected. * * @param listener */ public Registration addPointUnselectListener( ComponentEventListener listener) { return addListener(PointUnselectEvent.class, listener); } /** * Adds a point update listener, which will be notified when a data point is * updated. * * @param listener */ public Registration addPointUpdateListener( ComponentEventListener listener) { return addListener(PointUpdateEvent.class, listener); } /** * Adds a x axes extremes set listener, which will be notified when an x * axis extremes are set * * @param listener */ public Registration addXAxesExtremesSetListener( ComponentEventListener listener) { return addListener(XAxesExtremesSetEvent.class, listener); } /** * Adds a y axes extremes set listener, which will be notified when an y * axis extremes are set * * @param listener */ public Registration addYAxesExtremesSetListener( ComponentEventListener listener) { return addListener(YAxesExtremesSetEvent.class, listener); } /** * Adds theme variants to the component. * * @apiNote To use theme variants CSS styling mode needs to be enabled * ({@link #getConfiguration().getChart().setStyleMode(true)}) * * @param variants * theme variants to add */ public void addThemeVariants(ChartVariant... variants) { getThemeNames() .addAll(Stream.of(variants).map(ChartVariant::getVariantName) .collect(Collectors.toList())); } /** * Removes theme variants from the component. * * @param variants * theme variants to remove */ public void removeThemeVariants(ChartVariant... variants) { getThemeNames() .removeAll(Stream.of(variants).map(ChartVariant::getVariantName) .collect(Collectors.toList())); } /* * Handles Drilldown and Drillup events when using a callback. */ private class DrillCallbackHandler implements Serializable { private final Deque stack = new LinkedList<>(); private Registration drilldownRegistration; private Registration drillupRegistration; private void register() { drilldownRegistration = addDrilldownListener(this::onDrilldown); drillupRegistration = addChartDrillupListener(this::onDrillup); } private void unregister() { stack.clear(); drilldownRegistration.remove(); drillupRegistration.remove(); drilldownRegistration = null; drillupRegistration = null; } private void onDrilldown(DrilldownEvent details) { if (getDrilldownCallback() == null) { return; } final int seriesIndex = details.getSeriesItemIndex(); final int pointIndex = details.getItemIndex(); final Series series = resolveSeriesFor(seriesIndex); DataSeriesItem item = null; if (series instanceof DataSeries) { DataSeries dataSeries = (DataSeries) series; item = dataSeries.get(pointIndex); } final DrilldownDetails chartDrilldownEvent = new DrilldownDetails( series, item, pointIndex); final Series drilldownSeries = getDrilldownCallback() .handleDrilldown(chartDrilldownEvent); if (drilldownSeries != null) { stack.push(drilldownSeries); callClientSideAddSeriesAsDrilldown(seriesIndex, pointIndex, drilldownSeries); } } private void onDrillup(ChartDrillupEvent e) { stack.pop(); updateDrillHandler(); } private boolean canBeUnregistered() { return stack.isEmpty(); } private void callClientSideAddSeriesAsDrilldown(int seriesIndex, int pointIndex, Series drilldownSeries) { final String JS = "this.__callChartFunction($0, this.configuration.series[$1].data[$2], $3)"; getElement().executeJs(wrapJSExpressionInTryCatchWrapper(JS), "addSeriesAsDrilldown", seriesIndex, pointIndex, toJsonValue((AbstractConfigurationObject) drilldownSeries)); } private JsonValue toJsonValue(AbstractConfigurationObject series) { return getJsonFactory().parse(ChartSerialization.toJSON(series)); } private Series resolveSeriesFor(int seriesIndex) { if (stack.isEmpty()) { return getConfiguration().getSeries().get(seriesIndex); } else { return stack.peek(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy