Сложные графики и диаграммы в ASP.NET. Часть пятая – интерактивность.

Введение

В предыдущей статье речь шла об использовании диаграмм ChartSpace, входящих в состав библиотеки owc11. Мы продолжим изучение этой библиотеки, но в более сложном применении. В данном случае нас интересует интерактивность (то есть обратная связь). Только представьте себе: пользователь увидел круговую диаграмму, нажал мышью по интересующему его сектору, и тут же получил на месте круговой диаграммы гистограмму. Причём гистограммы выводятся разные – для каждого сектора свои. Дальше можно щёлкать уже гистограмму – и в зависимости от выбранного ряда столбиков получить ту или иную реакцию сервера. На что реагирует сервер? На цвет нажатой точки. Чтобы воплотить эту мысль, нам придётся основательно пересмотреть работу с ChartSpace, выделив процедуру работы с базой данных в отдельный кодовый класс (в VB есть также специальный тип кодовых классов – модули – они проще в обращении). Нам также придётся изучить вопросы передачи информации от класса к классу, узнать побольше о цветах, и соответственно глубже копнуть библиотеку owc11.

Схема работы

Рассмотрим схему взаимодействия Вэб-страницы, содержащей диаграмму, с другими модулями (рис. 1.)

Рис. 1. Необходимые составляющие и как они связаны.

Из рисунка видно, что схема работы расширилась относительно того, как это было в 4-й статье. В 4-й статье я постарался как можно больше упростить схему, чтобы читатели быстрее вникли: работа с базой данных велась прямо через ChartSpace. Теперь же шире задачи – соответственно шире схема работы. На самом деле текущая 5-я статья ближе к реальному проекту, так как проекты, использующие диаграммы, обычно довольно наворочанные. Тем не менее, эта схема гораздо проще тех, что используют элементы управления диаграммами сторонних разработчиков. Да и гораздо надёжнее: тут Вы не зависите от ошибок и ограничений сторонних разработчиков. Ну и гораздо дешевле: библиотека owc11 является бесплатным приложением к пакету MS Office, тогда как за библиотеку вроде Infragistics, Component One и им подобных, Вам придётся раскошелиться на $1000 (пиратские версии стоят дёшево, но более сложны в применении, так как время от времени их приходится лечить от лицензионного синдрома).

Несколько слов по поводу хранения в общих переменных результатов запросов к базе данных. В ходе исследований выяснилось, что наибольшее время при построении диаграмм может уйти на запросы к базе данных. Сами запросы бывают сложными, да и время на подключение к БД и отключение играет роль. В принципе можно было бы в общих переменных хранить массивы байтов или использовать вместо общих переменных сессионные, но этого делать нельзя. Нельзя в силу ряда причин. Рассмотрим эти причины:

1. Сессий может быть очень много, поэтому в сессии хранить объёмные данные не рекомендуется. Я всегда выступал против хранения там чего бы то ни было кроме небольших строковых данных.
2. Массивы байтов могут занимать много памяти, а память на сервере не резиновая. Опыт разработки показывает, что хранение большого объёма данных в общих переменных, например результатов запросов к БД, содержащих десятки тысяч строк, приводит к неожиданным сбоям – переменные просто обнуляются! Сайт может работать какое-то время, а потом бац – и ошибка времени выполнения типа «обращение к переменной, в которой кроме NULL ничего нет». И это на сервере, имеющем 1G памяти! Причём, я уверен, что и 100G не спасут: ASP.NET обладает какими-то предельными границами (порядка 100M) вроде того, как это было в DOS-версии Бэйсика. Только средствами самого Бэйсика можно было установить предельный размер памяти. К сожалению, в ASP.NET такой возможности нет, поэтому к использованию памяти следует относиться очень бережно, экономить каждый килобайт.

Структура программы

Представим схему, изображённую на рис. 1., в более приближённом к коду виде (рис. 2.)

Рис. 2. Реализация интерактивности с помощью библиотеки кодовых классов.

Примечание

В литературе по программированию часто заходит речь о трёхуровневом программировании. Как нетрудно догадаться, рассматриваемая библиотека кодовых классов представляет из себя второй уровень (рис. 3)

Рис. 3. Трёхуровневое представление программы.

Библиотека классов Class1.cs

Рассмотрим листинг этой библиотеки. Обратите внимание на комментарии, они Вам помогут понять как чего работает. Для наглядности код раскрашен с помощью Студии VS.NET 2005.

