/** * */ package org.gcube.portlets.user.performfishanalytics.client.viewbinder; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.gcube.portlets.user.performfishanalytics.client.DataMinerAlgorithms; import org.gcube.portlets.user.performfishanalytics.client.PerformFishAnalyticsConstant; import org.gcube.portlets.user.performfishanalytics.client.PerformFishAnalyticsServiceAsync; import org.gcube.portlets.user.performfishanalytics.client.view.LoaderIcon; import org.gcube.portlets.user.performfishanalytics.shared.KPI; import org.gcube.portlets.user.performfishanalytics.shared.OutputFile; import org.gcube.portlets.user.performfishanalytics.shared.csv.CSVFile; import org.gcube.portlets.user.performfishanalytics.shared.dataminer.DataMinerResponse; import com.github.gwtbootstrap.client.ui.Alert; import com.github.gwtbootstrap.client.ui.Button; import com.github.gwtbootstrap.client.ui.ControlGroup; import com.github.gwtbootstrap.client.ui.ListBox; import com.github.gwtbootstrap.client.ui.constants.AlertType; import com.github.gwtbootstrap.client.ui.constants.ControlGroupType; import com.github.gwtbootstrap.client.ui.constants.IconType; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.Style.TextAlign; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; /** * The Class AnalyticsPanelResult. * * @author Francesco Mangiacrapa francesco.mangiacrapa@isti.cnr.it Jan 29, 2019 */ public class DeaPanelResult extends Composite { /** * */ private static DeaPanelResultUiBinder uiBinder = GWT.create(DeaPanelResultUiBinder.class); /** * The Interface AnalyticsPanelResultUiBinder. * * @author Francesco Mangiacrapa at ISTI-CNR * (francesco.mangiacrapa@isti.cnr.it) Jan 30, 2019 */ interface DeaPanelResultUiBinder extends UiBinder { } @UiField HTMLPanel field_html_panel; @UiField VerticalPanel field_parameters_container; // @UiField // Label uib_label_focus_id; // @UiField // ControlGroup cg_focus_id_correlation; // @UiField // ListBox field_list_focus_id_correlation; @UiField ComplexPanel alert_info; @UiField ComplexPanel alert_error; @UiField ControlGroup cg_list_dea_kpi; @UiField ControlGroup cg_list_dea_input_kpi; @UiField ControlGroup cg_list_dea_output_kpi; @UiField ListBox list_dea_kpi; @UiField ListBox list_dea_input_kpi; @UiField ListBox list_dea_output_kpi; @UiField HorizontalPanel uib_vp_deanalanlysis_request_container; @UiField VerticalPanel uib_vp_deanalanlysis_algorithm; @UiField VerticalPanel uib_vp_deanalanlysis_algorithm_container; @UiField Button button_dea_analys_request; @UiField Button uib_add_to_input_kpi; @UiField Button uib_remove_from_input_kpi; @UiField Button uib_add_to_output_kpi; @UiField Button uib_remove_from_output_kpi; @UiField Button uib_reset_input_kpi; @UiField Button uib_reset_output_kpi; private enum KpiButtonType { KPI, INPUT_KPI, OUTPUT_KPI } private Map dataInputParameters; private DataMinerResponse dmResponse; private Map> kpiMapPointers = new HashMap>(); private List selectedKPIs; // private List selectedAreas; /** * Because this class has a default constructor, it can be used as a binder * template. In other words, it can be used in other *.ui.xml files as * follows: * Hello! Note that * depending on the widget that is used, it may be necessary to implement * HasHTML instead of HasText. */ public DeaPanelResult() { GWT.log("DeaPanelResult"); initWidget(uiBinder.createAndBindUi(this)); button_dea_analys_request.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { alert_error.clear(); cg_list_dea_input_kpi.setType(ControlGroupType.NONE); cg_list_dea_output_kpi.setType(ControlGroupType.NONE); if (list_dea_input_kpi.getItemCount() < 1) { showAlert("You must select at least one Input KPI", AlertType.ERROR, true, alert_error); cg_list_dea_input_kpi.setType(ControlGroupType.ERROR); return; } if (list_dea_output_kpi.getItemCount() < 1) { showAlert("You must select at least one Output KPI", AlertType.ERROR, true, alert_error); cg_list_dea_output_kpi.setType(ControlGroupType.ERROR); return; } List inputKPINames = new ArrayList(); for (int i = 0; i < list_dea_input_kpi.getItemCount(); i++) { inputKPINames.add(list_dea_input_kpi.getItemText(i)); } List outputKPINames = new ArrayList(); for (int i = 0; i < list_dea_output_kpi.getItemCount(); i++) { outputKPINames.add(list_dea_output_kpi.getItemText(i)); } callDeaAnalysis(inputKPINames, outputKPINames, button_dea_analys_request); } }); uib_add_to_input_kpi.setIcon(IconType.BACKWARD); uib_add_to_input_kpi.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { addListFields(KpiButtonType.INPUT_KPI); } }); uib_remove_from_input_kpi.setIcon(IconType.FORWARD); uib_remove_from_input_kpi.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { removeListFields(KpiButtonType.INPUT_KPI); } }); uib_add_to_output_kpi.setIcon(IconType.FORWARD); uib_add_to_output_kpi.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { addListFields(KpiButtonType.OUTPUT_KPI); } }); uib_remove_from_output_kpi.setIcon(IconType.BACKWARD); uib_remove_from_output_kpi.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { removeListFields(KpiButtonType.OUTPUT_KPI); } }); uib_reset_input_kpi.setIcon(IconType.REMOVE_CIRCLE); uib_reset_input_kpi.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { resetListFields(KpiButtonType.INPUT_KPI); } }); uib_reset_output_kpi.setIcon(IconType.REMOVE_CIRCLE); uib_reset_output_kpi.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { resetListFields(KpiButtonType.OUTPUT_KPI); } }); validateKPIList(); } private void validateKPIList() { if (list_dea_output_kpi.getItemCount() > 0 && list_dea_input_kpi.getItemCount() > 0) { alert_info.clear(); alert_error.clear(); cg_list_dea_input_kpi.setType(ControlGroupType.NONE); cg_list_dea_output_kpi.setType(ControlGroupType.NONE); button_dea_analys_request.setEnabled(true); } else { alert_info.clear(); button_dea_analys_request.setEnabled(false); showAlert("Please select Input and Output KPI", AlertType.INFO, true, alert_info); } } /** * Add to Input or Output KPI selected * * @param kpiButtonType * kpi button type * */ private void addListFields(KpiButtonType kpiButtonType) { List notSelected = null; switch (kpiButtonType) { case INPUT_KPI: notSelected = new ArrayList(); for (int i = 0; i < list_dea_kpi.getItemCount(); i++) { if (list_dea_kpi.isItemSelected(i)) { list_dea_input_kpi.addItem(list_dea_kpi.getItemText(i)); } else { notSelected.add(list_dea_kpi.getItemText(i)); } } list_dea_kpi.clear(); for (int i = 0; i < notSelected.size(); i++) { list_dea_kpi.addItem(notSelected.get(i)); } break; case KPI: break; case OUTPUT_KPI: notSelected = new ArrayList(); for (int i = 0; i < list_dea_kpi.getItemCount(); i++) { if (list_dea_kpi.isItemSelected(i)) { list_dea_output_kpi.addItem(list_dea_kpi.getItemText(i)); } else { notSelected.add(list_dea_kpi.getItemText(i)); } } list_dea_kpi.clear(); for (int i = 0; i < notSelected.size(); i++) { list_dea_kpi.addItem(notSelected.get(i)); } break; default: break; } validateKPIList(); } /** * Remove from Input or Output KPI selected * * @param kpiButtonType * kpi button type */ private void removeListFields(KpiButtonType kpiButtonType) { List notSelected = null; switch (kpiButtonType) { case INPUT_KPI: notSelected = new ArrayList(); for (int i = 0; i < list_dea_input_kpi.getItemCount(); i++) { if (list_dea_input_kpi.isItemSelected(i)) { list_dea_kpi.addItem(list_dea_input_kpi.getItemText(i)); } else { notSelected.add(list_dea_input_kpi.getItemText(i)); } } list_dea_input_kpi.clear(); for (int i = 0; i < notSelected.size(); i++) { list_dea_input_kpi.addItem(notSelected.get(i)); } break; case KPI: break; case OUTPUT_KPI: notSelected = new ArrayList(); for (int i = 0; i < list_dea_output_kpi.getItemCount(); i++) { if (list_dea_output_kpi.isItemSelected(i)) { list_dea_kpi.addItem(list_dea_output_kpi.getItemText(i)); } else { notSelected.add(list_dea_output_kpi.getItemText(i)); } } list_dea_output_kpi.clear(); for (int i = 0; i < notSelected.size(); i++) { list_dea_output_kpi.addItem(notSelected.get(i)); } break; default: break; } validateKPIList(); } /** * Reset Input or Output KPI selected * * @param kpiButtonType * kpi button type */ private void resetListFields(KpiButtonType kpiButtonType) { switch (kpiButtonType) { case INPUT_KPI: for (int i = 0; i < list_dea_input_kpi.getItemCount(); i++) { list_dea_kpi.addItem(list_dea_input_kpi.getItemText(i)); } list_dea_input_kpi.clear(); break; case KPI: for (int i = 0; i < list_dea_input_kpi.getItemCount(); i++) { list_dea_kpi.addItem(list_dea_input_kpi.getItemText(i)); } list_dea_input_kpi.clear(); for (int i = 0; i < list_dea_output_kpi.getItemCount(); i++) { list_dea_kpi.addItem(list_dea_output_kpi.getItemText(i)); } list_dea_output_kpi.clear(); break; case OUTPUT_KPI: for (int i = 0; i < list_dea_output_kpi.getItemCount(); i++) { list_dea_kpi.addItem(list_dea_output_kpi.getItemText(i)); } list_dea_output_kpi.clear(); break; default: break; } validateKPIList(); } /** * Gets the data miner response. * * @return the data miner response */ public DataMinerResponse getDataMinerResponse() { return dmResponse; } /** * Gets the data input parameters. * * @return the data input parameters */ public Map getDataInputParameters() { return dataInputParameters; } /** * Show alert. * * @param error * the error * @param type * the type * @param closable * the closable * @param panel * the panel */ private void showAlert(String error, AlertType type, boolean closable, ComplexPanel panel) { Alert alert = new Alert(error); alert.setType(type); alert.setClose(closable); alert.getElement().getStyle().setMargin(10, Unit.PX); panel.add(alert); } /** * Adds the selected kp is. * * @param selectedKPIs * the selected kp is */ public void addSelectedKPIs(List selectedKPIs) { this.selectedKPIs = selectedKPIs; } /** * Adds the selected areas. * * @param listAreas * the list areas */ /* * public void addSelectedAreas(List listAreas) { * * this.selectedAreas = listAreas; * * } */ /** * Gets the KPI for name. * * @param name * the name * @return the KPI for name */ public KPI getKPIForName(String name) { GWT.log("Searching KPI name: " + name); KPI foundKPI = null; String purgedName = name.trim(); for (KPI kpi : selectedKPIs) { String purgedKPIName = kpi.getName().trim(); if (purgedKPIName.compareToIgnoreCase(purgedName) == 0) { foundKPI = kpi; break; } } GWT.log("FOUND KPI: " + foundKPI); return foundKPI; } /** * Adds the parameters. * * @param keyToGet * the key to get * @param parameters * the parameters * @param toShowBatchTypeValue * label to show batch type value */ public void addParameters(String keyToGet, Map> parameters, String toShowBatchTypeValue) { final FlexTable flexTable = new FlexTable(); flexTable.setStyleName("colgrouptable"); try { List dataInputs = parameters.get(keyToGet); if (dataInputs == null || dataInputs.isEmpty()) return; dataInputParameters = new HashMap(); String theDataInputs = dataInputs.get(0); String[] splittedParams = theDataInputs.split(";"); for (String splitParam : splittedParams) { try { String[] keyvalue = splitParam.split("="); dataInputParameters.put(keyvalue[0], keyvalue[1]); } catch (Exception e) { } } flexTable.setWidget(0, 0, new HTML("Batch Type:")); flexTable.setWidget(0, 1, new HTML(toShowBatchTypeValue)); flexTable.setWidget(1, 0, new HTML("Level:")); flexTable.setWidget(1, 1, new HTML(dataInputParameters.get(PerformFishAnalyticsConstant.DM_SCALEP_PARAM))); // flexTable.setWidget(2, 0, new HTML("Batch ID:")); // flexTable.setWidget(2, 1, new // HTML(dataInputParameters.get(PerformFishAnalyticsConstant.DM_FOCUS_PARAM))); String KPINames = ""; for (KPI kpi : selectedKPIs) { KPINames += kpi.getName() + ", "; } KPINames = KPINames.substring(0, KPINames.length() - 2); flexTable.setWidget(2, 0, new HTML("KPI:")); flexTable.setWidget(2, 1, new HTML(KPINames)); fillDeaListBoxes(); } catch (Exception e) { GWT.log("Error in addParameters for DeaResulPanel: " + e.getLocalizedMessage(), e); } field_parameters_container.add(flexTable); } /** * Fill dea list boxes. */ private void fillDeaListBoxes() { list_dea_kpi.clear(); for (KPI kpi : selectedKPIs) { list_dea_kpi.addItem(kpi.getName()); } list_dea_input_kpi.clear(); list_dea_output_kpi.clear(); } /** * Gets the KPI for indexes. * * @param rowIndex * the row index * @param columnIndex * the column index * @return the KPI for indexes */ public List getKPIForIndexes(int rowIndex, int columnIndex) { String key = generateKey(rowIndex, columnIndex); return kpiMapPointers.get(key); } /** * Generate key. * * @param rowIndex * the row index * @param columnIndex * the column index * @return the string */ private String generateKey(int rowIndex, int columnIndex) { return rowIndex + "-" + columnIndex; } /** * Call dea analysis. * * @param inputKPINames * the input kpi names * @param outputKPINames * the output kpi names * @param button * the button */ private void callDeaAnalysis(List inputKPINames, List outputKPINames, Button button) { HorizontalPanel hp = new HorizontalPanel(); hp.getElement().addClassName("ext-horizontal-panel-without-margin"); VerticalPanel deaAnalysis = new VerticalPanel(); hp.add(deaAnalysis); uib_vp_deanalanlysis_algorithm_container.add(hp); // TODO final FlexTable flexTable = new FlexTable(); flexTable.setStyleName("colgrouptable"); StringBuilder stringInputKPINamesBuilder = new StringBuilder(); List inputKPI = new ArrayList(); for (String kpiName : inputKPINames) { inputKPI.add(getKPIForName(kpiName)); if (stringInputKPINamesBuilder.toString() == null || stringInputKPINamesBuilder.toString().isEmpty()) { stringInputKPINamesBuilder.append(kpiName); } else { stringInputKPINamesBuilder.append(","); stringInputKPINamesBuilder.append(kpiName); } } StringBuilder stringOutputKPINamesBuilder = new StringBuilder(); List outputKPI = new ArrayList(); for (String kpiName : outputKPINames) { outputKPI.add(getKPIForName(kpiName)); if (stringOutputKPINamesBuilder.toString() == null || stringOutputKPINamesBuilder.toString().isEmpty()) { stringOutputKPINamesBuilder.append(kpiName); } else { stringOutputKPINamesBuilder.append(","); stringOutputKPINamesBuilder.append(kpiName); } } ///// flexTable.setWidget(0, 0, new HTML("Selected Input KPI:")); flexTable.setWidget(0, 1, new HTML(stringInputKPINamesBuilder.toString())); flexTable.setWidget(1, 0, new HTML("Selected Output KPI:")); flexTable.setWidget(1, 1, new HTML(stringOutputKPINamesBuilder.toString())); flexTable.addStyleName("the_margin_top_bottom"); deaAnalysis.add(flexTable); ///// GWT.log("Calling Dea Analysys... with input: " + inputKPI + " and output: " + outputKPI); uib_vp_deanalanlysis_algorithm.setVisible(true); callDataMinerServiceForChart(dataInputParameters, inputKPI, outputKPI, DataMinerAlgorithms.DEA_ANALYSIS, deaAnalysis); } /** * Display output files as static entities. * * @param dmResponse * the dm response * @param chartType * the chart type * @param inputKPIs * the input kp is * @param outputKPIs * the output kp is * @param focusID * the focus id * @param container * the container * @param displayError * the display error */ private void displayOutputFilesAsStaticEntities(DataMinerResponse dmResponse, final DataMinerAlgorithms chartType, List inputKPIs, List outputKPIs, final String focusID, final Panel container, boolean displayError) { String title = displayError ? "No results " : ""; title += chartType.getTitle(); title += inputKPIs.size() > 1 ? " [Input KPIs: " : " [Input KPI: "; for (KPI kpi : inputKPIs) { title += " " + kpi.getName() + ","; } title = title.substring(0, title.length() - 1) + "]"; if (outputKPIs != null && outputKPIs.size() > 0) { title += inputKPIs.size() > 1 ? " [Output KPIs: " : " [Output KPI: "; for (KPI kpi : outputKPIs) { title += " " + kpi.getName() + ","; } title = title.substring(0, title.length() - 1) + "]"; } // title+= " Focus "+focusID; if (displayError) { Alert alert = new Alert(title); alert.setType(AlertType.ERROR); alert.setClose(false); alert.getElement().getStyle().setMargin(10, Unit.PX); container.add(alert); return; } final FlexTable resultsPanel = new FlexTable(); container.add(resultsPanel); final String toTitle = title; for (final OutputFile outputFile : dmResponse.getListOutput()) { switch (outputFile.getDataType()) { case IMAGE: PerformFishAnalyticsServiceAsync.Util.getInstance().getImageFile(outputFile, new AsyncCallback() { @Override public void onFailure(Throwable caught) { showAlert(caught.getMessage(), AlertType.ERROR, true, uib_vp_deanalanlysis_request_container); } @Override public void onSuccess(String base64Content) { String title = toTitle; ShowResult showResult = new ShowResult(title, TextAlign.LEFT); showResult.showImage(base64Content); showOrderedResults(resultsPanel, outputFile.getName(), showResult); } }); break; case CSV: PerformFishAnalyticsServiceAsync.Util.getInstance().getCSVFile(outputFile, true, new AsyncCallback() { @Override public void onFailure(Throwable caught) { showAlert(caught.getMessage(), AlertType.ERROR, true, uib_vp_deanalanlysis_request_container); } @Override public void onSuccess(CSVFile result) { GWT.log("Displaying: " + result); String cssTableStyle = "simpletable"; String title = toTitle; ShowResult showResult = new ShowResult(title, TextAlign.LEFT); showResult.showCSVFile(result, cssTableStyle); showOrderedResults(resultsPanel, outputFile.getName(), showResult); } }); break; default: break; } } } private void showOrderedResults(FlexTable flexTable, String outputName, ShowResult showResult) { if (outputName == null || outputName.isEmpty()) { flexTable.setWidget(5, 0, showResult); } else { if (outputName.contains("dea")) { flexTable.setWidget(0, 0, showResult); } else { if (outputName.contains("efficiency_aggregated")) { flexTable.setWidget(1, 0, showResult); } else { if (outputName.contains("efficiency_legend")) { flexTable.setWidget(2, 0, showResult); } else { if (outputName.contains("efficiency_per_id")) { flexTable.setWidget(3, 0, showResult); } else { flexTable.setWidget(4, 0, showResult); } } } } } } /** * Call data miner service for chart. * * @param dataInputParameters * the data input parameters * @param inputKPI * the input kpi * @param outputKPI * the output kpi * @param chartType * the chart type * @param focusID * the focus id * @param panel * the panel */ private void callDataMinerServiceForChart(Map dataInputParameters, final List inputKPI, final List outputKPI, final DataMinerAlgorithms chartType, final ComplexPanel panel) { GWT.log("Call DM wiht Selected KPI: " + inputKPI); StringBuilder dataInputsFormatter = new StringBuilder(); String scalePValue = dataInputParameters.get(PerformFishAnalyticsConstant.DM_SCALEP_PARAM); dataInputsFormatter.append(PerformFishAnalyticsConstant.DM_SCALEP_PARAM + "=" + scalePValue + ";"); dataInputsFormatter.append(PerformFishAnalyticsConstant.DM_BATCHTYPE_PARAM + "=" + dataInputParameters.get(PerformFishAnalyticsConstant.DM_BATCHTYPE_PARAM) + ";"); dataInputsFormatter.append(PerformFishAnalyticsConstant.DM_CHARTTYPE_PARAM + "=" + chartType + ";"); dataInputsFormatter.append(PerformFishAnalyticsConstant.DM_FARMFILE_PARAM + "=" + dataInputParameters.get(PerformFishAnalyticsConstant.DM_FARMFILE_PARAM) + ";"); if (inputKPI != null && inputKPI.size() > 0) { String kpiCodes = ""; for (KPI kpi : inputKPI) { kpiCodes += kpi.getCode() + "|"; } // remove last | kpiCodes = kpiCodes.substring(0, kpiCodes.length() - 1); GWT.log("Input KPICodes: " + kpiCodes); // ADDING KPIs code dataInputsFormatter.append(PerformFishAnalyticsConstant.DM_INPUT_KPI_PARAM + "=" + kpiCodes + ";"); } if (outputKPI != null && outputKPI.size() > 0) { String kpiCodes = ""; for (KPI kpi : outputKPI) { kpiCodes += kpi.getCode() + "|"; } // remove last | kpiCodes = kpiCodes.substring(0, kpiCodes.length() - 1); GWT.log("Output KPICodes: " + kpiCodes); // ADDING KPIs code dataInputsFormatter.append(PerformFishAnalyticsConstant.DM_OUTPUT_KPI_PARAM + "=" + kpiCodes + ";"); } final String focusID = dataInputParameters.get(PerformFishAnalyticsConstant.DM_FOCUS_PARAM); dataInputsFormatter.append(PerformFishAnalyticsConstant.DM_FOCUS_PARAM + "=" + focusID + ";"); String dataInParameters = dataInputsFormatter.toString(); GWT.log("Calling DM service with client input parameters: " + dataInParameters); Map> mapParameters = new HashMap>(); mapParameters.put(PerformFishAnalyticsConstant.DATA_INPUTS, Arrays.asList(dataInParameters)); final LoaderIcon loaderIcon = new LoaderIcon( "Submitting request to " + DataMinerAlgorithms.DEA_ANALYSIS.getTitle() + " Analysis..."); loaderIcon.getElement().getStyle().setMarginTop(10, Unit.PX); loaderIcon.setVisible(true); panel.setVisible(true); panel.add(loaderIcon); Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { loaderIcon.setFocus(true); } }); PerformFishAnalyticsServiceAsync.Util.getInstance().callingDataMinerPerformFishAnalysis(mapParameters, new AsyncCallback() { @Override public void onSuccess(DataMinerResponse dmResponse) { loaderIcon.setVisible(false); // field_unary_algorithm.setVisible(true); GWT.log("I'm displaying: " + dmResponse); displayOutputFilesAsStaticEntities(dmResponse, chartType, inputKPI, outputKPI, focusID, panel, false); } @Override public void onFailure(Throwable caught) { loaderIcon.setVisible(false); displayOutputFilesAsStaticEntities(dmResponse, chartType, inputKPI, outputKPI, focusID, panel, true); } }); } }