ASPNL logo (1 kb)
Saturday, February 04, 2012




Microsoft MVP

.NET Codewise Community
<< vorige | overzicht | volgende >>

Grafieken in ASP m.b.v. SVG

Door Johan vanden Borre
22 december 2003

Grafieken in ASP kunnen op verschillende manieren opgebouwd worden. Sommigen gebruiken excel objecten, office web components, of gaan gewoon in html dynamisch opgebouwde tabellen inkleuren. In dit artikel zou ik u willen laten kennis maken met wat momenteel toch een vrij "hot" topic is op het internet, namelijk SVG, wat staat voor Scalable Vector Graphics. SVG laat ons toe om zelf vectoriele tekeningen te gaan aanmaken en het is een op XML gebaseerde programmeertaal, en perfect geintegreerd met W3C standaarden zoals de DOM.

In SVG wordt er hoofdzakelijk gebruik gemaakt van x en y coördinaten om de vectors te tekenen. Bijvoorbeeld als rechte willen tekenen zullen we dus 2 keer een x en y coördinaat nodig hebben, of we kunnen gebruik maken van een wiskundige formule om een bepaalde vorm te creëeren. Onderstaande tekst verduidelijkt hoe ik SVG heb gebruikt om grafieken te genereren. Details over de syntax van SVG tags laat ik hier bewust achterwege, aangezien er op het internet voldoende informatie over terug te vinden is, en aangezien ik nog een leek ben in dit vak.

De opdracht was de volgende:
Uit een project-management database worden regelmatig snapshots genomen over de toestand van een project, zodat gedurende de loop van het project veranderingen kunnen worden opgevolgd en vergelijkingen kunnen worden gemaakt. Een van de vergelijkingen was het "overzicht milestones", waarbij verschuivingen van milestones in de tijd grafisch worden weergegeven. De eigenlijke gegevens worden in deze vorm aangeboden:

U kan de gegevens als volgt interpreteren:
HistoID 1 is een snapshot genomen op 16/06/2003, waarbij de milestone voor het beëindigen van de GTU-fase van het project gedefinieerd staat op 30/07/2003. Historiek 4, genomen op datum 14/08/2003 vertelt ons dat de GTU-fase beëindigd werd op 11/08/2003, meer dan één week later dan aanvankelijk geplanned. Het is nu de bedoeling om deze verschuivingen grafisch weer te wegen. Om het gemakkelijker te maken voor dit voorbeeld, heb ik de gegevens in een tabel SVG2 geladen in MS Access.
De ASP code zal dus als doel hebben om de SVG-tags op te bouwen, m.a.w. het opbouwen van tekenreeksen, en het berekenen van x- en y-coördinaten.

Instellen van het content type,zodat het door de browser wordt herkend als zijnde SVG.

<% response.contenttype = "image/svg" %>

Eigenlijke genereren van grafiek:
Hiervoor worden verschillende SVG-tags gebruikt zoals hoofdzakelijk:
<LINE … /> om rechten te tekenen,
<TEXT> om tekst op de grafiek te plaatsen
<POLYGON …/> voor het omschrijven van driehoeken
De asp code is voorzien van het nodige commentaar, zodat het wat handiger is om doorheen de code te lezen!

<%
Dim strsql, conn, rs, MinDate, MaxDate, intDays
Dim X, Y, DistanceFromBorder, DistanceFromTop, ColspanX, ColspanY
Dim DistanceFromX, DistanceFromY, XMarkers, YMarkers
Dim FirstDayOfGrid, LastDayOfGrid, Teller, prevposX, prevposY

‘settings: x-as is 550 pixels lang, en begint op een afstand van 75 pixels
‘van de kant van het scherm.

DistanceFromBorder = 75
DistanceFromTop = 75
XLength = 550
YLength = 700

‘pagina hoofding aanmaken
Response.Write "<?xml version=""1.0"" standalone=""no""?>"
Response.Write "<!DOCTYPE svg PUBLIC ""-//W3C//DTD SVG 1.0//EN"" ""http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"">"

‘svg document beginnen: altijd tussen <SVG></SVG> tags
Response.Write "<svg width=""100%"" height=""100%"">"