using System; 
  using System.Web; 
  using System.Data; 
  using System.Data.SqlClient; 
  namespace MaxPro 
  { 
         public class All//простые функции 
         { 
               //var 
               public const Int32 iMaxInt32=2147483647; 
               //end var 
               // Функция IsNull как в VB6, переваривает любые типы 
               public static Boolean IsNull(Object oObject) 
               { 
                      if(oObject==null || oObject==DBNull.Value) return true; else return false; 
               } 
               // Функция IsNumeric, переваривает любые типы 
               public static Boolean IsNumeric(Object oObject) 
               { 
                      if(IsNull(oObject)) return false; 
                      try//проверка на число 
                      { 
                             Convert.ToDecimal(oObject); 
                             return true; 
                      } 
                      catch 
                      { 
                             return false; 
                      } 
               } 
               // Функция IsByte, переваривает любые типы 
               public static Boolean IsByte(Object oObject) 
               { 
                      if(IsNull(oObject)) return false; 
                      try//проверка на число типа "байт" 
                      { 
                             Convert.ToByte(oObject); 
                             return true; 
                      } 
                      catch 
                      { 
                             return false; 
                      } 
               } 
               // Функция, проверяющая совпадение цветов. Переваривает любые типы 
               public static Boolean bColor(Object oR1,Object oG1, Object oB1,Object oR2,Object oG2, Object oB2) 
               { 
                      if(!IsByte(oR1) || !IsByte(oR2) || !IsByte(oG1) || !IsByte(oG2) || !IsByte(oB1) || !IsByte(oB2)) return false; 
                      //var 
                      Double dR1=Convert.ToDouble(oR1);//красный1 
                      Double dG1=Convert.ToDouble(oG1);//зелёный1 
                      Double dB1=Convert.ToDouble(oB1);//синий1 
                      Double dR2=Convert.ToDouble(oR2);//красный2 
                      Double dG2=Convert.ToDouble(oG2);//зелёный2 
                      Double dB2=Convert.ToDouble(oB2);//синий2 
                      Double dTanA1=dG1/dR1;//1-й цвет, тангенс альфа=g/r 
                      Double dTanB1=dB1/dR1;//1-й цвет, тангенс бета=b/r 
                      Double dTanG1=dB1/dG1;//1-й цвет, тангенс гама=b/g 
                      Double dTanA2=dG2/dR2;//2-й цвет, тангенс альфа=g/r 
                      Double dTanB2=dB2/dR2;//2-й цвет, тангенс бета=b/r 
                      Double dTanG2=dB2/dG2;//2-й цвет, тангенс гама=b/g 
                      Double dD=.1;//отклонение (дельта) в долях единицы от цветов в условных обозначениях 
                      Double dDa;//=Abs(1-dTanA1/dTanA2) 
                      Double dDb;//=Abs(1-dTanB1/dTanB2) 
                      Double dDg;//=Abs(1-dTanG1/dTanG2) 
                      //end var 
                      dDa=Convert.ToDouble(Math.Abs(1-dTanA1/dTanA2)); 
                      dDb=Convert.ToDouble(Math.Abs(1-dTanB1/dTanB2)); 
                      dDg=Convert.ToDouble(Math.Abs(1-dTanG1/dTanG2)); 
                      if(dDa<=dD && dDb<=dD && dDg<=dD) return true; else return false; 
               } 
               // Функция заполнения объектного массива CommandType.StoredProcedure() 
               public static Int32 iZap0(String sConnection,String sProcedure,out Object[,] oZap,out Exception oErr) 
               { 
                      //var 
                      Int32 iReturn=-1; 
                      Int32 iX=0; 
                      Int32 iY=0; 
                      Int32 iXmax=0; 
                      Int32 iYmax=0; 
                      SqlConnection oConnection=new SqlConnection(sConnection); 
                      SqlCommand oCommand=new SqlCommand(); 
                      SqlDataReader oReader; 
                      System.Collections.ArrayList[] oCol; 
                      //end var 
                      oErr=null; 
                      oCommand.CommandType=CommandType.StoredProcedure; 
                      oCommand.CommandText=sProcedure; 
                      oCommand.Connection=oConnection;   
                      try 
                      { 
                             oConnection.Open(); 
                             oReader = oCommand.ExecuteReader(); 
                             iXmax = oReader.FieldCount; 
                             if(iXmax<=0) throw(new Exception(iXmax+" колонок.")); 
                             oCol=new System.Collections.ArrayList[iXmax]; 
                             for(iX=0;iX<=iXmax-1;iX++) 
                             { 
                                    oCol[iX]=new System.Collections.ArrayList(); 
                             } 
                             iY=-1; 
                             while(oReader.Read()) 
                             { 
                                    iY+=1; 
                                    if(iY<iMaxInt32) 
                                    { 
                                          iYmax=iY+1; 
                                          for(iX=0;iX<=iXmax-1;iX++) 
                                          { 
                                                 oCol[iX].Add(oReader[iX]); 
                                          } 
                                    } 
                                    else 
                                    { 
                                          throw(new Exception(iY+" строк (>=2 147 483 647).")); 
                                   }//end if 
                             }//end while 
                             oZap=new Object[iXmax,iYmax]; 
                             for(iX=0;iX<=iXmax-1;iX++) 
                             { 
                                    for(iY=0;iY<=iYmax-1;iY++) 
                                    { 
                                          oZap[iX,iY]=oCol[iX][iY]; 
                                    } 
                             } 
                             oReader.Close(); 
                             oReader=null; 
                             oCommand=null; 
                      } 
                      catch(Exception oError) 
                      { 
                             oZap=new Object[0,0]; 
                             oErr=oError; 
                             iYmax=-1; 
                      } 
                      finally 
                      { 
                             if(!(oConnection==null)) 
                             { 
                                    oConnection.Close(); 
                                    oConnection=null; 
                             } 
                      } 
                      iReturn=iYmax; 
                      return iReturn; 
               } 
         } 
         public class Ow//работа с БД ИмяКаталога на 10.20.30.40 (DataBaseConnectionN) 
         { 
               //var 
               public const String sConnection="user id=ИмяПользователя; password=Пароль; data source=10.20.30.40;initial catalog=ИмяКаталога"; 
               //end var 
               // Функция заполнения объектного массива CommandType.StoredProcedure() 
               public static Int32 iZap0(String sProcedure,out Object[,] oZap,out Exception oErr) 
               { 
                      return All.iZap0(sConnection,sProcedure,out oZap,out oErr); 
               } 
         } 
         public class Chart//работа с ChartSpace 
         { 
               /* String sCategories="значение1"+СимволТабуляции(символ с кодом 9)+"значение2"+... 
                * String sValues="значение1"+СимволТабуляции(символ с кодом 9)+"значение2"+... 
                */ //В VB6 также вместе со "строками через табулятор" поддерживаются одномерные массивы 
               //var 
               public const String sProcedure001="Procedure121"; 
               public const String sProcedure002="Procedure122"; 
               public const String sProcedure003="Procedure123"; 
               public const String sProcedure004="Procedure124"; 
               public const String sProcedure005="Procedure125"; 
               public static Int32 iDataLiteral=Convert.ToInt32(OWC11.ChartSpecialDataSourcesEnum.chDataLiteral); 
               public static Object[,] _oZap001; 
               public static Object[,] _oZap002; 
               public static Object[,] _oZap003; 
               public static Object[,] _oZap004; 
               public static Object[,] _oZap005; 
               //end var 
               //property 
               public static Object[,] oZap001 
               { 
                      //var 
                      //end var 
                      get 
                      { 
                             Int32 iNum=0; 
                             Exception oErr; 
                             if(_oZap001==null) 
                             { 
                                    iNum=Ow.iZap0(sProcedure001,out _oZap001,out oErr); 
                                    if(iNum<=0) 
                                    { 
                                          _oZap001=null; 
                                    } 
                             } 
                             return _oZap001; 
                      } 
               } 
               public static Object[,] oZap002 
               { 
                      //var 
                      //end var 
                      get 
                      { 
                             Int32 iNum=0; 
                             Exception oErr; 
                             if(_oZap002==null) 
                             { 
                                    iNum=Ow.iZap0(sProcedure002,out _oZap002,out oErr); 
                                    if(iNum<=0) 
                                    { 
                                          _oZap002=null; 
                                    } 
                             } 
                             return _oZap002; 
                      } 
               } 
               public static Object[,] oZap003 
               { 
                      //var 
                      //end var 
                      get 
                      { 
                             Int32 iNum=0; 
                             Exception oErr; 
                             if(_oZap003==null) 
                             { 
                                    iNum=Ow.iZap0(sProcedure003,out _oZap003,out oErr); 
                                    if(iNum<=0) 
                                    { 
                                          _oZap003=null; 
                                    } 
                             } 
                             return _oZap003; 
                      } 
               } 
               public static Object[,] oZap004 
               { 
                      //var 
                      //end var 
                      get 
                      { 
                             Int32 iNum=0; 
                             Exception oErr; 
                             if(_oZap004==null) 
                             { 
                                    iNum=Ow.iZap0(sProcedure004,out _oZap004,out oErr); 
                                    if(iNum<=0) 
                                    { 
                                          _oZap004=null; 
                                    } 
                             } 
                             return _oZap004; 
                      } 
               } 
               public static Object[,] oZap005 
               { 
                      //var 
                      //end var 
                      get 
                      { 
                             Int32 iNum=0; 
                             Exception oErr; 
                             if(_oZap005==null) 
                             { 
                                    iNum=Ow.iZap0(sProcedure005,out _oZap005,out oErr); 
                                    if(iNum<=0) 
                                    { 
                                          _oZap005=null; 
                                    } 
                             } 
                             return _oZap005; 
                      } 
               } 
               //end property 
               // Процедура заполнения категорий и значений из массива 
               public static void Zap2String(Object[,] oZap,out String sCategories,out String sValues) 
               { 
                      //var 
                      Int32 i=0; 
                      Int32 iNum=0; 
                      //end var 
                      sCategories=""; 
                      sValues=""; 
                      if(oZap==null) 
                      { 
                             sCategories="Ошибка БД"; 
                             sValues="0"; 
                             return; 
                      } 
                      iNum=oZap.GetLength(1);//число строк 
                      if(iNum<=0) 
                      { 
                             sCategories="Пусто"; 
                             sValues="0"; 
                             return; 
                      } 
                      for(i=0;i<=iNum-1;i++) 
                      { 
                             if(i==iNum-1) 
                             { 
                                    sCategories+=Convert.ToString(oZap[0,i]); 
                                    sValues+=Convert.ToString(oZap[1,i]); 
                             } 
                             else 
                             { 
                                    sCategories+=Convert.ToString(oZap[0,i])+"     ";//в конец добавляется табуляция 
                                    sValues+=Convert.ToString(oZap[1,i])+"  ";//в конец добавляется табуляция 
                             } 
                      } 
               } 
               // Функция, выдающая одну круговую диаграмму 3D (1 ряд значений). Перегрузка 1 из 2. String. 
               public static Byte[] o1PieExploded3D1s(Int32 iX,Int32 iY,String sChartCaption,String sCategories1,String sValues1) 
               { 
                      //var 
                      OWC11.ChartSpaceClass oChartSpace=new OWC11.ChartSpaceClass(); 
                      Byte[] oImg; 
                      //end var 
                      //активизация заголовка диаграммы 
                      oChartSpace.HasChartSpaceTitle=true; 
                      //создание заголовка 
                      oChartSpace.ChartSpaceTitle.Caption=sChartCaption; 
                      //создание первой диаграммы, нумерация с нуля 
                      oChartSpace.Charts.Add(0); 
                      //создание первого ряда значений, нумерация с нуля 
                      oChartSpace.Charts[0].SeriesCollection.Add(0); 
                      //заполнение ряда категорий 
                      oChartSpace.Charts[0].SeriesCollection[0].SetData(OWC11.ChartDimensionsEnum.chDimCategories,iDataLiteral,sCategories1); 
                      //заполнение ряда значений 
                      oChartSpace.Charts[0].SeriesCollection[0].SetData(OWC11.ChartDimensionsEnum.chDimValues,iDataLiteral,sValues1); 
                      //определение типа диаграммы 
                      oChartSpace.Charts[0].Type=OWC11.ChartChartTypeEnum.chChartTypePieExploded3D; 
                      //включение условных обозначений 
                      oChartSpace.Charts[0].HasLegend=true; 
                      //добавление первого слоя подписей 
                      OWC11.ChDataLabels oDataLabel1=oChartSpace.Charts[0].SeriesCollection[0].DataLabelsCollection.Add(); 
                      //добавление в первый слой подписей процентной составляющей 
                      oDataLabel1.HasPercentage=true; 
                      //добавление в первый слой подписей числовых значений 
                      oDataLabel1.HasValue=true; 
                      //определение разделяющего символа, по умолчанию точка с запятой 
                      oDataLabel1.Separator="n"; 
                      //установление цвета шрифта для первого слоя подписей 
                      oDataLabel1.Font.Color="red"; 
                      //вывод картинки в массив байтов 
                      oImg=(Byte[])oChartSpace.GetPicture("gif",iX,iY); 
                      //возврат результата 
                      return oImg; 
               } 
               // Функция, выдающая одну круговую диаграмму 3D (1 ряд значений). Перегрузка 2 из 2. Object[,] 
               public static Byte[] o1PieExploded3D1s(Int32 iX,Int32 iY,String sChartCaption,Object[,] oZap1) 
               { 
                      //var 
                      String sCategories1=""; 
                      String sValues1=""; 
                      //end var 
                      //заполняем из массива строки категорий и значений 
                      Zap2String(oZap1,out sCategories1,out sValues1); 
                      //возврат результата 
                      return o1PieExploded3D1s(iX,iY,sChartCaption,sCategories1,sValues1); 
               } 
               // Функция, выдающая одну гистограмму 3D (2 ряда значений). Перегрузка 1 из 3. Разные категории. String. 
               public static Byte[] o1ColumnClustered3D2s(Int32 iX,Int32 iY,String sChartCaption,String SeriesCaption1,String SeriesCaption2,String sCategories1,String sValues1,String sCategories2,String sValues2) 
               { 
                      //var 
                      OWC11.ChartSpaceClass oChartSpace=new OWC11.ChartSpaceClass(); 
                      Byte[] oImg; 
                      //end var 
                      //активизация заголовка диаграммы 
                      oChartSpace.HasChartSpaceTitle=true; 
                      //создание заголовка 
                      oChartSpace.ChartSpaceTitle.Caption=sChartCaption; 
                      //создание первой диаграммы, нумерация с нуля 
                      oChartSpace.Charts.Add(0); 
                      //создание первого ряда значений, нумерация с нуля 
                      oChartSpace.Charts[0].SeriesCollection.Add(0); 
                      //заполнение ряда категорий 
                      oChartSpace.Charts[0].SeriesCollection[0].SetData(OWC11.ChartDimensionsEnum.chDimCategories,iDataLiteral,sCategories1); 
                      //заполнение ряда значений 
                      oChartSpace.Charts[0].SeriesCollection[0].SetData(OWC11.ChartDimensionsEnum.chDimValues,iDataLiteral,sValues1); 
                      //создание второго ряда значений, нумерация с нуля 
                      oChartSpace.Charts[0].SeriesCollection.Add(1); 
                      //заполнение ряда категорий 
                      oChartSpace.Charts[0].SeriesCollection[1].SetData(OWC11.ChartDimensionsEnum.chDimCategories,iDataLiteral,sCategories2); 
                      //заполнение ряда значений 
                      oChartSpace.Charts[0].SeriesCollection[1].SetData(OWC11.ChartDimensionsEnum.chDimValues,iDataLiteral,sValues2); 
                      //определение типа диаграммы 
                      oChartSpace.Charts[0].Type=OWC11.ChartChartTypeEnum.chChartTypeColumnClustered3D; 
                      //включение условных обозначений 
                      oChartSpace.Charts[0].HasLegend=true; 
                      //включение названий для первой диаграммы 
                      oChartSpace.Charts[0].SeriesCollection[0].Caption=SeriesCaption1; 
                      oChartSpace.Charts[0].SeriesCollection[1].Caption=SeriesCaption2; 
                      //вывод картинки в массив байтов 
                      oImg=(Byte[])oChartSpace.GetPicture("gif",iX,iY); 
                      //возврат результата 
                      return oImg; 
               } 
               // Функция, выдающая одну гистограмму 3D (2 ряда значений). Перегрузка 2 из 3. Любые категории. Object[,]. 
               public static Byte[] o1ColumnClustered3D2s(Int32 iX,Int32 iY,String sChartCaption,String SeriesCaption1,String SeriesCaption2,Object[,] oZap1,Object[,] oZap2) 
               { 
                      //var 
                      String sCategories1=""; 
                      String sCategories2=""; 
                      String sValues1=""; 
                      String sValues2=""; 
                      //end var 
                      //заполняем из массивов строки категорий и значений 
                      Zap2String(oZap1,out sCategories1,out sValues1); 
                      Zap2String(oZap2,out sCategories2,out sValues2); 
                      //возврат результата 
                      return o1ColumnClustered3D2s(iX,iY,sChartCaption,SeriesCaption1,SeriesCaption2,sCategories1,sValues1,sCategories2,sValues2); 
               } 
               // Функция, выдающая одну гистограмму 3D (2 ряда значений). Перегрузка 3 из 3. Одинаковые категории. String. 
               public static Byte[] o1ColumnClustered3D2s(Int32 iX,Int32 iY,String sChartCaption,String SeriesCaption1,String SeriesCaption2,String sCategories1,String sValues1,String sValues2) 
               { 
                      //возврат результата 
                      return o1ColumnClustered3D2s(iX,iY,sChartCaption,SeriesCaption1,SeriesCaption2,sCategories1,sValues1,sCategories1,sValues2); 
               } 
               // Функция, выдающая ту или иную диаграмму в зависимости от кода 
               public static Byte[] oChartSpace(Int32 iKod) 
               { 
                      //var 
                      Int32 iNum=0; 
                      Object[,] oZap1; 
                      Object[,] oZap2; 
                      Exception oErr; 
                      //end var 
                      switch (iKod)//не увлекайтесь условно-постоянными результатами, они загружают память Вэб-сервера 
                      { 
                             case -1://условно-постоянный результат 
                                    return o1PieExploded3D1s(300,300,"Результаты голосования",oZap001); 
                             case 1://переменный результат 
                                    iNum=Ow.iZap0(sProcedure001,out oZap1,out oErr); 
                                    return o1PieExploded3D1s(300,300,"Результаты голосования",oZap1); 
                             case -2://условно-постоянный результат 
                                    return o1ColumnClustered3D2s(300,300,"Сказали ДА","Левые","Правые",oZap002,oZap003); 
                             case 2://переменный результат 
                                    iNum=Ow.iZap0(sProcedure002,out oZap1,out oErr); 
                                    iNum=Ow.iZap0(sProcedure003,out oZap2,out oErr); 
                                    return o1ColumnClustered3D2s(300,300,"Сказали ДА","Левые","Правые",oZap1,oZap2); 
                             case -3://условно-постоянный результат 
                                    return o1ColumnClustered3D2s(300,300,"Сказали НЕТ","Левые","Правые",oZap004,oZap005); 
                             case 3://переменный результат 
                                    iNum=Ow.iZap0(sProcedure004,out oZap1,out oErr); 
                                    iNum=Ow.iZap0(sProcedure005,out oZap2,out oErr); 
                                    return o1ColumnClustered3D2s(300,300,"Сказали НЕТ","Левые","Правые",oZap1,oZap2); 
                             default: 
                                    return o1PieExploded3D1s(300,300,"Образец диаграммы","Один  Два       Три","1      2      3"); 
                      } 
               } 
         } 
  }

