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
DistanceFromBorder = 75
DistanceFromTop = 75
XLength = 550
YLength = 700
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"">"
Response.Write "<svg width=""100%"" height=""100%"">"
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""/>"
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)
End If
rs.Close
ColspanY = YLength / (YMarkers + 1)
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
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
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
FirstDayOfGrid = GetFirstDate(DateAdd("D",-7,MinDate),2,0)
LastDayOfGrid = GetLastDate(MaxDate,2,0)
intDays = DateDiff("d",FirstDayOfGrid,LastDayOfGrid)
ColspanX = XLength / (intDays + 14)
For teller = 1 To intDays
If teller mod 7 = 0 Then
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""/>"
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.
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 teller = 1 To YMarkers
diffDays = DateDiff("d",FirstDayOfGrid,rs("datum"))
distanceX = diffDays * ColspanX
distanceY = teller * ColspanY
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
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>"
%>
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)
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
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)
Select Case pperiod
Case 1
intDaysInMonth = DaysInMonth(month(datum),year(datum))
GetLastDate = cstr(intDaysInMonth) & cstr(month(datum)) & _ cstr(year(datum))
Case 2
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!
|