‘tekenen van de X en de Y-as (<LINE/> tag)
Response.Write "<line x1=""" & DistanceFromBorder & """ y1=""" & DistanceFromTop &
""" x2=""" & XLength & """ y2=""" & DistanceFromTop &
""" style=""stroke:rgb(0,0,0);stroke-width:1""/>"
Response.Write "<line x1=""" & DistanceFromBorder & """ y1=""" & DistanceFromTop &
""" x2=""" & DistanceFromBorder & """ y2=""" & YLength &
""" style=""stroke:rgb(0,0,0);stroke-width:1""/>"

    ‘Markeringen op Y-as tekenen
Set Conn = Server.Createobject("ADODB.Connection")
Set rs = Server.Createobject("ADODB.RecordSet")
conn.Open "DSN=SVG"
strsql = "select count(*) from (SELECT DISTINCT histoid FROM SVG2) DUMMY "
rs.Open strsql, conn
If Not rs.EOF Then
   YMarkers = rs(0)
   ‘geeft het aantal punten die moeten getekend worden op de Y-as
   ‘(per datum een kort streepje)

End If
rs.Close

ColspanY = YLength / (YMarkers + 1) ‘gelijke afstand tussen de streepjes berekenen

‘streepjes tekenen (<LINE/> tag), (distance from border + 5
‘ om ze lichtjes te laten uitspringen uit de Y-as)

For teller = 1 To YMarkers
   Response.Write "<line x1=""" & DistanceFromBorder & """ y1=""" &
   DistanceFromTop + (teller*ColspanY) & """ x2=""" & DistanceFromBorder + 5 & """ y2=""" & DistanceFromTop + (teller*ColspanY) & """ style=""stroke:rgb(0,0,0);stroke-width:1""/>"
Next

‘schrijven van de datums op de Y-as als text (<TEXT></TEXT> tags)
strsql = "select distinct histodate from svg2 order by histodate desc;"
rs.Open strsql, conn
If Not rs.EOF Then
   For teller = 1 To YMarkers
      Response.Write "<text x=""" & DistanceFromBorder - 55 & """ y=""" &
      DistanceFromTop + (teller*ColspanY) & """ style=""color:#000000;font-style:normal;font-size:11"">" & ConvertDate(rs(0)) & "</text>"
      rs.MoveNext
   Next
End If   
rs.close

    ‘Markeringen op X-as tekenen
'get daterange (mindate vs maxdate) in milestones

strSql = "select min(datum) FROM SVG2"
rs.Open strsql, conn
If Not rs.EOF Then
   MinDate= rs(0)
End If
rs.Close
strSql = "select max(datum) FROM SVG2"
rs.open strsql, conn
If Not rs.EOF Then
   MaxDate= rs(0)
End If
rs.Close

‘eerste datum bepalen (een week eerder dan in de DB), anders wordt de ‘(0,0) positie de eerste datum uit de DB, wat geen goed resultaat oplevert.
FirstDayOfGrid = GetFirstDate(DateAdd("D",-7,MinDate),2,0)

‘Laatste dag van de week die MaxDate bevat.
LastDayOfGrid = GetLastDate(MaxDate,2,0)

‘Aantal dagen tussen eerste en laatste dag op de X-as berekenen.
intDays = DateDiff("d",FirstDayOfGrid,LastDayOfGrid)

'gelijke afstand tss de streepjes bepalen (+14 om voldoende ‘plaats te hebben)
ColspanX = XLength / (intDays + 14)

‘markeringen tekenen:
For teller = 1 To intDays
   '7 = 1 marker per 7 days
   If teller mod 7 = 0 Then
      ‘lijntje tekenen:
      Response.Write "<line x1=""" & DistanceFromBorder +
      (teller*ColspanX) & """ y1=""" & DistanceFromTop & """
      x2=""" & DistanceFromBorder + (teller*colspanX) & """ y2=""" & DistanceFromTop + 5 & """ style=""stroke:rgb(0,0,0);stroke-width:1""/>"
      ‘Datum schrijven (geroteerd wegens plaatsgebrek)
      Response.Write "<g style=""color:#000000;font- style:normal;font-size:11"">"
      Response.Write "<text transform=""translate(" &
Replace(cstr(DistanceFromBorder + (teller*ColspanX)),",",".") & "," & DistanceFromTop - 5 & ") rotate(270)"">" & ConvertDate(DateAdd("D",teller,FirstDayOfGrid)) & "</text>"
      Response.Write "</g>"
   End If
Next

Wat betreft de berekening van x en y coördinaten, heb ik wel een opmerking. Het is aangewezen om met kommagetallen met een punt te werken in plaats van met een komma, aangezien er anders fouten zullen optreden. Daarom maak ik soms gebruik van de replace functie om “,” te vervangen door “.”, ofwel kan je ook de Round() functie gebruiken, waarbij je dan afrondt op de eenheid.

‘driehoeken tekenen
strSql = "select * FROM SVG2 order by type, histodate DESC "
rs.open strsql, conn
prevposX = 0 'previous polygon X position (+/-)
prevposY = 0 'previous polygon Y position (+/-)
If Not rs.eof Then
   While not rs.eof
      ‘for each marker on the Y-axis (each marker is a histodate)
      For teller = 1 To YMarkers
         ‘get x-coordinate for mst-date:
         ‘1.get difference in days between mst-date & first day of grid
         diffDays = DateDiff("d",FirstDayOfGrid,rs("datum"))
         ‘2. the colspan per day is know, so now calc distance to datum
         distanceX = diffDays * ColspanX
         ‘get y-coordinate for histodate/histoID
         distanceY = teller * ColspanY
         ‘draw polygon
         Response.Write "<polygon points=""" &
