In this exercise, we will write XQuery to output SVG in the form of a timeline infographic, working with the Banksy project files. Your SVG timeline might look something like ours: something like this (with the colors, text positioning, fonts, and styling up to you). Our timeline plots the range of years in which Banksy has been known to be producing artwork, using the area of geometric shapes (circles) to indicate which years seem to feature the most intensive activity based on the Banksy XML database. We have also plotted in side-by-side aqua and lavender bars the relative proportion of spray-paint to canvas media productions by Banksy for each year. (Our SVG should really include a legend documenting this shape and color-coded information, and we encourage you to create one on your own graph as you work on this exercise.)
You should work in our newtFire eXist-db to complete this exercise, since it is easier to view SVG live
as you are constructing it in its Direct Output view. Start an XQuery with the following global variable defining the location of our banksy collection:
declare variable $banksyColl := collection('/db/Assignments/banksyForSVG/');
Open one or two of the files in the collection in eXist-db in order to familiarize yourself with its contents. All of the information we will be working with for this infographic is stored in the sourceDesc
element. You may want to write some more global variables to help familiarize yourself with how to find the title, medium, and date information coded in the collection. The structure of our work in XQuery to generate an SVG file will require us to start with global variables, and then work with FLWOR statements when we begin encoding the SVG document.
.
Our tasks are:
To help get you started, here is how we are organizing our code. We will start by defining a series of global variables that pull year values from the Banksy collection, converting those years to integers, and make some calculations. We declare a global variable to hold an SVG file, and then we begin writing SVG. The structure is something like this:
declare variable $banksyColl := collection('/db/Assignments/banksyForSVG/');
(: declare more global variables to pull data from the Banks collection. :)declare variable $timelineSpacer := 100;
declare variable $ThisFileContent :=
<svg> <g> <line x1="??" y1="??" x2="??" y2="??" style="??;??;"/> { <!--ebb: FLWOR statements will go here when we are ready to make a for-loop over each year value one by one, inside a pair of curly braces--> } </g> </svg>;
Notice that unlike FLWOR statements, global variables are always followed by a semicolon ;
.
In plotting a vertical line that runs from top to bottom in chronological order, we can take advantage of the y-coordinate space that increases as we move down the screen with SVG. (You may, if you like, opt to plot your timeline horizontally instead, if you prefer to think of time scrolling from left to right.) First of all, we need to know how long our line should be. To measure it, remember that we want to mark a small set of years separated from each other by a regular interval (large enough to give us room to plot some information). We need to write variables to determine how many years we need to plot, and then separate them by regular space. We could do this by hand, and pound this out point by point, but since the Banksy collection will certainly change with new XML added as Banksy produces more work, it would be better to write code that searches for the maximum and minimum year represented in the collection at any given time. (That means you could run your XQuery whenever the XML collection is updated and easily update your infographic with new data.)
For this exercise we are only working with year data, you should use the tokenize()
function and isolate the portion of the ISO-formatted date you need (some files have complex yyyy-mm-dd dates and some only yyyy, but the yyyy portion will always be the first piece or the only piece, so this should work). Isolate the years. Then you should to use the XPath min()
and max()
functions to define variables identifying the earliest and latest years in our series.
How will you determine the length of the line? Define variables to determine the number of years to plot (a simple subtraction). Try plotting the line just like that and decide for yourself how much to expand it. In our plot, we defined a global variable just for spacing, and it contains the number we use to multiply the length of the line to stretch it out. We set it to 100, but you could use any value you like.
Note: An XQuery variable can hold the results of a simple arithmetic calculation. The operators +
, -
, *
, and div
are used for addition, subtraction, multiplication, and division. We can’t use a forward slash for division because that has an XPath meaning.) At some point in this process, you will need to convert the year strings into integers in order to do basic calculations. In XQuery, you must do this by wrapping xs:integer()
around your code holding a year to make sure it is recognized as an integer datatype.
Now that you have defined the variables you need to measure the line, we can begin plotting SVG elements! Think about how to plot a line in SVG, and which variables you have defined that will help you plot the start of the line at x1 and y1 and the end of it at x2 and y2. Remember to use curly braces { }
to activate an XQuery variable to fill SVG attributes values.
You should be able to plot the timeline now! Run your results with the Eval
button, and view them as Adaptive Output
in the results window to look at your code. You should see SVG generated with its namespace in the root node, and your should see a simple SVG file containing a line element. You can view the SVG as a graphic in XQuery by toggling the XML Output
option to Direct Output
, but you will probably need to scroll to see you entire line. That is because we need to set the width and height attributes on our SVG and set up the long vertical line to be viewable in a browser window on scrolling down.
viewport
and shifting things with transform="translate(x, y)"
so you can see the full line:To make your long line visible, you want to estimate something wider than its widest x value and something a little longer than its largest y value so that you program a viewable area for your SVG. When generating SVG with calculated values as we are doing, this can be tricky, so we usually output our code first and read its maximum values before plugging in what is known as the viewport. To create a viewport, you need to add @height
and @width
attributes to your <svg>
element. We did this in our SVG timeline by using raw numbers without units, estimating a bit beyond our largest y value and our widest x value, thinking about how wide we will eventually want to make our file.
We also decided to shift our SVG over a little bit so that if we use 0, 0
coordinates, the timeline won’t be flush against the top and side of the screen. To do this, we work with the <g>
element, which bundles the SVG elements into a group. Within the viewport we have defined on the <svg>
element, we shift the <g>
to adjust the x and y values of the plotted elements inside over by x units and up or down by y units. Here is how we did it, but you may decide you would like to position your plot a little differently:
<svg width="2000" height="3000">
<g transform="translate(30, 30)">
<line x1="??" y1="??" x2="??" y2="??" style="??;??;"/> { <!--ebb: FLWOR statements will go here, inside a pair of curly braces--> } </g> </svg> ;
You have lots of options for scaling here, and we encourage you to experiment with various ways to shrink, expand, rotate, or skew your timeline. Here are some excellent resources on the viewbox and transform and scaling properties in SVG:
After browsing these pages, see if you can shrink your timeline a little or alter its angle on the screen!
We want to see years marked on our timeline, so we need to mark these at those regular 365 day intervals aross our line. You could do this by hand, but there is a better way that we will show you here. To set a series of marks along a line at regular intervals, you need to break a number into regular units. This is a special application of the for loop
, to generate a series of integers within the span of years represented in our timeline. In your global variables, you should have a calculation of the number of years in your timeline, and this is what we want to work with to create regular hashmarks for each year. The syntax for the for loop
that breaks the number 10 down into the integers that lead up to it (0, 1, 2, 3, etc up to 10), is this:
for $i in (0 to 10)
Here, $i is a range variable that we can use to loop over the numbers 0 to 10 in sequence. That is really handy for us with our span of a number of years, and you can use your XQuery variable for the measure of years in place of the number 10 in our model sequence above.
for loopfor XQuery to work its magic.) You know that the dates are spaced apart by a spacing factor we defined, so you will want to multiply
$i
by that factor as well to generate y positions for each hash mark.for loopso that you can output the text of the date next to your hash mark. Hint: You can do this by adding the minimum year for the collection to
$i
.text
element positioned nearby in a legible spot. You will notice that XQuery generates an error when you try to output multiple SVG elements in a return statement! That is because XQuery requires you to output just one thing in a FLWOR return
, and you are giving it multiple things to output. With SVG you deal with this by wrapping all the elements together in an SVG <g>
element (which literally creates a single group of elements). Any positioning values you set in the root <SVG>
or ancestor <g>
elements will be inherited by this new <g>
you place here.Our last SVG drawing tasks involve setting a variable size on the circles marking the years, so we can see at a glance which years feature the most artwork in the collection. You can choose the plot the relative quantities of artwork in other ways, as you wish, perhaps with rectangles or other SVG shapes, and perhaps centered on the line or set off to the right or left. However you choose to plot this, you should also output a text label to indicate the actual number of entries in the collection created in a given year.
You should have created a variable in the previous step that would automatically generate the four digit year values to label each hashmark. Work with that variable now, together with a helpful global variable we defined for you at the start of this assignment, so that you:
$i
, This should locate each piece of artwork recorded in a given year.count()
of that artwork. We will need this to plot our circles (or rectangles or other shapes as you wish)for loop
. Experiment with setting its dimensions, its fill, and style. Add an SVG text element to label each of these with a literal count of the artwork in the year indicated at $i
."spray_paint"
or "canvas"
. Use Eval to run your code and view your SVG. Copy your XQuery into a text file, save it following our standard homework naming conventions. You will need to add an SVG namespace to it for this to be viewed in a web browser! Add it to the SVG root element after you have generated the file (trying to do it in XQuery is complicated; we’ll explain if you ask): <svg xmlns="http://www.w3.org/2000/svg" width="2000" height="3000">
Then upload it to Courseweb with a copy of your XQuery file for this exercise.
To save your output file in your new directory, you will need to do define one more global variable, the biggest of them all: a variable that contains the entire SVG document you coded within your eXist script. You will declare a variable whose value equals the entire contents of the <svg>
element, and you can do that with the handy semicolon that always concludes a global variable in XQuery:
declare variable $ThisFileContent :=
<svg width="2000" height="3000"> <g transform="translate(30, 30)"> <line x1="??" y1="??" x2="??" y2="??" style="??;??;"/> { <!--ebb: FLWOR statements here, inside a pair of curly braces--> } </g> </svg>;
let $filename := "timeline.svg" let $doc-db-uri := xmldb:store("/db/yourFolder", $filename, $ThisFileContent) return $doc-db-uri
(: Output at http://newtfire.org:8338/exist/rest/db/yourFolder/timeline.svg :)
This works by creating a variable that actually stores an entire output file, and then, with one last FLWOR, encodes it with a special function, xmldb:store()
, which takes three arguments to give it a filepath in eXist, a filename that you can define, and finally the file content that you encoded in SVG.