Excel как WEB-клиент

Источник: vbrussian
Файфель Б.Л.

Наверное, каждый 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 можно скачать здесь.
 
Успехов!


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=29791