Создание графиков с использованием Draw2D и SWT на платформе Java

Создавайте ваш собственный платформо-независимый код для построения X-Y графиков, столбиковых диаграмм и т.п.

Создание схем и графиков на платформе Java™ всегда вызывало интерес разработчиков. Традиционно Java-разработчики использовали для этого java.awt.Graphics или Java 2D API. Некоторые, возможно, применяют готовые инструменты с открытыми исходными кодами, например, JSci. Однако многие из доступных вариантов ограничивают вас выбором AWT или Swing. Чтобы минимизировать зависимость от инструментальных средств сторонних поставщиков или упростить рисование графиков, рассмотрите возможность применения Draw2D и напишите свой собственный код для черчения.

Indiver Dwivedi, Cтарший инженер-программист, IBM India

Author photoИндивер Двиведи (Indiver Dwivedi) является старшим инженером-программистом, работающим в Pune Lab для IBM India Software Labs. Он поступил в IBM в 2000 и работал над такими проектами как Lotus® SmartSuite и IBM data access tool for IBM Workplace. Он имеет четырехлетний опыт программирования на Ladder Logic, C и C++. Программировал логические контроллеры и компьютерные системы диспетчерского контроля и получения данных (SCADA) для систем промышленной автоматизации и управления (IAC, industrial automation and control). Интересуется темами, относящимися к взаимодействию устройств и графическому представлению исторических данных, хранящихся в системах считывания информации в IAC. В настоящее время работает в команде IBM Pune Lab и участвует в проекте IBM Workplace Designer.



24.05.2005

В двух словах о Draw2D

DrawD представляет собой облегченную систему графических элементов, размещенных на SWT Composite. Экземпляр Draw2D состоит из SWT Composite, облегченной системы и графических примитивов. Графические примитивы являются строительными блоками Draw2D. Полная информация по Draw2D API находится в системе помощи Eclipse в "Руководстве разработчика Draw2D". Поскольку в этой статье мы не собираемся обучать Draw2D, достаточно того, чтобы вы понимали, что Draw2D API помогает рисовать образы на SWT Canvas. Вы можете использовать стандартные графические примитивы, например, Ellipse, Polyline, RectangleFigure и Triangle непосредственно; вы можете расширить их, создавая свои собственные графические примитивы. Кроме того, некоторые примитивы-контейнеры, например Panel, могут выступать в качестве общего контейнера для всех дочерних элементов.

DrawD имеет два важных пакета: org.eclipse.draw2d.geometry и org.eclipse.draw2d.graph, которые я использую в данной статье. Пакет org.eclipse.draw2d.geometry содержит такие полезные классы как, например, Rectangle, Point и PointList, не требующие объяснений. Разработчики, возможно, не работали плотно с другим пакетом org.eclipse.draw2d.graph. Этот пакет предоставляет некоторые важные классы, такие как DirectedGraph, Node, Edge, NodeList и EdgeList, помогающие при создании графиков.

В этой статье, я объясню, как использовать Draw2D для написания кода, помогающего вам визуализировать ваши данные в графическом виде. Я начну с описания техники масштабирования значений данных, принадлежащих одному диапазону (например от 0 до 2048) в их эквивалентные значения, принадлежащие другому диапазону (например, от 0 до 100). Затем я проиллюстрирую, как нарисовать график X-Y из любого количества последовательностей, каждая из которых содержит набор элементов данных. После изучения общих понятий вы легко сможете нарисовать другие типы графиков, например, секторные и столбиковые диаграммы.


Процесс рисования графика шаг за шагом

Шаг 1: Что вы хотите нарисовать?

Очевидно, вы хотите вывести в виде графика данные из какого-нибудь источника данных. То есть, нам необходимы данные для визуализации в графическом формате. Для краткости, вместо чтения данных из XML-файла или какого-нибудь другого источника данных, я сгенерировал данные в простой функции с названием dataGenerator, которая использует цикл for(;;) и возвращает сгенерированные значения в виде списка массивов.

Листинг 1. Генерирование данных
	private ArrayList dataGenerator() {
		double series1[] = new double[5]; 
	    	for(int i=0; i<series1.length; i++)
	    		series1[i] = (i*10) + 10; // a linear 
	    		series containing	10,20,30,40,50
	    	
	    	double series2[] = new double[9]; 
	    	series2[0] = 20; series2[1] = 150; series2[2] = 5;
	    	series2[3] = 90; series2[4] = 35;  series2[5] = 20;
                            series2[6] = 150; series2[7] = 5; series2[8] = 45;
	    	
    		double series3[] = new double[7]; 
    		for(int i=0; i<series3.length; i++)
    			series3[i] = (i*20) + 15;
   		
    		seriesData.add(series1);
    		seriesData.add(series2);
    		seriesData.add(series3);	
	                returnseriesData;
	}

