/*
* SoapUI, Copyright (C) 2004-2016 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*/
package com.eviware.soapui.impl.wsdl.panels.loadtest;
import com.eviware.soapui.impl.wsdl.loadtest.ColorPalette;
import com.eviware.soapui.impl.wsdl.loadtest.WsdlLoadTest;
import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics;
import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics.Statistic;
import com.eviware.soapui.impl.wsdl.loadtest.data.StatisticsHistory.StatisticsHistoryModel;
import com.jgoodies.forms.builder.ButtonBarBuilder;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.Scrollable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
/**
* Graphical representation of testschedule statistics
*
* @author Ole.Matzura
*/
public class JStatisticsHistoryGraph extends JComponent implements Scrollable {
private static final Color THREADCOUNT_COLOR = Color.GREEN.darker();
private static final int SCROLL_AHEAD = 50;
private static final Color TOTAL_COLOR = Color.BLACK;
private final WsdlLoadTest loadTest;
private final LoadTestStatistics statisticsModel;
private StatisticsHistoryModel data;
private JComponent legend;
private InternalTableModelListener tableModelListener = new InternalTableModelListener();
private long[] maxValues;
private float[] scales;
public JStatisticsHistoryGraph(WsdlLoadTest loadTest) {
this.loadTest = loadTest;
this.statisticsModel = loadTest.getStatisticsModel();
this.data = statisticsModel.getHistory().getStatisticsValueHistory(Statistic.AVERAGE);
setAutoscrolls(true);
addMouseMotionListener(new InternalMouseMotionListener());
data.addTableModelListener(tableModelListener);
initMaxValues();
initScales();
setBackground(Color.WHITE);
setOpaque(true);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
initScales();
}
});
}
public long getResolution() {
return statisticsModel.getHistory().getResolution();
}
public void setResolution(long resolution) {
statisticsModel.getHistory().setResolution(resolution);
}
public TableModel getModel() {
return data;
}
public void release() {
data.removeTableModelListener(tableModelListener);
}
public void setStatistic(Statistic statistic) {
if (data != null) {
data.removeTableModelListener(tableModelListener);
data.release();
}
data = statisticsModel.getHistory().getStatisticsValueHistory(statistic);
initMaxValues();
initScales();
data.addTableModelListener(tableModelListener);
getParent().invalidate();
revalidate();
repaint();
}
private void initMaxValues() {
maxValues = new long[data.getColumnCount()];
for (int c = 0; c < data.getRowCount(); c++) {
for (int i = 0; i < data.getColumnCount(); i++) {
long value = (Long) data.getValueAt(c, i);
if (value > maxValues[i]) {
maxValues[i] = value;
}
}
}
}
private void initScales() {
scales = new float[maxValues.length];
for (int c = 0; c < maxValues.length; c++) {
recalcScale(c);
}
}
private boolean recalcScale(int index) {
float scale = (index == 0 || maxValues[index] == 0) ? 1 : (float) (getHeight())
/ (float) (maxValues[index] + 10);
if (scale > 1) {
scale = 1;
}
if (Float.compare(scale, scales[index]) == 0) {
return false;
}
scales[index] = scale;
return true;
}
public void paintComponent(Graphics g) {
g.setColor(getBackground());
Rectangle clip = g.getClipBounds();
g.fillRect((int) clip.getX(), (int) clip.getY(), (int) clip.getWidth(), (int) clip.getHeight());
double right = clip.getX() + clip.getWidth();
int height = getHeight();
for (int c = (int) clip.getX(); c < data.getRowCount() && c < right; c++) {
for (int i = 0; i < data.getColumnCount(); i++) {
if (i == 0) {
g.setColor(THREADCOUNT_COLOR);
} else if (i == data.getColumnCount() - 1) {
g.setColor(TOTAL_COLOR);
} else {
g.setColor(ColorPalette.getColor(loadTest.getTestCase().getTestStepAt(i - 1)));
}
int yOffset = (int) ((float) ((Long) data.getValueAt(c, i)) * scales[i]);
if (clip.contains(c, height - yOffset - 1)) {
g.drawLine(c, height - yOffset - 1, c, height - yOffset - 1);
}
}
}
}
public JComponent getLegend() {
if (legend == null) {
buildLegend();
}
return legend;
}
private void buildLegend() {
ButtonBarBuilder builder = new ButtonBarBuilder();
builder.addFixed(new JLabel("ThreadCount", createLegendIcon(THREADCOUNT_COLOR), JLabel.LEFT));
builder.addUnrelatedGap();
builder.addFixed(new JLabel("Total", createLegendIcon(TOTAL_COLOR), JLabel.LEFT));
builder.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
legend = builder.getPanel();
}
private Icon createLegendIcon(Color color) {
BufferedImage image = new BufferedImage(10, 10, BufferedImage.TYPE_3BYTE_BGR);
Graphics g = image.getGraphics();
g.setColor(color);
g.fillRect(1, 1, 8, 8);
g.setColor(Color.DARK_GRAY);
g.drawRect(0, 0, 10, 10);
return new ImageIcon(image);
}
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
public Dimension getPreferredSize() {
int height = getHeight();
int width = data.getRowCount() + SCROLL_AHEAD;
return new Dimension(width, height);
}
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 1;
}
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 10;
}
public boolean getScrollableTracksViewportWidth() {
return false;
}
public boolean getScrollableTracksViewportHeight() {
return true;
}
private final class InternalTableModelListener implements TableModelListener {
public synchronized void tableChanged(TableModelEvent e) {
boolean repaint = false;
if (e.getType() == TableModelEvent.INSERT) {
int firstRow = e.getFirstRow();
int lastRow = e.getLastRow();
int height = getHeight();
for (int c = firstRow; c <= lastRow; c++) {
for (int i = 0; i < data.getColumnCount(); i++) {
long value = (Long) data.getValueAt(c, i);
if (value > maxValues[i]) {
maxValues[i] = value;
repaint = recalcScale(i);
}
}
}
if (!repaint) {
Rectangle rect = new Rectangle(firstRow, 0, (lastRow - firstRow) + 1, height);
repaint(rect);
}
Dimension size = getSize();
Rectangle r = getVisibleRect();
double x2 = r.getX() + r.getWidth();
if (x2 >= data.getRowCount() && x2 < data.getRowCount() + SCROLL_AHEAD) {
scrollRectToVisible(new Rectangle(firstRow + SCROLL_AHEAD / 2, 0, (lastRow - firstRow) + 1, height));
}
if (!repaint && size.getWidth() < data.getRowCount() + SCROLL_AHEAD) {
revalidate();
}
} else if (e.getType() == TableModelEvent.UPDATE) {
initMaxValues();
initScales();
repaint = true;
}
if (repaint) {
getParent().invalidate();
revalidate();
repaint();
}
}
}
private class InternalMouseMotionListener implements MouseMotionListener {
public void mouseDragged(MouseEvent e) {
Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1);
scrollRectToVisible(r);
}
public void mouseMoved(MouseEvent e) {
}
}
}