Ну как Вам? Так и напрашивается сказать: «Господи, да что же это такое, неужели нельзя попроще как-то?» Ну во-первых, простые вещи закончились в 4-й статье, где нам надо было всего-то построить какой-никакой график. Во-вторых, это решение самое что ни на есть простое. Да, можно было бы обойтись без этой библиотеки. Но каждая из представленных выше процедур и функций используется по нескольку раз – так почему же эти повторяющиеся блоки не прописать вот так вот в отдельном файле? А чтобы у Вас, друзья, не осталось сомнений в правильности выбранного пути, поведаю историю, рассказанную известным писателем, Брюсом Мак Кинни. Когда Брюс был молод и только что устроился в Микрософт простым программистом, к нему подошёл начальник, посмотрел что за коды тот пишет и сказал: «Никогда не пиши один и тот же блок кода дважды пока работаешь со мной!» Кстати сказать, если Вы обратили внимание, две первые части сериала о диаграммах были тоже написаны в таком духе: в 1-й читатели знакомились с основами программирования на VB-script под owc, а во 2-й Anatoly Lubarsky уже показал как надо наводить порядок с классами.

Что ж, добавлю к многочисленным комментариям ещё несколько. Обратите внимание на то, что в файле Class1.cs одно пространство имён, но несколько классов. 1-й по счёту класс содержит функции общего назначения, которые могут быть востребованы в любых программах. 2-й по счёту класс ориентирован на работу с конкретной базой данных. Таких баз данных может быть несколько – скажем, пять разных каталогов на одном SQL-сервере, парочка каталогов на другом SQL-сервере, парочка каталогов на Oracle-е, три Access-овских базы данных и директория с файлами dBase-4. Ничего не забыл? Fox Pro вроде уже никто не использует, ну ладно, одна база данных Fox Pro. Итого мы насчитали 14 разных подключений. Стало быть, Вам потребуется 14 классов, подобных 2-му по счёту. Фактически, это будут дубли 2-го класса с разницей в строку подключения.