cStr(Round(DistanceFromBorder-5+distanceX,0)) & "," & cStr(Round(DistanceFromTop + distanceY,0)) & " " & cStr(Round(DistanceFromBorder+distanceX,0)) & "," & cStr(Round(DistanceFromTop+distanceY+5,0)) & " " & cStr(Round(DistanceFromBorder + distanceX + 5,0)) & "," & cStr(Round(DistanceFromTop + distanceY,0)) & """ style=""fill:rgb(126,14,83);stroke:rgb(126,14,83);stroke-width:1""/>"
         If prevposX <> 0 And prevposY <> 0 Then
‘draw connectionline from previous polygon to current ‘polygon if there is any (<>0)
Response.Write "<line x1=""" & cStr(Round(prevposX,0)) &
""" y1=""" & cStr(Round(prevposY,0)) & """ x2=""" & cStr(Round(DistanceFromBorder + distanceX,0)) & """ y2=""" & cStr(Round(DistanceFromTop + distanceY,0)) & """ style=""stroke:rgb(0,0,0);stroke-width:1""/>"
         End If
         prevposX = DistanceX + DistanceFromBorder
         prevposY = DistanceY + DistanceFromTop
         rs.MoveNext
      Next
      prevposX = 0
      prevposY = 0
   Wend
End If

    Set rs = Nothing
conn.Close
Set conn = Nothing
Response.Write "</svg>" ‘close SVG tag
%>

Hieronder staan enkele algemene, nuttige functies, die ik regelmatig gebruik in ASP applicaties, alsook in de code hierboven.

<%
Public Function ConvertDate(strDate)
   dim m, d, y
   m = month(strDate)
   d = day(strDate)
   y = year(strDate)
   ConvertDate = d & "/" & m & "/" & y
End Function
%>

<%
Private function GetFirstDate(datum, pPeriod, interval)
‘gets the first day of the week/month of a certain period (pperiod),
‘changed by interval

   Select Case pperiod
      Case 1 'maandelijks
         dtLoc = DateAdd("M",interval,datum)
         Do While Day(dtLoc) > 1
            dtLoc = DateAdd("d",-1,dtLoc)
         loop
         GetFirstDate = MonthName(Month(ConvertDate(dtLoc)))
      Case 2 'wekelijks
         dtLoc = DateAdd("W",interval * 7,datum)
         While WeekDay(dtLoc,2) <> 1
            dtLoc = DateAdd("D",+1,dtLoc)
         Wend
         GetFirstDate = ConvertDate(dtLoc)
   End Select
End Function
%>
<%

Private function GetLastDate(datum, pPeriod, interval)
‘gets the last day of the week/month of a certain period (pperiod),
‘changed by interval

   Select Case pperiod
      Case 1 'maandelijks
         intDaysInMonth = DaysInMonth(month(datum),year(datum))
GetLastDate = cstr(intDaysInMonth) & cstr(month(datum)) & _ cstr(year(datum))
      Case 2 'wekelijks
         dtLoc = DateAdd("W",interval * 7,datum)
         While WeekDay(dtLoc,2) <> 7
            dtLoc = DateAdd("D",+1,dtLoc)
         Wend
         GetLastDate = ConvertDate(dtLoc)
   End Select
End Function
%>
<%
Private Function DaysInMonth(MM, YY)
   DaysInMonth = DateSerial(YY,MM + 1,0) - DateSerial(YY,MM + 1,1) + 1
End Function
%>

Zoals je kan zien ligt de moelijkheid in het bepalen van alle nodige coördinaten, maar is SVG op zich zeker niet zo heel moeilijk, aangezien je een maar een beperkte set van instructies ter beschikking hebt. De resulterende grafiek kan je hier zien :

De grafiek kan naderhand nog worden uitgebreid zodat bij elke lijn ook de fase van het project wordt vermeld, of je zou kleurcodes kunnen toepassen op elke lijn, die dan in een legende verder worden toegelicht.

Standaard biedt de plugin ook handige functionaliteiten zoals een zoomfunctie, die je kan bekomen door met de rechtermuisknop op de afbeelding te klikken. Het enige nadeel dat ik persoonlijk aan deze oplossing heb ondervonden is het afdrukken, waarbij de schaal van de oorspronkelijke tekening niet altijd wordt bewaard. Helaas heb ik hier nog geen oplossing voor gevonden.

Wel, tot zover dit voorbeeld ter illustratie van de mogelijkheden van SVG. Deze toepassing ervan is al iets complexer, maar een goede tutorial voor de geinteresseerden kan je vinden op: http://www.webreference.com/authoring/languages/svg/

En zoals altijd: Happy programming! Enjoy!

<< vorige | ^ naar boven | overzicht | volgende >>
copyright 2000-2007 ASPNL