Статьи

Пользовательский компонент Facelets Chart с использованием Flash с открытым исходным кодом


Вы, наверное, слышали старый принцип, что есть ложь, проклятая ложь, статистика и диаграммы Excel — ну, любой, кто когда-либо пытался делать какие-либо серьезные отчеты о статистике в Интернете, знает, что вам нужно уметь создавать хорошие графики. В течение многих лет были такие библиотеки, как JFreeChart, которые выполняли разумную работу по рисованию графиков статическим образом, в последние годы появление Google Analytics (например, диаграммы ценовой политики Google Finance) значительно повысило планку качества. Внезапно всем, включая вашего линейного менеджера, потребовались причудливые линейные графики, которые показывают числа, представленные каждой точкой, когда вы наводите мышь вдоль линии (я не собираюсь использовать диаграммы Google, поскольку для их построения требуется подключение к Интернету.Если ваше приложение установлено в какой-либо защищенной среде, где внешние подключения не разрешены, у вас большие проблемы).

После некоторого поиска в Google я наткнулся на проект Open Flash Chart (
http://teethgrinder.co.uk/open-flash-chart ). Open Flash Chart позволяет каждому иметь на своем сайте интерактивные Flash-диаграммы. Итак, сегодня я собираюсь обсудить, как мы можем использовать открытую флэш-память для создания потрясающих флеш-диаграмм на Java. Я собираюсь разработать фацетный компонент JSF, который можно легко подключить к любому приложению JSF. Самое лучшее в диаграмме Open Flash — это то, что она потребляет данные JSON, поэтому компонент, который я собираюсь разработать, сможет обрабатывать диаграммы любого типа (круговые, линейчатые, линейные и т. Д.), Нам просто нужно подать данные диаграммы. в формате JSON, а остальные будут заботиться о компоненте.

Пользовательский компонент диаграммы

Пользовательский компонент JSF представлен Java-классом, который происходит от UIComponentBase. Экземпляр этого класса создается всякий раз, когда отображается новая страница, содержащая компонент. Я не буду вдаваться в подробности того, как мы создаем пользовательские компоненты. Давайте перейдем прямо к определению компонента.

package gognamunish.jofc.poc;

import java.io.IOException;

import javax.el.ValueExpression;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

/**
* Custom chart component for Open Source Flash
*
* @author Munish Gogna
*
*/
public class JOFCChartUIComponent extends UIComponentBase {

private static final String COMPONENT_FAMILY = "JOFCChart";

private String width = "400"; // default width
private String height = "400"; // default height
private String jsonSource;

@Override
public void encodeBegin(FacesContext context) throws IOException {
super.encodeBegin(context);

final ResponseWriter writer = context.getResponseWriter();

writer.startElement("script type=\"text/javascript\" src=\"../jofc/js/swfobject.js\"",this);
writer.endElement("script");
writer.startElement("script type=\"text/javascript\" src=\"../jofc/js/json2.js\"",this);
writer.endElement("script");
writer.startElement("script type=\"text/javascript\"", this);
writer.write("swfobject.embedSWF (");
writer.write("\"../jofc/open-flash-chart.swf\", \"jofc_chart\",");
writer.write("\"" + getWidth() + " \", \"" + getHeight() + " \", \"9.0.0\", \"expressInstall.swf\", ");
writer.write("{\"get-data\":\"ofc_get_data\"} );");
writer.write("function ofc_get_data() { return JSON.stringify( " +getJSONSource() + ");}");
writer.endElement("script");
}

@Override
public void encodeEnd(FacesContext context) throws IOException {
super.encodeEnd(context);
}

@Override
public Object saveState(FacesContext facesContext) {
Object values[] = new Object[4];
values[0] = super.saveState(facesContext);
values[1] = this.getAttributes().get(JOFCChart.WIDTH);
values[2] = this.getAttributes().get(JOFCChart.HEIGHT);
values[3] = this.getAttributes().get(JOFCChart.JSON_SOURCE);
return values;

}

@Override
public void restoreState(FacesContext facesContext, Object state) {
Object values[] = (Object[]) state;
super.restoreState(facesContext, values[0]);
this.getAttributes().put(JOFCChart.WIDTH, values[1]);
this.getAttributes().put(JOFCChart.HEIGHT, values[2]);
this.getAttributes().put(JOFCChart.JSON_SOURCE, values[3]);
}

@Override
public String getFamily() {
return COMPONENT_FAMILY;
}

public String getWidth() {
ValueExpression ve = getValueExpression(JOFCChart.WIDTH);
if (ve != null) {
return (String) ve.getValue(getFacesContext().getELContext());
}
return width;
}

public String getJSONSource() {
ValueExpression ve = getValueExpression(JOFCChart.JSON_SOURCE);
if (ve != null) {
return (String) ve.getValue(getFacesContext().getELContext());
}
return jsonSource;
}


public String getHeight() {
ValueExpression ve = getValueExpression(JOFCChart.HEIGHT);
if (ve != null) {
return (String) ve.getValue(getFacesContext().getELContext());
}
return height;
}


public void setWidth(String width) {
this.width = width;
}

public void setHeight(String height) {
this.height = height;
}

public void setJsonSource(String jsonSource) {
this.jsonSource = jsonSource;
}
}

Примечание: обратите внимание, что мы не определили никакого отдельного средства визуализации для нашего компонента, компонент отображает HTML самостоятельно.

Давайте определим дескриптор для нашего пользовательского компонента facelet — jofc_taglib.xml

<facelet-taglib>
<namespace> http://gognamunish.jofc.poc </namespace>
<tag>
<tag-name>chart</tag-name>
<component>
<component-type>JOFCChart</component-type>
</component>
</tag>
</facelet-taglib>

Компоненты JSF Facelets должны быть зарегистрированы как в файлах web.xml, так и face-config.xml.

запись web.xml:

<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>/WEB-INF/jofc_taglib.xml</param-value>
</context-param>

Запись лиц-конфигурации:

<component>
<component-type>JOFCChart</component-type>
<component-class>gognamunish.jofc.poc.JOFCChartUIComponent</component-class>
</component>

Генерация данных JSON

Как я уже говорил ранее, Open Flash работает с данными JSON, а это значит, что нам придется снабжать наш пользовательский компонент диаграммы некоторыми данными JSON. Здесь у нас есть два варианта: либо мы сами создаем данные JSON, либо используем некоторую библиотеку. Я выбрал второй вариант, т.е.
http://code.google.com/p/jofc2/ Java API. Эта библиотека не стабильна на 100% и содержит несколько ошибок (читайте между строками в следующем Java-коде).

Попробуем построить примерные данные о поступлении за октябрь 2010 года с использованием линейного графика. Этот пример демонстрирует несколько вещей, которые вы можете сделать с помощью линейных графиков.

package gognamunish.jofc.data;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import jofc2.OFC;
import jofc2.model.Chart;
import jofc2.model.Text;
import jofc2.model.axis.Label;
import jofc2.model.axis.XAxis;
import jofc2.model.axis.XAxisLabels;
import jofc2.model.axis.YAxis;
import jofc2.model.axis.Label.Rotation;
import jofc2.model.elements.LineChart;
import jofc2.model.elements.LineChart.Dot;

/**
* Source class for our sample Admission chart
*
* @author Munish Gogna
*
*/
public class DataSource {

private List admissionDataList;

/**
* Returns the JSON data which defines the elements of the OFC2 charts.
*/
public String getJOFCChartData() {
Chart chart = new Chart();
chart.setTitle(new Text("Admission Data for October 2010"));
XAxis xAxis = new XAxis();
XAxisLabels xAxisLabels = new XAxisLabels();
xAxisLabels.addLabels(getXLabels());
xAxis.setXAxisLabels(xAxisLabels);
chart.setXAxis(xAxis);

YAxis yAxis = new YAxis();
yAxis.setMax(getAdmissionList().size() + 1);
chart.setYAxis(yAxis);

LineChart lineChart = new LineChart();
lineChart.setText("copyright: Munish Gogna 2010");
lineChart.addDots(getValues());
lineChart.setFontSize(10);

chart.addElements(lineChart);

// JOFC API BUG - The enum Rotation.VERTICAL should have value 90 as opposed to -90
return OFC.getInstance().render(chart).replace("-90", "90");
}

private List getXLabels() {
List xLabelsList = new ArrayList();
for (AdmissionData cd : getAdmissionList()) {
Label label = new Label();
label.setText(cd.month);
// for displaying the X labels vertically
label.setRotation(Rotation.VERTICAL);
xLabelsList.add(label);
}
return xLabelsList;
}

private List getValues() {
List valuesList = new ArrayList();
Dot dot;
for (AdmissionData cd : getAdmissionList()) {
dot = new Dot(cd.noOfStudents);
dot.setTooltip("#val# on #x_label#");
dot.setDotSize(2);
dot.setHaloSize(3);
dot.setColour("#FF0000");
valuesList.add(dot);
}
return valuesList;
}

private List getAdmissionList() {
if (admissionDataList == null) {
admissionDataList = new ArrayList();
Random random = new Random();
AdmissionData data;
for (int i = 1; i <= 30; i++) {
data = new AdmissionData("October" + i, random.nextInt(30));
admissionDataList.add(data);
}
}
return admissionDataList;
}

/** helper class for plotting admission related data */
static class AdmissionData {

public String month;
public int noOfStudents;

AdmissionData(String month, int noOfStudents) {
this.month = month;
this.noOfStudents = noOfStudents;
}
}
}

В отсутствие библиотеки Java API JOFC2 нам пришлось бы предоставить следующую строку json для нашего компонента диаграммы для искомой диаграммы. Не пугайтесь, JSON не предназначен для потребления человеком ?

{"is_thousand_separator_disabled":0,"is_decimal_separator_comma":0,"title":{"text":"Admission Data for October 2010"},"y_axis":{"max":31},"x_axis":

{"labels":{"labels":[{"text":"October1","rotate":"90"},{"text":"October2","rotate":"90"},{"text":"October3","rotate":"90"},

{"text":"October4","rotate":"90"},{"text":"October5","rotate":"90"},{"text":"October6","rotate":"90"},{"text":"October7","rotate":"90"},

{"text":"October8","rotate":"90"},{"text":"October9","rotate":"90"},{"text":"October10","rotate":"90"},{"text":"October11","rotate":"90"},

{"text":"October12","rotate":"90"},{"text":"October13","rotate":"90"},{"text":"October14","rotate":"90"},{"text":"October15","rotate":"90"},

{"text":"October16","rotate":"90"},{"text":"October17","rotate":"90"},{"text":"October18","rotate":"90"},{"text":"October19","rotate":"90"},

{"text":"October20","rotate":"90"},{"text":"October21","rotate":"90"},{"text":"October22","rotate":"90"},{"text":"October23","rotate":"90"},

{"text":"October24","rotate":"90"},{"text":"October25","rotate":"90"},{"text":"October26","rotate":"90"},{"text":"October27","rotate":"90"},

{"text":"October28","rotate":"90"},{"text":"October29","rotate":"90"},

{"text":"October30","rotate":"90"}]}},"num_decimals":2,"is_fixed_num_decimals_forced":0,"elements":[{"font-size":10,"text":"copyright: Munish Gogna

2010","type":"line","dot-style":{"halo-size":2,"type":"solid-dot","dot-size":2},"on-show":{"type":""},"values":[{"value":15,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":16,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":14,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":25,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":28,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":6,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":23,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":6,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":20,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":21,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":24,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":3,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":17,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":25,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":13,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":15,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":23,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":1,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":23,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":18,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":20,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":7,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":5,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":8,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":11,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":2,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":11,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":7,"halo-

size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"},{"value":2,"halo-size":3,"tip":"#val# on #x_label#","dot-

size":2,"colour":"#FF0000"},{"value":7,"halo-size":3,"tip":"#val# on #x_label#","dot-size":2,"colour":"#FF0000"}],"loop":false}]}

Пока все хорошо, давайте посмотрим, как выглядит наш график, а не интерактивный график?

Последний шаг — использование компонента

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:jofc="http://gognamunish.jofc.poc"
xmlns:p="http://primefaces.prime.com.tr/ui">

<f:view contentType="text/html">
<h:body>
<jofc:chart width="500" height="400" jsonSource="#{data.JOFCChartData}" />
<div id="jofc_chart" />
</h:body>
</f:view>
</html>

и тут появляется волшебный флеш фильм:

admission chart

То, что вы видите выше, является статичным изображением, но на самом деле вы получите флэш-ролик, так как мы перемещаем мышь вдоль линии, соответствующим образом изменяется подсказка (во время снятия скриншота указатель мыши был 24 октября).

Это было просто Вообще-то, этот компонент можно улучшить многими способами. Остальное я оставляю вам, ребята.