Обзор правил работы с базами данных пропустим, возможно, я остановлюсь на этом подробнее в другой статье, а пока довольствуйтесь, пожалуйста, одной единственной функцией iZap0. Эта функция возвращает число строк запроса на выборку. Если результат меньше нуля, то ошибка. Также обратите внимание, что результаты запроса пишутся в массив oZap, а ошибка в переменную oErr. Ничего не поделаешь, поскольку надо вернуть более одного значения, приходится писать в стиле Win API, возвращая результаты через параметры.

Осталось прокомментировать последний 3-й класс, работающий с диаграммами. Он может работать как с переменными результатами запросов к базе данных, так и с условно-постоянными. Переменные берутся каждый раз новые, вживую, так сказать. А условно-постоянные вычисляются по первому требованию и хранятся в общих переменных, доступных в отличие от сессионных всем пользователям. Обращение к общим переменным ведётся естественно через свойства, причём только для чтения. Идём дальше. Категории и значения этих категорий могут быть двух типов – строки с разделительными табуляциями (в VB это выглядело бы как Chr(9) – символ с кодом 9) и массивы. С массивами проще, так как они возвращаются функцией iZap0. Вот их-то мы и будем использовать, а строки я привёл просто так, для наглядности. Также обратите, пожалуйста, внимание, я привёл процедуры для построения двух разновидностей графиков – круговой диаграммы с одним рядом значений и гистограммы с двумя рядами значений. Каждая из процедур имеет по нескольку перегрузок: для строк и массивов. Последняя функция выдаёт диаграмму в виде байтового массива, причём используется всего один (!) параметр. Я взял из шахмат восклицательный знак в скобках, он означает сильный ход. Ход действительно сильный, если вспомнить о вызове http-обработчика. Вызывать мы его будем так: «ImageButton1.ImageUrl="Chart.ashx?kod=НекоеЦелоеЧисло";».