Шаг 2: Техника масштабирования - генерирование координат X и Y из имеющихся данных

Новые понятия

FigureCanvas

FigureCanvas в Draw2D является расширением SWT Canvas. FigureCanvas может содержать графические примитивы Draw2D.

Panel

Panel является контейнером графических примитивов общего назначения в Draw2D, который может содержать дочерние примитивы. Вы можете добавить множество примитивов на одну Panel и затем добавить этот один элемент Panel в FigureCanvas.

DirectedGraph

DirectedGraph является 2-D-графиком, имеющим конечное число элементов Node. Каждый Node расположен на некоторой Point, смежные Node связаны (или соединены) друг с другом посредством Edges.

Когда вы желаете нарисовать график на 2-D плоскости, вы должны найти координаты X и Y каждой точки. Магия рисования графиков состоит в способности масштабировать имеющиеся данные из одного диапазона в другой, то есть, для набора значений, например {10,20,30}, вы должны точно решить, какие точки (в координатах X и Y) на 2-D плоскости представляют значения 10, 20 и 30.

Всегда рисуйте в фиксированном масштабе. Другими словами, вы можете нарисовать любое количество точек в ограниченной области. Поскольку область фиксирована, вы можете всегда найти интервал (длину) по оси X и интервал (высоту) по оси Y. Знание интервалов по оси X и Y – это только одна часть уравнения. Другая часть – найти диапазон значений данных и вычислить координаты каждого значения, найдя эквивалентное ему значение в новом диапазоне.

Вычисление координат X и Y

X-координаты: X-координата – это расстояние до точки по горизонтали от начала координат. Расстояния по горизонтали для всех точек в наборе вычисляются простым подсчетом количества элементов и делением длины оси X на n сегментов, где n равно количеству элементов в данном наборе. В результате этого деления мы получаем длину каждого сегмента. Первая точка в наборе располагается на расстоянии, равном длине сегмента. Каждая следующая точка располагается на расстоянии длины сегмента плюс расстояние от начала координат до предыдущей точки.

Например, для набора {10,20,30,40} вы сразу видите, что нужно нарисовать четыре точки, поскольку набор содержит четыре значения. Таким образом, ось X должна быть поделена на четыре одинаковых сегмента с длиной length=span/4. Таким образом, если длина оси X равна 800, длина сегмента будет равна 800/4, или 200. X-координата первого элемента (10) будет равна 200, второго элемента (20) – 400 и т.д.

Листинг 2. Вычисление X-координат
private int[] getXCoordinates
(ArrayList seriesData){
	int xSpan = (int)GraFixConstants.xSpan;
           int longestSeries = 
Utilities.getLongestSeries(seriesData);
           int numSegments = 
           ((double[])seriesData.get
(longestSeries)).length;        
           int sectionWidth = 
           (int)xSpan / numSegments; 
//want to divide span of xAxis
        
           int xPositions[] = 
           new int[numSegments]; 
// will contain X-coordinate of all dots.
	for(int i=0; i<numSegments; i++){
		xPositions[i]= 
		(i+1)*sectionWidth;//dots 
spaced at distance of sectionWidth			
	}
	return xPositions;
}

Y-координаты: Y-координата – это расстояние до точки от начала координат по вертикали. Вычисление Y-координат заключается в масштабировании значений из одного диапазона в другой. Например, для набора значений {10,20,30,40}, видно, что диапазон данных равен от 0 до 40, а новый диапазон – это длина (высота) оси Y. Предположив, что высота оси Y равна 400, эквивалентная высота первого элемента (10) будет равна 100, второго элемента (20) – 200 и т.д.

Вы сможете лучше понять процесс масштабирования значений из одного диапазона в другой на следующем примере. Предположим, что один диапазон значений равен 0-2048, и вы хотите масштабировать любое значение из этого диапазона (например, 1024) в другой диапазон, равный 0-100. Вы сразу найдете результат – эквивалентное значение равно 50. В масштабировании участвуют следующие три строки арифметических действий:

строка 1---> 2048 / 1024 равно 2.
строка 2---> 100 - 0 равно100. 
строка 3---> 100 / 2 равно 50, что и является требуемым масштабированным значением.

