/******************************************************************************* * Copyright (c) 2013, 2016 Red Hat Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Andrew Ferrazzutti <aferrazz@redhat.com> - initial API and implementation *******************************************************************************/ package org.eclipse.linuxtools.internal.systemtap.graphing.ui.charts; import org.eclipse.swt.widgets.Composite; import org.swtchart.Chart; import org.swtchart.IAxis; import org.swtchart.ISeries; import org.swtchart.Range; /** * @since 3.0 */ public class BarChart extends Chart { private final static int MIN_LABEL_SIZE = Messages.BarChartBuilder_LabelTrimTag.length(); private final int fontSize; private double[][] actualYSeries; private String[] fullLabels = null; private IAxis xAxis = null; private boolean updateSuspended = false; @Override public void suspendUpdate(boolean suspend) { if (updateSuspended == suspend) { return; } updateSuspended = suspend; // make sure that chart is updated if (!suspend) { updateLayout(); } } public BarChart(Composite parent, int style) { super(parent, style); fontSize = getFont().getFontData()[0].getHeight(); xAxis = getAxisSet().getXAxis(0); xAxis.enableCategory(true); xAxis.setCategorySeries(new String[]{""}); //$NON-NLS-1$ } /** * Sets the BarChart's x-axis category labels such that labels won't get * cut off if there isn't enough room to display them fully. Use this * instead of accessing the chart's x-axis and setting its category * series directly. * @param series The category series. */ public void setCategorySeries(String[] series) { xAxis.setCategorySeries(series); fullLabels = xAxis.getCategorySeries(); } /** * Return the y-value of the specified series' bar. Use this instead of referring directly * to the chart's series values with {@link #getSeriesSet()} and {@link ISeries#getYSeries()}. * @param series The index of the bar series to get data from. * @param barIndex The index of the bar to get the y-value of. * @return The y-value of the specified bar. */ public double getBarValue(int series, int barIndex) { return actualYSeries[series][barIndex]; } /** * Returns a list of the full (non-trimmed) label names of each bar. * Use this instead of accessing the x-axis' category series, which * may contain trimmed label names. * @return An array containing the names of each bar in the chart. */ public String[] getCategorySeries() { String[] copiedCategorySeries = null; if (fullLabels != null) { copiedCategorySeries = new String[fullLabels.length]; System.arraycopy(fullLabels, 0, copiedCategorySeries, 0, fullLabels.length); } return copiedCategorySeries; } @Override public void updateLayout() { if (updateSuspended) { return; } // If the x-axis and its labels are set, ensure that their contents fit the width of each label. if (fullLabels != null) { hideBars(); String[] labels = xAxis.getCategorySeries(); if (labels != null && labels.length > 0) { String[] trimmedLabels = null; trimmedLabels = fitLabels(fullLabels); // Only update labels if their trimmed contents are different than their current contents. for (int i = 0; i < fullLabels.length; i++) { if (!trimmedLabels[i].equals(labels[i])) { labels = trimmedLabels; break; } } if (labels == trimmedLabels) { // setCategorySeries triggers an unnecessary call to updateLayout, so prevent it. updateSuspended = true; xAxis.setCategorySeries(labels); updateSuspended = false; } } } super.updateLayout(); } /** * Given an array of label names, return a new set of names that have been trimmed down * in order to fit in the chart axis without getting cut off. * @param labels An array of label names that may be trimmed. * @return A new array containing label names that have been trimmed to fit in the axis display. */ private String[] fitLabels(String[] labels) { Range range = xAxis.getRange(); int maxLabelSize = (int) Math.max(getClientArea().width / (Math.max(range.upper - range.lower, 1) * fontSize), MIN_LABEL_SIZE); String[] trimlabels = new String[labels.length]; for (int i = 0; i < labels.length; i++) { if (labels[i].length() > maxLabelSize) { trimlabels[i] = labels[i].substring(0, maxLabelSize - MIN_LABEL_SIZE) .concat(Messages.BarChartBuilder_LabelTrimTag); } else { trimlabels[i] = labels[i]; } } return trimlabels; } /* * Workaround for EBZ #427019: out-of-bounds bars appear, so change their values to * keep them properly hidden. Save actual bar values elsewhere so they can be retrieved. */ private void hideBars() { Range rangeX = xAxis.getRange(); double bottomY = getAxisSet().getYAxis(0).getRange().lower; double nonNegBottomY = Math.max(0, bottomY); ISeries[] allSeries = getSeriesSet().getSeries(); actualYSeries = new double[allSeries.length][]; for (int i = 0, n = allSeries.length; i < n; i++) { double[] yseries = allSeries[i].getYSeries(); if (yseries == null) { return; } // Store the true values of the bars into a different array, so they // can be displayed later if need be. actualYSeries[i] = new double[yseries.length]; System.arraycopy(yseries, 0, actualYSeries[i], 0, yseries.length); for (int x = (int) rangeX.lower; x <= (int) rangeX.upper; x++) { if (yseries[x] < bottomY) { yseries[x] = bottomY; } } if (yseries.length - 1 > rangeX.upper) { yseries[(int) rangeX.upper + 1] = nonNegBottomY; } allSeries[i].setYSeries(yseries); } } }