И ещё один важный комментарий. Обратите внимание на функцию bColor, содержащуюся в классе All. Она сравнивает цвета двух точек с дельтой 0,1 (разбег 10%). Рассмотрим как работает эта функция. Для этого нам потребуется взять с полки школьный учебник математики и вспомнить (изучить) что такое векторы. Впрочем, зачем брать учебник, если перед Вами эта статья? Тут и так будет об этом написано, просто читайте дальше и всё.

Что такое компьютерный цвет и как его распознать

Во вступлении было написано, я напомню, что мы делаем интерактивность, определяя цвет точки (пиксела), нажатой мышью в поле диаграммы. Если цвет соответствует одному из представленных в условных обозначениях (легенде), то диаграмма меняет свой лик на другую, соответствующую цвету легенды.

Просто так, втупую, определить цвет точки не получится, так как объёмные 3D-диаграммы имеют не один, а кучу цветов, похожих на эталонный, взятый из условных обозначений. Библиотека ChartSpace генерирует файлы GIF с наилучшим качеством, поэтому цветов, похожих на эталонный, могут быть десятки. И всё же эти десятки каким-то образом представляют один цвет. Меняется лишь яркость.

Цвет на экране монитора – это вектор, имеющий направление, заданное тремя координатами: красной, зелёной и синей. Длина вектора – это яркость света. Яркость представлена числом типа Byte – байт – то есть от 0 до 255 на каждый цвет. Поскольку число Byte целое, то один и тот же цвет может слегка меняться: чем меньше яркость, тем меньшее количество цветов можно представить. Поэтому для идентификации эталонного цвета нужен небольшой разбег, скажем 10%. Направление вектора, то есть цвет с фиксированной яркостью, определяется тремя углами: ?, ? и ?. А углы можно задать через тангенсы, см. рис. 4.

Рис. 4. Векторное представление цвета.

Тангенсы – это отношения проекций на каждую ось. Вот, взгляните на вырезку из функции bColor:

Double dTanA1=dG1/dR1;//1-й цвет, тангенс альфа=g/r
Double dTanB1=dB1/dR1;//1-й цвет, тангенс бета=b/r
Double dTanG1=dB1/dG1;//1-й цвет, тангенс гама=b/g