Шаг 3: Где вы хотите рисовать?

Вам необходима какая-нибудь область для рисования. Создайте ваш собственный вид (view), расширив Eclipse ViewPart и используя SWT Composite. В качестве альтернативы вы можете использовать SWT-оболочку, вызываемую из функции main().

При расширении Eclipse ViewPart вы должны реализовать по крайней мере две функции: createPartControl(Composite parent) и setFocus(). Функция createPartControl(Composite parent) вызывается автоматически, когда ваш вид должен быть нарисован на экране. Вам интересен только полученный таким образом SWT Composite. Передайте его в класс, который вы написали для черчения графиков.

Листинг 3. Использование Eclipse ViewPart для рисования
public class MainGraFixView extends ViewPart{
	public void createPartControl(Composite parent) {
		
		//создать или получить данные в arraylist
		ArrayList seriesData = dataGenerator();

		//создать экземпляр построителя графиков, и предоставить ему данные.
		DirectedGraphXYPlotter dgXYGraph = new DirectedGraphXYPlotter(parent);
		dgXYGraph.setData(seriesData);
		dgXYGraph.plot(); //запрос на рисование		

	}
	public void setFocus() {	
	}
}

Шаг 4: Какой тип графика вам нужен?

Если вы имеете данные и область для рисования, вы должны решить, какой тип визуализации вам нужен. В данной статье я продемонстрирую, как написать код для создания линейного графика. Если вы поймете технику, лежащую в основе рисования линейного графика, то сможете нарисовать и другие типы графиков, например секторные и столбиковые диаграммы. Для изучения способа построения линейного графика посмотрите класс DirectedGraphXYPlotter, который я написал для этой статьи. (см. файл \src\GraFix\Plotters\DirectedGraphXYPlotter.java в исходном коде, прикрепленном к этой статье).

Шаг 5: Создание вашего собственного построителя X-Y-графиков

Построитель X-Y-графиков должен уметь рисовать любое число линий на 2-D плоскости. Каждая линия должна графически показывать позицию каждой точки своей последовательности со ссылкой на ось X и на ось Y. Каждая точка должна быть соединена со следующей точкой последовательности линией. Вы можете создать такой построитель, используя графические примитивы Draw2D, представляющие точку и линию. Например, для представления точки я создал примитив Dot, расширив примитив Ellipse, и использовал PolylineConnection для представления соединительных линий.

Класс DirectedGraphXYPlotter имеет только две функции со спецификатором доступа public: setData(ArrayList seriesData) и plot(). Функция setData(ArrayList seriesData) принимает данные (см. шаг 1), которые вы хотите визуализировать графически, а функция plot() начинает рисовать график.

После вызова функции plot() необходимо последовательно выполнить следующие действия:

  1. Возьмите SWT Composite и положите на него FigureCanvas. Затем на канву положите примитив-контейнер общего назначения, например Panel.
  2. Посчитайте количество последовательностей для вывода графика и заполните необходимое число NodeLists и EdgeLists для создания DirectedGraphs.
  3. Нарисуйте оси X и Y на примитиве Panel. (См. файлы XRulerBar.java и YRulerBar.java в каталоге \src\GraFix\Figure прикрепленного к статье исходного кода).
  4. Создайте столько DirectedGraphs, сколько имеется последовательностей для рисования.
  5. Нарисуйте точки и соединительные линии на примитиве Panel, получая данные графика из DirectedGraphs, созданного на шаге d.
  6. Наконец, установите содержимое канвы, предоставив примитив Panel, который содержит все точки и соединительные линии, подготовленные вами.

В коде, приведенном ниже:

  • Строки 6-11 соответствует шагу a.
  • Строка 14, функция populateNodesAndEdges(), соответствует шагу b.
  • Строка 16, функция drawAxis(),соответствует шагу c.
  • Строки 17, 18 и 19 соответствуют шагам d и e.
  • Строка 20 соответствует шагу f.
Листинг 4. Функция plot()
1.	public void plot(){
2.	    //вернуть управление, если негде рисовать, или нет данных для рисования.
3.	    if(null==_parent || null==_seriesData)
4.	        return;
5.
6.	    Composite composite = new Composite(_parent, SWT.BORDER);
7.	    composite.setLayout(new FillLayout());
8.	    FigureCanvas canvas = new FigureCanvas(composite);
9.	    
10.	    Panel contents = new Panel();//Panel – 
примитив-контейнер общего назначения
11.	    contents.setLayoutManager(new XYLayout());
12.	    initializeSpan(contents.getClientArea());
13.	    
14.	    populateNodesAndEdges();	    
15.	    
16.	    drawAxis(contents);
17.	    for(int i=0; i<_numSeries; i++){
18.	    	drawDotsAndConnections(contents,getDirectedGraph(i)); // 
нарисовать точки и соединительные линии
19.	    }
20.	    canvas.setContents(contents);
21.	}

