blob: d87b6a9d46e1d6943b07f2fc8db88403a7d79eb2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2018 IBM Corporation and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Red Hat Inc. - modified to handle SWTChart 0.9.0 vs 0.8.0; ongoing maintenance
*******************************************************************************/
package org.eclipse.linuxtools.internal.systemtap.graphing.ui.charts;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.linuxtools.internal.systemtap.graphing.ui.charts.listeners.ChartWithAxisMouseMoveListener;
import org.eclipse.linuxtools.internal.systemtap.graphing.ui.preferences.GraphingPreferenceConstants;
import org.eclipse.linuxtools.systemtap.graphing.core.adapters.IAdapter;
import org.eclipse.linuxtools.systemtap.graphing.ui.charts.AbstractChartBuilder;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swtchart.IAxis;
import org.eclipse.swtchart.ISeries;
import org.eclipse.swtchart.ITitle;
import org.eclipse.swtchart.LineStyle;
import org.eclipse.swtchart.Range;
/**
* A {@link AbstractChartBuilder} for building a chart with axes.
* @author Qi Liang
*/
public abstract class AbstractChartWithAxisBuilder extends AbstractChartBuilder {
private PaintListener titleBoundsPaintListener;
private double defaultMargin = 0.04;
/**
* @return The size of the chart's left margin.
* @since 3.0
*/
protected double getChartMarginXL() {
return defaultMargin;
}
/**
* @return The size of the chart's right margin.
* @since 3.0
*/
protected double getChartMarginXU() {
return defaultMargin;
}
/**
* @return The size of the chart's top margin.
* @since 3.0
*/
protected double getChartMarginYL() {
return defaultMargin;
}
/**
* @return The size of the chart's bottom margin.
* @since 3.0
*/
protected double getChartMarginYU() {
return defaultMargin;
}
protected boolean xLineGrid, yLineGrid;
/**
* @since 3.0
*/
protected int xSeriesTicks, ySeriesTicks;
/**
* Creates a chart series for this chart.
* @param i The index of the series to create.
* @return The newly created series.
*/
protected abstract ISeries createChartISeries(int i);
@Override
protected void updateProperties(PropertyChangeEvent event) {
super.updateProperties(event);
String eventName = event.getProperty();
if (eventName.equals(GraphingPreferenceConstants.P_SHOW_X_GRID_LINES)) {
xLineGrid = store.getBoolean(GraphingPreferenceConstants.P_SHOW_X_GRID_LINES);
buildXAxis();
} else if (eventName.equals(GraphingPreferenceConstants.P_SHOW_Y_GRID_LINES)) {
yLineGrid = store.getBoolean(GraphingPreferenceConstants.P_SHOW_Y_GRID_LINES);
buildYAxis();
} else if (eventName.equals(GraphingPreferenceConstants.P_X_SERIES_TICKS)) {
xSeriesTicks = store.getInt(GraphingPreferenceConstants.P_X_SERIES_TICKS);
buildXAxis();
} else if (eventName.equals(GraphingPreferenceConstants.P_Y_SERIES_TICKS)) {
ySeriesTicks = store.getInt(GraphingPreferenceConstants.P_Y_SERIES_TICKS);
buildYAxis();
}
}
/**
* Constructs a builder for a chart with axes and associates it to one data set.
* @param adapter An {@link IAdapter} for reading from the chart's data set.
* @param parent The parent {@link Composite} that will contain this chart builder.
* @param style The style of the chart to construct.
* @param title The title of the chart to construct.
*/
public AbstractChartWithAxisBuilder(IAdapter adapter, Composite parent, int style, String title) {
super(adapter, parent, style, title);
xLineGrid = store.getBoolean(GraphingPreferenceConstants.P_SHOW_X_GRID_LINES);
yLineGrid = store.getBoolean(GraphingPreferenceConstants.P_SHOW_Y_GRID_LINES);
xSeriesTicks = store.getInt(GraphingPreferenceConstants.P_X_SERIES_TICKS);
ySeriesTicks = store.getInt(GraphingPreferenceConstants.P_Y_SERIES_TICKS);
}
@Override
protected void createChart() {
super.createChart();
applyTitleBoundsListener();
chartMouseMoveListener = new ChartWithAxisMouseMoveListener(chart, chart.getPlotArea().getControl());
}
/**
* After this method is called, the chart's title will (from then on) be centered with the plot area.
* @since 3.0
*/
protected void applyTitleBoundsListener() {
ITitle title = chart.getTitle();
// Underlying SWT Chart implementation changes from the title being a Control to just
// a PaintListener. In the Control class case, we can move it's location to
// center over a PieChart, but in the latter case, we need to alter the title
// with blanks in the PaintListener and have the title paint after it
// once the title has been altered.
if (title instanceof Control) {
titleBoundsPaintListener = e -> {
Rectangle bounds = chart.getPlotArea().getBounds();
Control title1 = (Control) chart.getTitle();
Rectangle titleBounds = title1.getBounds();
title1.setLocation(new Point(bounds.x + (bounds.width - titleBounds.width) / 2, title1.getLocation().y));
};
chart.addPaintListener(titleBoundsPaintListener);
} else {
// move title paint listener to end
chart.removePaintListener((PaintListener)title);
titleBoundsPaintListener = e -> {
ITitle title1 = chart.getTitle();
Font font = title1.getFont();
Font oldFont = e.gc.getFont();
e.gc.setFont(font);
Control legend = (Control)chart.getLegend();
Rectangle legendBounds = legend.getBounds();
int adjustment = legendBounds.width - 15;
Point blankSize = e.gc.textExtent(" "); //$NON-NLS-1$
int numBlanks = ((adjustment / blankSize.x) >> 1) << 1;
String text = title1.getText().trim();
for (int i = 0; i < numBlanks; ++i) {
text += " "; //$NON-NLS-1$
}
e.gc.setFont(oldFont);
title1.setText(text);
};
chart.addPaintListener(titleBoundsPaintListener);
chart.addPaintListener((PaintListener)title);
}
}
/**
* Removes the chart's paint listener for repositioning the title, if one has been applied.
* @since 3.0
*/
protected void removeTitleBoundsListener() {
if (titleBoundsPaintListener != null) {
chart.removePaintListener(titleBoundsPaintListener);
titleBoundsPaintListener = null;
}
}
/**
* Builds X axis.
*/
@Override
protected void buildXAxis() {
String labels[] = adapter.getLabels();
IAxis xAxis = this.chart.getAxisSet().getXAxis(0);
if (xLineGrid) {
xAxis.getGrid().setStyle(LineStyle.SOLID);
} else {
xAxis.getGrid().setStyle(LineStyle.NONE);
}
xAxis.getTick().setForeground(BLACK);
xAxis.getTick().setTickMarkStepHint(xSeriesTicks);
ITitle xTitle = xAxis.getTitle();
xTitle.setForeground(BLACK);
if (labels.length > 0) {
xTitle.setText(labels[0]);
}
else {
xTitle.setText(""); //$NON-NLS-1$
}
}
/**
* Builds Y axis.
*/
@Override
protected void buildYAxis() {
IAxis yAxis = this.chart.getAxisSet().getYAxis(0);
yAxis.getTitle().setText(""); //$NON-NLS-1$
if (yLineGrid) {
yAxis.getGrid().setStyle(LineStyle.SOLID);
} else {
yAxis.getGrid().setStyle(LineStyle.NONE);
}
yAxis.getTick().setForeground(BLACK);
yAxis.getTick().setTickMarkStepHint(ySeriesTicks);
}
/**
* Builds X series.
*/
@Override
protected void buildXSeries() {
Object data[][] = adapter.getData();
if (data == null || data.length == 0) {
return;
}
int start = 0, len = Math.min(this.maxItems, data.length), leny = data[0].length-1;
if (this.maxItems < data.length) {
start = data.length - this.maxItems;
}
Double[] all_valx = new Double[len];
Double[][] all_valy = new Double[leny][len];
// Will want to centre view around points, so be as accurate with max/min as possible.
double maxX = Double.NEGATIVE_INFINITY;
double maxY = maxX;
double minX = Double.POSITIVE_INFINITY;
double minY = minX;
// Read in from the data array all x/y points to plot.
// In the case of an empty (null) value in either axis, ignore both x & y axis data for that point.
for (int i = 0; i < len; i++) {
for (int j = 0; j < leny + 1; j++) {
Double val = getDoubleOrNullValue(data[start + i][j]);
if (j == 0) {
if (val != null) {
all_valx[i] = val;
maxX = Math.max(val, maxX);
minX = Math.min(val, minX);
} else {
break;
}
} else if (val != null) {
all_valy[j-1][i] = val;
maxY = Math.max(val, maxY);
minY = Math.min(val, minY);
}
}
}
// Now create dense arrays of x/y values that exclude null values,
// and plot those values to the chart.
ISeries allSeries[] = chart.getSeriesSet().getSeries();
ISeries series = null;
for (int i = 0; i < leny; i++) {
if (i >= allSeries.length) {
series = createChartISeries(i);
} else {
series = chart.getSeriesSet().getSeries()[i];
}
double[] valx = new double[len];
double[] valy = new double[len];
int len_trim = 0;
for (int j = 0; j < len; j++) {
if (all_valx[j] != null && all_valy[i][j] != null) {
valx[len_trim] = all_valx[j].doubleValue();
valy[len_trim] = all_valy[i][j].doubleValue();
len_trim++;
}
}
double[] valx_trim = new double[len_trim];
double[] valy_trim = new double[len_trim];
for (int j = 0; j < len_trim; j++) {
valx_trim[j] = valx[j];
valy_trim[j] = valy[j];
}
series.setXSeries(valx_trim);
series.setYSeries(valy_trim);
}
if (series != null && series.getXSeries().length > 0) {
applyRangeX(minX, maxX);
applyRangeY(minY, maxY);
}
chart.redraw();
}
/**
* Updates the visible range of the chart's x-axis.
* @param min The smallest x-value that should be in range.
* @param max The largest x-value that should be in range.
*/
private void applyRangeX(double min, double max) {
IAxis axis = chart.getAxisSet().getXAxis(0);
double actualRange = max - min;
double scaledRange = actualRange * scale;
double marginL = scaledRange > 0 ? scaledRange * getChartMarginXL() : 1;
double marginU = scaledRange > 0 ? scaledRange * getChartMarginXU() : 1;
double lower = (actualRange - scaledRange) * scroll + min;
axis.setRange(new Range(lower - marginL, lower + scaledRange + marginU));
}
/**
* Updates the visible range of the chart's y-axis.
* @param min The smallest y-value that should be in range.
* @param max The largest y-value that should be in range.
* @since 3.0
*/
protected void applyRangeY(double min, double max) {
IAxis axis = chart.getAxisSet().getYAxis(0);
double actualRange = max - min;
double scaledRange = actualRange * scaleY;
double marginL = scaledRange > 0 ? scaledRange * getChartMarginYL() : 1;
double marginU = scaledRange > 0 ? scaledRange * getChartMarginYU() : 1;
double lower = (actualRange - scaledRange) * scrollY + min;
axis.setRange(new Range(lower - marginL, lower + scaledRange + marginU));
}
@Override
public void updateDataSet() {
buildXSeries();
chartMouseMoveListener.update();
}
@Override
protected void buildYSeries() {
}
}