Считается, что цвета одинаковы, если их тангенсы отличаются не на много друг от друга. Для данного применения вполне достаточно ограничить отличие 10-ю процентами:

Double dD=.1;//отклонение (дельта) в долях единицы от цветов в условных обозначениях

В образце кода Вы также сможете увидеть ещё один параметр цвета – прозрачность. В нашем случае цвет всегда непрозрачный, и параметр равен максимальному значению 255.

Http-обработчик (HttpHandler) Chart.ashx

Дорогие друзья, с помощью библиотеки Class1.cs мы настолько разгрузили все остальные части программы, что на http-обработчик смешно смотреть, там нет никакого кода:

<%@ WebHandler language="C#" class="HandlerChart.ChartHttpHandler" %> 
  // Имя этого HTML+C# файла Chart.ashx и компилировать его не надо. 
  using System; 
  using System.Web; 
  using MaxPro; 
  namespace HandlerChart 
  { 
         public class ChartHttpHandler : IHttpHandler 
         { 
               // Переопределяем метод ProcessRequest. 
               public void ProcessRequest(HttpContext context) 
               { 
                      //var 
                      Byte[] oImg; 
                      //end var 
                      //генерация бинарного потока в формате gif 
                      oImg=Chart.oChartSpace(Convert.ToInt32(context.Request.QueryString["kod"])); 
                      context.Response.ContentType="image/gif"; 
                      context.Response.OutputStream.Write(oImg,0,oImg.Length); 
               } 
               // Переопределяем свойство IsReusable. 
               public bool IsReusable 
               { 
                      get { return true; } 
               } 
         } 
  }

Как заметил в своё время известный чемпион Москвы по пауэрлифтингу, Евгений Шатров, если на грифе нету блинов, то нету и веса (хотя я с этим отсутствием веса должен был присесть не один десяток раз). Тоже самое здесь: поскольку кодов всего несколько строчек, то нечего и комментировать. Замечу только, что этот код раскрашен разными цветами с помощью Студии VS.NET 2005. 2003-я Студия ASHX не красит! Ещё одно важное замечание: если Вы работаете в VS.NET 2003 и скорость работы Студии Вас устраивает, то это не значит, что 2005-я Студия работает так же быстро. У меня, например, ушло 30 секунд на загрузку этого коротенького Chart.ashx в 2005-й Студии. Как я уже писал в других публикациях, Микрософт добавляет к новым версиям специальные замедляющие коды, поэтому чтобы комфортно и главное быстро работать в среде VS.NET 2005, Вам придётся поменять свой компьютер на новый с Intel гипертрейдинг или двуядерным AMD. При этом тактовая частота «по пентиуму» должна быть не менее 3GHz (рекомендуется 4GHz), а память не менее 1Gb (рекомендуется 2G). Пожалуйста, предупредите об этом Ваше руководство сейчас, чтобы они успели включить деньги, необходимые на замену системного блока, в бюджет следующего года или в смету расходов следующего месяца. Иначе может возникнуть ситуация, при которой Генеральный директор подпишет бумагу на закупку компьютера, а финансисты, экономисты или главбух заблокируют. Имейте ввиду: графики платежей готовятся заранее, а на крупных предприятиях, таких как НК «ЮКОС», они готовятся за целый месяц до начала планируемого!

Вэб-страница WebForm5.aspx

Эта страница почти так же мала как и Chart.ashx, вот взгляните, пожалуйста, на её листинг:

Рис. 5. Внешний вид страницы:

Исходный код:

<% @ Page language="c#" Codebehind="WebForm5.aspx.cs" AutoEventWireup="false" Inherits="Demo.WebForm5" %> 
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >  
  <html> 
         <head> 
               <title>WebForm5</title> 
               <meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1"> 
               <meta name="CODE_LANGUAGE" Content="C#"> 
               <meta name="vs_defaultClientScript" content="JavaScript"> 
               <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> 
         </head> 
         <body MS_POSITIONING="GridLayout"> 
               <form id="Form1" method="post" runat="server"> 
                      &nbsp; 
                      <asp:ImageButton id="ImageButton1" style="Z-INDEX: 104; LEFT: 8px; POSITION: absolute; TOP: 8px" 
                             runat="server" ImageUrl="Chart.ashx?kod=-1"></asp:ImageButton> 
                      <asp:Label id="Label1" style="Z-INDEX: 101; LEFT: 320px; POSITION: absolute; TOP: 8px" runat="server" 
                             Font-Size="12pt">Координаты:</asp:Label> 
                      <asp:Label id="Label2" style="Z-INDEX: 102; LEFT: 320px; POSITION: absolute; TOP: 40px" runat="server">Цвет:</asp:Label> 
                      <asp:Panel id="Panel1" style="Z-INDEX: 103; LEFT: 320px; POSITION: absolute; TOP: 72px" runat="server" 
                             Width="56px" Height="56px"></asp:Panel> 
                      <asp:Label id="Label3" style="Z-INDEX: 105; LEFT: 320px; POSITION: absolute; TOP: 136px" runat="server">Попадание:</asp:Label> 
               </form> 
         </body> 
  </html> 