Две важные внутренние функции, вызываемые в plot(), populateNodesAndEdges() и drawDotsAndConnections() помогают в рисовании точек. Перед тем как вы узнаете, что точно делают эти функции, рассмотрим DirectedGraph.

Что такое DirectedGraph? Для рисования графика в Draw2D вы сначала должны создать график виртуально, чтобы определить выводимые точки и линии. После создания этого графика вы можете использовать его в дальнейшем для фактического начала рисования примитивов на канве. Вы можете визуализировать DirectedGraph в виде 2-D-графика, имеющего конечное число элементов Node; каждый элемент Node расположен в некоторой точке (Point); смежные элементы Nodes связаны (или соединены) друг с другом элементами Edges.

Вы можете понять загадку создания DirectedGraph при помощи следующих строк кода. Прежде всего, создайте список элементов Nodes и список элементов Edges. Затем создайте новый DirectedGraph и укажите в качестве его членов (Nodes и Edges) только что созданные списки NodeList и EdgeList. Используйте GraphVisitor для обращения к этому DirectedGraph. Для упрощения работы пакет org.eclipse.draw2d.internal.graph содержит много реализаций GraphVisitor, которые уже имеют специализированные алгоритмы для обращения к графику.

Итак, пример кода для включения DirectedGraph может быть таким:

Листинг 5. Пример DirectedGraph
//Это пример, вы должны добавить реальные элементы
 Node(s) в этот список NodeList.
NodeList nodes = new NodeList(); //создать список nodes.
// Это пример, вы должны добавить реальные элементы 
Edge(s) в этот список EdgeList.
EdgeList edges = new EdgeList(); // создать список edges. 
DirectedGraph graph = new DirectedGraph();
graph.nodes = nodes;
graph.edges = edges;
new BreakCycles().visit(graph);//запрос в BreakCycles для обращения к графику.
//теперь наш "график" готов к использованию.

Теперь, когда вы знаете, что DirectedGraph содержит список элементов Nodes, в котором каждый Node может содержать некоторые данные и хранить их координаты X и Y, и список элементов Edges, в котором каждый Edge знает о двух элементах Node на обоих концах, вы можете использовать эту информацию для рисования графиков, применяя следующую методику, состоящую из двух частей:

Часть A - Заполнить Nodes и Edges путем:

  • Создания NodeList, содержащего по одному Node на элемент набора. Например, для набора {10,20,30,40} требуется создать четыре Node.
  • Поиска координат X и Y для каждого элемента и сохранения их в переменных экземпляра членах node.x и node.y.
  • Создания EdgeList, содержащего n-1 элементов Edges, где n – количество элементов в наборе. Например, для набора {10,20,30,40} требуется создать три Edges.
  • Указания Node для левого и правого края каждого Edge и установки переменных экземпляра edge.start и edge.end соответственно.

Часть B – Нарисовать графические примитивы, представленные Nodes и Edges путем:

  • Рисования примитива Dot, представленного каждым Node.
  • Рисования примитива PolylineConnection, представленного каждым Edge.
  • Привязывания каждого примитива PolylineConnection к примитивам Dot слева и справа.

Теперь вернемся к работе с внутренними функциями:

  • Функция populateNodesAndEdges() реализует часть A рассмотренной методики, а drawDotsAndConnections() реализует часть B.
  • Функция populateNodesAndEdges() считает количество последовательностей, предназначенных для вывода на график. Она создает один NodeList и один EdgeList для каждой последовательности.
  • Каждый NodeList содержит список Nodes для конкретной последовательности. Каждый Node хранит информацию о координатах X и Y. Функции getXCoordinates() и getYCoordinates() извлекают значение координат X и Y соответственно. Эти функции также масштабируют значения данных из одного диапазона в другой, используя алгоритм, описанный на шаге 2.
  • Каждый EdgeList содержит список Edges для конкретной последовательности. Каждый Edge содержит Node слева и другой Node справа.
