Файфель Б.Л.
Наверное, каждый WEB-разработчик сталкивался с проблемой построения графиков и диаграмм в браузере. Это и сейчас (несмотря на бурное развитие средств разработки) остается серьезной проблемой. Дело в том, что отцы-основатели HTML не предусмотрели достаточно внятных средств для отображения презентационной графики. Нет, готовую картинку отобразить легко. Но если ее нужно строить динамически... Возможностей не так много: можно строить картинку на сервере и передавать клиенту либо в виде файла, либо в виде графического контента; а можно передавать данные для построения графика/диаграммы клиенту, и картинку строить на клиентском компьютере.
Каждый из этих подходов имеет свои плюсы и минусы.
При построении картинки на сервере нужно разработать самому или где-то взять (купить!) готовый серверный компонент.
При построении картинки на стороне клиента графический компонент придется внедрять в html-страницу. При этом нужно, чтобы компонет присутствовал у клиента. А если компонента у клиента нет, потребуется обеспечить его инсталляцию через WEB-интерфейс. В этом случае компонент желательно подписать. Клиенту при инсталляции будут задаваться лишние вопросы, а это отпугивает...
Предлагаю для отображения графики использовать Excel. Это не так абсурдно, как кажется на первый взгляд.
В составе Excel есть великолепный мастер диаграмм. Мы получаем с WEB-сервера только числовые данные, а нужную нам диаграмму или график строит Excel. При этом внешний вид (тип диаграммы, ее конкретные настройки, размеры и т.д.) мы задаем сами, и всегда можем изменить.
Поскольку WEB-сервер только генерирует поток числовых данных (это в десятки раз меньший объем, чем графическая картинка!), нагрузка на WEB-сервер и сеть значительно снижается, что тоже неплохо.
Хотите попробовать? Не пожалеете!
Начнем с сервера. Автор является поклонником Microsoft-технологий. В данном случае мы будем использовать ASP для серверного сценария и Access для хранения данных.
Создаем пустую Access-ную базу; файл назовем "myBase.mdb". Занесем в эту базу данные для построения трех графиков:
-y=sin(x);
-y=x^2;
-y=Exp(-0.2*x)*Sin(5*x)
Для этого создадим в базе таблицу (назвав ее, скажем, Dat), у которой будут четыре поля x, y1, y2 и y3 (все типа Float). Затем заполним таблицу данными. Все это делается следующим VBS-скриптом:
'----- Получим имя текущей директории
Tmp=Cstr(WScript.ScriptFullName)
l=Len(Tmp)
For i=l to 1 Step -1
If mid(Tmp,i,1)="\" then
HomeDir=Left(Tmp,(i-1))
Exit For
End if
Next
'----- Создадим ADO-соединение и recordset
Set Conn = CreateObject("ADODB.Connection")
Set RS = CreateObject("ADODB.Recordset")
DSNName = "DRIVER=Microsoft Access Driver (*.mdb);DBQ="
DSNName = DSNName & HomeDir & "\mybase.mdb"
Conn.Open DSNName
'------ Проверим, присутствует ли наша таблица в базе
flgPresent=False
Set QS = Conn.OpenSchema(20)
Do While Not QS.EOF
TblName = Cstr(QS.Fields("TABLE_NAME").Value)
If Ucase(TblName)="DAT" then
flgPresent=True
Exit Do
End if
QS.MoveNext
Loop
QS.Close
Set QS=Nothing
If Not flgPresent then '------ Если таблицы еще нет - создаем
SQL="create table Dat (x Float, y1 Float, y2 Float, y3 Float)"
RS.Open SQL, Conn, 3, 3
Else '------ А если есть - чистим
SQL="Delete from Dat"
RS.Open SQL, Conn, 3, 3
End if
'------ Заполним таблицу
For xx=0 to 10 step 0.01 ' диапазон и шаг построения
y1=Sin(xx) ' формулы
y2=xx^2 ' формулы
y3=Exp(-0.2*xx)*Sin(5*xx) ' формулы
aX=Cstr(xx)
aY1=Cstr(y1)
aY2=Cstr(y2)
aY3=Cstr(y3)
SQL="insert into Dat Values (" & Cstr(aX) & "," & Cstr(aY1) & "," & _
Cstr(aY2) & "," & Cstr(aY3) & ")"
RS.Open SQL, Conn, 3, 3
Next
Conn.Close
Set Conn=Nothing
MsgBox "OK!"
Создадим на нашем WEB-сервере виртуальную директорию, скопируем в нее пустую базу (mybase.mdb) и приведенный выше скрипт (сохранив его в текстовом файле creatable.vbs). Теперь исполним скрипт. Если появилось сообщение "OK!", значит таблица наполнена.
Займемся ASP-сценарием.
Это должен быть простой ASP-скрипт, который берет на вход единственный параметр NG - номер графика (число 1, 2 или 3), а возвращает выходной поток следующего вида:
N/
x, y/
x, y/
...
Здесь N - число точек, x - очередное значение аргумента; y - очередное значение функции. Символ "/" - это разделитель групп.
У человека мало-мальски знакомого с ASP, текст сценария никаких затруднений не вызовет:
<%
'--- Получим номер графика. Если он неверен - принудительно
'--- ставим 1
nGr=Request("NG")
if nGr = "" then nGr=1
If (nGr > 3) Or (nGr < 1) then nGr=1
'--- Создаем соединение и recordset
Set Conn = Server.CreateObject("ADODB.Connection")
Set RS = Server.CreateObject("ADODB.Recordset")
DSNName = "DRIVER=Microsoft Access Driver (*.mdb);DBQ="
DSNName = DSNName & Server.MapPath("./mybase.mdb")
'--- Открываемся...
Conn.Open DSNName
'--- Определим число точек
SQL="Select count(*) As N From Dat"
RS.Open sql, Conn, 3, 3
N=Cstr(RS("N"))
RS.Close
'--- Передадим число точек клиенту
Response.Write N & "/"
'--- Строим строку запроса
SQL="Select x,y" & Cstr(nGR) & " From Dat order by x"
'--- Исполняем ее...
RS.Open sql, Conn, 3, 3
'--- Возвращаем ответ клиенту
Do while not RS.EOF
x =RS("x")
y=RS("y" & Cstr(nGr))
Response.Write x & "/" & y & chr(10)
RS.MoveNext
Loop
Conn.Close
Set Conn=Nothing
%>
Назовем наш сценарий gety.asp. Предполагаем, что наша база находится в той же директории, что и скрипт. Это, разумеется, не слишком безопасно, но мы строим учебный пример и хотим проверить механизм работы.
Чтобы убедиться, что сценарий работает, зайдем на наш сайт обычным браузером, набрав в строке адреса:
http://Имя_нашего_сайта/Имя_Вирт_директории/gety.asp?NG=1
Ответ должен быть примерно таким:
Получилось? Отлично! Сайт готов. Теперь займемся клиентской частью. Для этого нам потребуется Excel (97-й или 2000-й) и специальный компнент MSINET.OCX. Если на Вашем компьютере установлен VB версии 5 или 6 в Professional/Enterprise-редакции, то, скорее всего, этот компонент у Вас уже есть. Если же это не так, можете скачать приложение к статье в виде архива. В этом архиве содержится и MSINET.OCX.
Замечание для тех, у кого компнента не было.
Файл MSINET.OCX нужно извлечь из архива, скопировать в Вашу системную директорию и зарегистрировать. Регистрация выполняется так. Находясь в системной директории ..\Windows\System32, выполняем команду:
regsvr32 MSINET.OCX
Если появится сообщение об успешной регистрации - все хорошо. Если появится сообщение об ошибке - скорее всего у Вас отсутствует run-time библиотека Msvbvmxx.Dll (xx - номер версии). Хотя, трудно представить, чтобы у посетителей сайта vbrussain.com не было этой библиотеки...
Итак, компонент у нас установлен. Запускаем Excel. С помощью мастера диаграмм размещаем на первом листе диаграмму, подходящего для графика типа. Область данных для диаграммы спозиционируем за пределы видимой части листа (в колонки AA и AB, чтобы не мешала).
Слева от диаграммы разместим четыре надписи: три соответствуют трем графикам, а четвертая предназначена для очистки области диаграммы. Все это выглядит примерно так:
Теперь заходим в редактор VBA и добавляем к проекту форму. Называем форму frmWeb, устанавливаем произвольные размеры, и размещаем на форме компонент Inet. Вид и размеры формы совершенно не важны: форма используется только как контейнер для компонента Inet и никогда не будет отображаться.
У Вас нет компонента Inet в палитре? Нужно подключить. Это делается так. Щелкаем правой клавишей мыши на панели элементов управления. В появившемся контекстном меню выбираем "Дополнительные компоненты".
Появится окно дополнительных компонентов. Выбираем в нем "Microsoft Internet Transfer Control", ставим крестик и нажимаем ОК:
После этого у Вас в палитре элементов управления появится новый компонент с иконкой:
Кладем этот компонент на нашу форму. Далее нужно написать совсем немного кода. В область листа вставляем главную процедуру приема данных:
Sub AcceptData(Url As String)
Dim Buffer() As Byte
Dim XX() As Double
Dim YY() As Double
Application.ScreenUpdating = False ' Запретим ненужное мигание
Columns("AA:AB").Select ' Чистим область данных диаграммы
Selection.ClearContents
Buffer = frmWeb.Inet1.OpenURL(Url, 1) ' Получаем данные от WEB-сервера
Nb& = UBound(Buffer, 1)
If Nb&=-1 then
Msgbox "Сервер не отвечает!"
Exit Sub
End if
'::: Выделяем к-во точек
Tmp$ = ""
ii& = 0
Do
S$ = Chr$(Buffer(ii&))
ii& = ii& + 1
If S$ = "/" Then Exit Do
Tmp$ = Tmp$ + S$
Loop
BSize& = Val(Tmp$)
'::: Размещаем массив
ReDim XX(1 To BSize&) As Double
ReDim YY(1 To BSize&) As Double
'::: Извлекаем данные
kk& = 0
Do
If ii& > Nb& Then Exit Do
cX$ = ""
Do
S$ = Chr$(Buffer(ii&))
ii& = ii& + 1
If ii& > Nb& Then Exit Do
If S$ = "/" Then Exit Do
If S$ = "," Then S$ = "."
cX$ = cX$ + S$
Loop
If ii& > Nb& Then Exit Do
cY$ = ""
Do
S$ = Chr$(Buffer(ii&))
ii& = ii& + 1
If ii& > Nb& Then Exit Do
If S$ = Chr$(10) Then Exit Do
If S$ = "," Then S$ = "."
cY$ = cY$ + S$
Loop
If ii& > Nb& Then Exit Do
kk& = kk& + 1
XX(kk&) = Val(cX$)
YY(kk&) = Val(cY$)
Loop
'::: Размещаем данные
For ii& = 1 To kk&
Sheets(1).Cells(ii&, 27).Value = XX(ii&)
Sheets(1).Cells(ii&, 28).Value = YY(ii&)
Next ii&
'::: Заносим их в диаграмму
ActiveSheet.ChartObjects(1).Activate
ActiveChart.SetSourceData _
Source:=Sheets("Лист1").Range("AB1:AB" + CStr(kk&)), PlotBy:=xlColumns
ActiveChart.SeriesCollection(1).XValues = "=Лист1!R1C27:R" + _
CStr(kk&) + "C27"
Sheets(1).Range("A1").Select
Application.ScreenUpdating = True
End Sub
Немного комментариев. Эта процедура имеет единственный входной параметр - URL-строку запроса. У компонента "Microsoft Internet Transfer Control" мы используем метод OpenURL. Метод имеет два параметра: собственно адрес в URL-нотации и тип данных. Результат возвращается в байтовом массиве. Причем, размер масива устанавливается автоматически. Это очень удобно.
Получив ответ, следует первым делом определить количество принятых байтов. Это делает оператор Nb& = UBound(Buffer, 1). Если Nb& окажется равным -1, значит WEB-сервер не отвечает. А дальше нужно выделить из ответа число точек и сами точки. Как можно видеть из текста процедуры, автор помещает их в рабочие массивы. А можно было бы сразу заносить в область данных диаграммы (колонки AA и AB).Обратите внимание и на то, что для преобразования числа с плавающей точкой из символьного типа в двоичный используется функция VAL. А она не воспринимает запятую в качестве разделителя целой и дробной части. Поэтому приходится проверять наличие запятой и заменять ее точкой. (Запятая может появиться, если на WEB-сервере системный разделитель - запятая).
Осталось только добавить четыре тривиальные процедурки к этому коду:
Public Sub drawSin()
AcceptData "http://bob/Graph/gety.asp?NG=1"
End Sub
Public Sub drawSQ()
AcceptData "http://bob/Graph/gety.asp?NG=2"
End Sub
Public Sub drawExp()
AcceptData "http://bob/Graph/gety.asp?NG=3"
End Sub
Public Sub Clear()
Columns("AA:AB").Select
Selection.ClearContents
Range("A1").Select
End Sub
Первые три обеспечивают построение графиков, а последняя служит для очистки области диаграммы. Возвращаемся на главный лист и назначаем надписям процедуры-события:
-надписи "Y=Sin(x)" - DrawSin
-надписи "Y=x^2" - DrawSq
-надписи "Колебания" - DrawExp
-надписи "Очистить" - Clear.
Теперь щелкнем, например, по надписи "Колебания":
Неплохо? По-моему, тоже...
Механизм ясен. В приведенном примере мы берем данные, уже "сидящие" в таблице. Но ничего не мешает разработать asp- (или php-) скрипты, которые будут строить новые данные для отображения у клиента. В простейшем случае это может выглядеть хотя бы так: в базе находятся данные, например, об объеме продаж за 10 лет (день за днем). При запросе передается интервал дат (дата начала и дата конца интервала). Скрипт выбирает из таблицы данные, относящиеся к запрошенному интервалу, и возвращает их клиенту. А на клиентской стороне строится диаграмма. Можно даже на клиентской стороне формировать строки SQL-запросов, и передавать их на сервер. А сервер вернет результат, из которых Excel построит диаграмму.
Создавайте таблицы с данными произвольной сложности, конструируйте нужные SQL-запросы, и наслаждайтесь отличной презентационной графикой Excel! Если нужно отображать не только графики, а еще и текст - нет проблем. Текст можно точно так же извлечь из базы, передать клиенту и отобразить потом в ячейках листа или в надписях. На сервере можно использовать всю мощь SQL (включая хранимые процедуры).
Кроме того, обязательно нужно предусмотреть обработку серверных ошибок. Это не очень сложно. Переименуйте на сервере файл gety.asp, войдите в редактор VBA, поставьте точку останова на оператор Nb& = UBound(Buffer, 1), и попытайтесь построить любой график. В байтовый массив будет занесено стандратное сообщение (типа 404). Можно, привязавшись к характерным контекстам этого сообщения, распознать ошибку и выдать пользователю сообщение типа "Страница не найдена".
Описанный подход автор использует в корпоративной сети в течение ряда лет. Но что мешает использовать описанную технику в глобальном Интернете?
В прилагаемый пример автор вставил ссылку на реальный Internet-сервер. Можете убедиться, что пример будет работоспособным (только не забудьте сначала установить Internet-соединение!)
Полный текст примера и компонент MSINET.OCX можно скачать здесь.
Успехов!