Раздельный код:

  using System; 
  using System.Collections; 
  using System.ComponentModel; 
  using System.Data; 
  using System.Drawing; 
  using System.Web; 
  using System.Web.SessionState; 
  using System.Web.UI; 
  using System.Web.UI.WebControls; 
  using System.Web.UI.HtmlControls; 
  using MaxPro; 
  namespace Demo 
  { 
         /// <summary> 
         /// Summary description for WebForm5. 
         /// </summary> 
         public class WebForm5 : System.Web.UI.Page 
         { 
               //var 
               protected System.Web.UI.WebControls.ImageButton ImageButton1; 
               protected System.Web.UI.WebControls.Label Label1; 
               protected System.Web.UI.WebControls.Label Label2; 
               protected System.Web.UI.WebControls.Label Label3; 
               protected System.Web.UI.WebControls.Panel Panel1; 
               //end var 
               // Блок инициализации страницы 
               #region Web Form Designer generated code 
               override protected void OnInit(EventArgs e) 
               { 
                      // 
                      // CODEGEN: This call is required by the ASP.NET Web Form Designer. 
                      // 
                      InitializeComponent(); 
                      MyInitializeComponent(); 
                      base.OnInit(e); 
               } 
               /// <summary> 
               /// Required method for Designer support - do not modify 
               /// the contents of this method with the code editor. 
               /// </summary> 
               private void InitializeComponent() 
               {     
                      this.Load += new System.EventHandler(this.Page_Load); 
               } 
               // Защита от стирания конструктором Вэб-форм 
               private void MyInitializeComponent() 
               { 
                      this.ImageButton1.Click+=new System.Web.UI.ImageClickEventHandler(this.ImageButton1_Click); 
               } 
               #endregion 
               // Процедура загрузки страницы 
               private void Page_Load(object sender, System.EventArgs e) 
               { 
                      Label1.Text=""; 
                      Label2.Text=""; 
                      Label3.Text=""; 
                      Panel1.BackColor=Color.Empty; 
               } 
               // Процедура обработки нажатия мыши на картинке 
               private void ImageButton1_Click(Object sender,ImageClickEventArgs e) 
               { 
                      if(ImageButton1.ImageUrl!="Chart.ashx?kod=-1") 
                      { 
                             ImageButton1.ImageUrl="Chart.ashx?kod=-1"; 
                             return; 
                      } 
                      //var 
                      Int32 iX=e.X;//координата X нажатой точки 
                      Int32 iY=e.Y;//координата Y нажатой точки 
                      Double dR;//цвет нажатой точки, красная составляющая 
                      Double dG;//цвет нажатой точки, зелёная составляющая 
                      Double dB;//цвет нажатой точки, синяя слставляющая 
                      Int32 iA;//прозрачность 
                      Double dR1=128;//красная составляющая 1-го цвета условных обозначений (синего) 
                      Double dG1=128;//зелёная составляющая 1-го цвета условных обозначений (синего) 
                      Double dB1=255;//синяя составляющая 1-го цвета условных обозначений (синего) 
                      Double dR2=128;//красная составляющая 2-го цвета условных обозначений (фиолетового) 
                      Double dG2=32;//зелёная составляющая 2-го цвета условных обозначений (фиолетового) 
                      Double dB2=96;//синяя составляющая 2-го цвета условных обозначений (фиолетового) 
                      //переменные, хранящие образ картинки 
                      Byte[] oImg=Chart.oChartSpace(-1);//диаграмма в байтах 
                      System.IO.MemoryStream oStream=new System.IO.MemoryStream(oImg);//диаграмма в потоке 
                      Bitmap oBitmap=new Bitmap(oStream);//диаграмма в картинке 
                      //end var 
                      dR=oBitmap.GetPixel(e.X,e.Y).R; 
                      dG=oBitmap.GetPixel(e.X,e.Y).G; 
                      dB=oBitmap.GetPixel(e.X,e.Y).B; 
                      iA=oBitmap.GetPixel(e.X,e.Y).A; 
                      Label1.Text="Координаты: (x,y) = ("+iX+","+iY+")"; 
                      Label2.Text="Цвет: (r,g,b,a) = ("+dR+","+dG+","+dB+","+iA+")"; 
                      Panel1.BackColor=oBitmap.GetPixel(iX,iY); 
                      if(All.bColor(dR,dG,dB,dR1,dG1,dB1)) 
                      { 
                             Label3.Text="Попадание в синий"; 
                             ImageButton1.ImageUrl="Chart.ashx?kod=-2"; 
                      } 
                      else if(All.bColor(dR,dG,dB,dR2,dG2,dB2)) 
                      { 
                             Label3.Text="Попадание в фиолетовый"; 
                             ImageButton1.ImageUrl="Chart.ashx?kod=-3"; 
                      } 
                      else 
                      { 
                             Label3.Text="Другой цвет"; 
                      } 
               } 
         } 
  }

Изучая приведённый код, обратите, пожалуйста, внимание, что обращения к общим функциям происходят через пространство имён MaxPro:

using MaxPro;

стоящую в начале Вэб-страницы и http-обработчика. Также, пожалуйста, обратите внимание на процедуру

// Защита от стирания конструктором Вэб-форм
               private void MyInitializeComponent()

Эта процедура не генерируется автоматически, напротив она создана вручную с целью обезопасить код от так называемой «автоматической порчи». Всё дело в том, что C# в рамках VS.NET 2003, в отличие от VB, спроектирован менее скрупулезно, и при работе c ним нужен глаз да глаз. В частности, код может самопроизвольно испортится, если вовремя не обезопаситься с помощью процедуры MyInitializeComponent, в которой следует разместить все ссылки на события кроме Page_Load.

В данном примере строится круговая диаграмма с двумя областями – синей и фиолетовой (результат работы хранимой процедуры Procedure121). При нажатии мышью на синей области строится гистограмма из процедур Procedure122 и 123. При нажатии на фиолетовой области строится другая гистограмма из процедур Procedure124 и 125. Если же пользователь промахнулся, то диаграмма остаётся прежней. Также выводятся координаты нажатой точки и её цвет. Самое время вспомнить о цвете. Объёмные 3D-диаграммы содержат несколько десятков оттенков одного и того же цвета. Эталонным цветом является цвет, взятый из условных обозначений. Скажем, если эталонный цвет синий, то диаграмма содержит разные синие цвета – от светло-синего до тёмно-синего. Этим достигаются объёмные эффекты. Для сравнения выбранного мышью цвета с эталонным используется функция bColor, работа которой была описана выше. Эталонные цвета примера – синий и фиолетовый были выбраны библиотекой ChartCpace автоматически исходя из количества необходимых цветов. Если бы круговая диаграмма содержала три сектора, то третий был бы жёлтым. Такая предопределённость наруку программисту, поскольку эталонные цвета можно заранее вычислить.

Класс Bitmap позволяет узнать цвет точки по её координатам. Но конструктор new этого класса не поддерживает массив байтов. Наиболее подходящая перегрузка конструктора ориентирована на поток. Только имейте ввиду, что перевести массив байтов в простой поток Stream не получится, следует использовать MemoryStream.