Листинг 6. Функция populateNodesAndEdges()
private void populateNodesAndEdges(){
	    
    _seriesScaledValues = new ArrayList(getScaledValues(_seriesData));
		_nodeLists = new ArrayList();
		_edgeLists = new ArrayList();
		
		for(int i=0; i<_numSeries; i++){
			_nodeLists.add(new NodeList());// 
один NodeList на последовательность.
			_edgeLists.add(new EdgeList());//
 один EdgeList на последовательность.
		}
		//заполнить все NodeLists элементами Nodes. 
		for(int i=0; i<_numSeries; i++){//для каждой последовательности 
			double data[] = (double[])_seriesData.get(i);//получить последовательность
			int xCoOrds[] = getXCoordinates(_seriesData);
			int yCoOrds[] = getYCoordinates(i, data);
			//каждый NodeList будет содержать столько Nodes,
 сколько имеется точек в последовательности
			for(int j=0; j<data.length; j++){
				Double doubleValue = new Double(data[j]);
				Node node = new Node(doubleValue);
				node.x = xCoOrds[j];
				node.y = yCoOrds[j];				
				((NodeList)_nodeLists.get(i)).add(node);
			}					
		}
		//заполнить все EdgeLists элементами Edges. 
		for(int i=0; i<_numSeries; i++){
			NodeList nodes = (NodeList)_nodeLists.get(i);			
			for(int j=0; j<nodes.size()-1; j++){
				Node leftNode = nodes.getNode(j);
				Node rightNode = nodes.getNode(j+1);
				Edge edge = new Edge(leftNode,rightNode);
				edge.start = new Point(leftNode.x, leftNode.y); 
				edge.end = new Point(rightNode.x, rightNode.y);
		((EdgeList)_edgeLists.get(i)).add(edge);
	}	
}
int breakpoint = 0;
	}

После того, как функция populateNodesAndEdges() выполнит свою работу по созданию NodeLists и EdgeLists для всех последовательностей, другая функция drawDotsAndConnections() начинает рисовать графические примитивы Dot для каждого Node и примитивы PolylineConnection для каждого Edge.

Листинг 7. Функции drawDotsAndConnections(), drawNode() и drawEdge()
	private void drawDotsAndConnections(IFigure
 contents, DirectedGraph graph){
	    for (int i = 0; i < graph.nodes.size(); i++) {
	        Node node = graph.nodes.getNode(i);
	        drawNode(contents, node);
	    }
	    for (int i = 0; i < graph.edges.size(); i++) {
	        Edge edge = graph.edges.getEdge(i);
	        drawEdge(contents, edge);
	    }		
	}
	

	private void drawNode(IFigure contents, Node node){
		 Dot dotFigure = new Dot();
		 node.data = dotFigure;
		 int xPos = node.x;
		 int yPos = node.y;	 
		 contents.add(dotFigure);
		 contents.setConstraint(dotFigure, new Rectangle(xPos,yPos,-1,-1));
	}
	

	private void drawEdge(IFigure contents, Edge edge){
	    PolylineConnection wireFigure = new PolylineConnection();
		//edge.source является элементом Node слева 
	    EllipseAnchor sourceAnchor = new
 EllipseAnchor((Dot)edge.source.data);

		//edge.target является элементом Node справа
		EllipseAnchor targetAnchor = new
 EllipseAnchor((Dot)edge.target.data);
	    wireFigure.setSourceAnchor(sourceAnchor);
	    wireFigure.setTargetAnchor(targetAnchor);
	    contents.add(wireFigure);	    
	}
Результат рисования графика
Результат рисования графика

Итоги

Если вам понадобиться представить данные в графическом виде, Draw2D является хорошим инструментом. Использование Draw2D для написания вашего собственного Java-кода, выполняющего рисование диаграмм и графиков, может помочь вам сконцентрироваться на коде масштабирования и рисования, оставляя для Draw2D и SWT работу по рендерингу и выводу на экран. Вы можете также контролировать внешний вид ваших графиков, используя по своему выбору графические примитивы Draw2D. Draw2D упрощает основную работу по рисованию диаграмм и графиков, а также минимизирует вашу зависимость от инструментальных программ сторонних поставщиков.


Загрузка

ОписаниеИмяРазмер
Unzip and open this plug-in as an Eclipse projectGraFix.zip  ( HTTP | Download Director Помощь )24 KB

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


  • Bluemix

    Узнайте больше информации о платформе IBM Bluemix, создавайте приложения, используя готовые решения!

  • Библиотека документов

    Более трех тысяч статей, обзоров, руководств и других полезных материалов.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Технология Java
ArticleID=96591
ArticleTitle=Создание графиков с использованием Draw2D и SWT на платформе Java
publish-date=05242005