После запуска на форме появляется круговая диаграмма, содержащая результаты голосования. При нажатии мышью на итогах голосования картинка меняется на гистограмму, отражающую соответствующие положительные или отрицательные итоги голосования. Наряду с этим сервер выдаёт координаты нажатой точки, её цвет (r,g,b,a) и образец цвета.

Рассмотрим хранимые процедуры

CREATE PROCEDURE Procedure121 
  AS 
  SELECT     'за', 428 
  UNION 
  SELECT     'против', 89 
  GO
  CREATE PROCEDURE Procedure122 
  AS 
  SELECT     '1999г', 33 
  UNION 
  SELECT     '2005г', 400 
  GO
  CREATE PROCEDURE Procedure123 
  AS 
  SELECT     '1999г', 100 
  UNION 
  SELECT     '2005г', 28 
  GO
  CREATE PROCEDURE Procedure124 
  AS 
  SELECT     '1999г', 144 
  UNION 
  SELECT     '2005г', 9 
  GO
  CREATE PROCEDURE Procedure125 
  AS 
  SELECT     '1999г', 20 
  UNION 
  SELECT     '2005г', 80 
  GO

Рассмотрим какие получаются графики

Рис. 6. Результат запуска Вэб-страницы.

Рис. 7. Результат нажатия на синей области.

Рис. 8. Результат нажатия на фиолетовой области.

Рис. 9. Результат промаха.

Рис. 10. Результат загрузки http-обработчика без параметров.

Замечание по поводу размещения проекта на хостовом сервере

Есть один момент, из-за которого хостовый сервер может отказаться работать с графиками. Это ошибка типа «не могу найти библиотеку такую-то». Причина ошибки в том, что сервер ищет необходимую ему сборку в папке bin, и, не найдя её там, обращает свой взор в глобальный кэш сборок (GAC). Понятно, что в глобальном кэше скорее всего не окажется необходимой сборки, и тогда возникает такая ошибка. Пожалуйста, будьте внимательны при размешении сборок в папке bin. Если у Вас на localhost-е всё работает, это не значит, что всё так же гладко должно пройти и на хостовом сервере. И дело не в том, что у Вас стоит MS-Offoce, а там нет. Дело в способе регистрации библиотек, которые Вы добавили в Ваш проект в папку References. Поэтому на хостовый сервер MS-Office ставить не надо, а надо лишь зарегистрировать там необходимые библиотеки dll (сборки). Есть два способа регистрации – динамический (рекомендуется) и вручную.

Динамический способ регистрации сборок (рекомендуется)

Пройдитесь по всем библиотекам, добавленным автоматически Студией в папку References проекта при добавлении компонента COM «Microsoft Office Web Components 11.0». И измените свойство Copy Local каждой библиотеки, установив его в True, как показано на рисунке 11. Ради смеха скажу, что регистрировать .NET-овские сборки, вроде System.dll, не надо, они и так уже зарегистрированы по умолчанию.

Рис. 11. Динамический способ регистрации сборок.

После этого нам остаётся перестроить проект. В папку bin будут добавлены необходимые сборки, которые Вы перенесёте на хостовый сервер. Это и есть динамическая регистрация.

Регистрация сборок вручную

Эта регистрация трудоёмка, по этому не рекомендуется. Если же Вы всё-таки решили дерзнуть, то специально для таких людей я скопировал инструкции MSDN:

Пакет .NET Framework SDK предоставляет Программу установки служб .NET Framework (Regsvcs.exe) для ручной регистрации сборки, содержащей компоненты служб. Regsvcs.exe является служебной программой командной строки. Кроме того, можно получить доступ к данным возможностям регистрации программно, через класс System.EnterpriseServices.RegistrationHelper, создав экземпляр класса RegistrationHelper и используя метод InstallAssembly.

Хотя это и не всегда требуется, часто бывает полезно зарегистрировать сборки вручную для проверки на этапе разработки. В отличие от динамической регистрации регистрация вручную обеспечивает выдачу уведомлений о встреченных во время выполнения ошибках.

Примечание. При создании серверного приложения сборка и все сборки, от которых она зависит, должны быть добавлены в глобальный кэш сборок (GAC) при помощи установщика Windows до использования приложения сервера; иначе приложение выдаст исключение.

Следующая таблица перечисляет шаги, выполняемые Regsvcs.exe (или API), и описывает возможные сбои на каждом шаге.

Шаг

Возможные сбои

Результат

Загружает сборку.

Произошла ошибка при загрузке сборки.

Отображает сообщение об ошибке и описание сбоя.

Регистрирует сборку.

Произошла ошибка при регистрации типа.

Неправильно указанный результат сборки приводит к возникновению исключения TypeLoadException.

Создает библиотеку типов.

Произошла ошибка при генерации.

Неправильно указанный результат сборки приводит к возникновению исключения TypeLoadException.

Вызывает метод LoadTypeLibrary для регистрации библиотеки типов.

Произошла ошибка при вызове Automation.

Создает исключение TypeLoadException.

Устанавливает библиотеку типов в запрошенное приложение.

Программа регистрации сборок (Regasm.exe) не может найти указанное приложение.

Отображает сообщение об ошибке «Один из объектов не найден.»

Для решения проблемы укажите местоположение указанной библиотеки типов и приложения.

Конфигурирует класс.

Программа регистрации обнаружила несоответствие атрибутов служб при регистрации, например, если есть класс с противоречивыми свойствами, такими как:

TransactionOption.Required

SynchonizationOption.Disabled

Отображает ошибку, описывающую конфликт, или изменяет одну из несоответствующих служб.

Заключение

Итак, Вы можете смело копировать коды, приведённые в этой статье к себе в проект и работать с графиками по аналогии. Особенно я рекомендую библиотеку Class1.cs, с её помощью Вы на удивление быстро построите любые диаграммы, добавив всего несколько строк кода. А если Вам не хватит двух типов рассмотренных диаграмм, то сможете без труда добавить самостоятельно любое их количество.

Ну вот и всё. Приятной работы, друзья!