Temperature graphs

In a previous article we looked at remote measurement of temperature and humidity. Now it is time to show the results of the temperature measurements. First a recap of the setup:

Three remote temperature and humidity sensors are located in the living room, the garage and outside. The following graph shows the output of about 1 week:

As you can see, the garage is not isolated very well. The temperature in the garage is quite similar to the temperature outside. If the weather predictions are correct, we will see temperatures below 0 degrees centigrade soon. Finally a test for the parsing of negative values!

Luckily, the isolation of the living room is much better than that of the garage. During the night the temperature drops only 2 degrees from 20 to 18, even though the thermostat is programmed to keep the temperature above 15 degrees. The spikes to about 25 degrees are a result of the placement of the sensor in the living room: early in the afternoon, the sun sometimes shines directly on it.

Posted in programming | Tagged , | 1 Comment

Measuring temperature and humidity with a JeeNode

In a previous article we looked at the Arduino. Now we will look at an (almost) Arduino compatible clone, the JeeNode.

It is a bit smaller and cheaper than the original version and operates at 3.3V instead of 5V. This makes it easy to connect 3.3V peripherals, such as the radio module RX868 by ELV.

Connecting this device to the JeeNode means that we can listen to signals sent out by the temperature and humidity sensor S555TH.

Many thanks go out to JeeLabs for providing the On-Off Keying (OOK) decoding used by the S555TH, and Pieter Meulenhoff for supplying me with the idea of using this sensor and initial versions of the software.

The S555TH transmits the following data within the S300 protocol:

The first byte contains the sign of the temperature value and the address of the sensor. If the first bit is ‘0’, the temperature sign is ‘+’, else it is ‘-‘. The second to fourth bit are used for the address (1-8) of the sensor. This is used for identifying the sensor, but also helps in avoiding packet collisions. Each sensor will wait for (177 minus 0.5 * address) seconds between transmissions.

The second through fourth bytes contain the temperature and humidity values. For example, the humidity value is obtained by multiplying 10 with the first nibble of the fourth byte, adding the second nibble of the fourth byte, and adding 0.1 times the first nibble of the third byte.

In code this looks like:

uint8_t address = 1 + ((buf[0] >> 4) & 0x07);
int16_t temperature = (buf[0] >> 7 ? -1 : 1) * (100 * (buf[2] & 0x0F) + 10 * (buf[1] >> 4) + 1 * (buf[1] & 0x0F));
uint16_t humidity = 100 * (buf[3] >> 4) + 10 * (buf[3] & 0x0F) + 1 * (buf[2] >> 4);

Serial.print("S555TH: address=");
Serial.print(address, DEC);
Serial.print(" temperature=");
Serial.print(temperature / 10.0, 1);
Serial.print(" humidity=");
Serial.println(humidity / 10.0, 1);

Note that we multiplied the temperature and humidity values by 10 to be able to print them nicely to the serial monitor, in which we divide by 10 again.

The complete Arduino sketch can be downloaded here.

Posted in Hardware, programming | Tagged , , , | 1 Comment

Programming the Arduino ethernet shield

The Arduino is an open-source electronics prototyping platform based on flexible, easy-to-use hardware and software. It features an IDE that allows you to program the Arduino using a standard USB cable. Several shields exist that extend the functionality of the standard Arduino platform. In the picture above the Arduino is shown on the left and the ethernet shield is shown on the right.

The ethernet shield adds IP communication, acting as a client or as a server. The most well-known protocols are supported right out of the box, including TCP, UDP and ICMP. Several enthousiasts have added support for even more protocols, such as DHCP and NTP.

Creating programs for the Arduino with the ethernet shield is simple:

#include

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 10, 0, 0, 177 };
byte server[] = { 64, 233, 187, 99 }; // Google

Client client(server, 80);

void setup()
{
   Ethernet.begin(mac, ip);
   Serial.begin(9600);

   delay(1000);

   Serial.println("connecting...");

   if (client.connect()) {
      Serial.println("connected");
      client.println("GET /search?q=arduino HTTP/1.0");
      client.println();
   } else {
      Serial.println("connection failed");
   }
}

void loop()
{
   if (client.available()) {
      char c = client.read();
      Serial.print(c);
   }

   if (!client.connected()) {
      Serial.println();
      Serial.println("disconnecting.");
      client.stop();
      for(;;)
         ;
   }
}

Every Arduino program has at least two parts: a setup() function and a loop() function. The Arduino IDE creates an actual proper C program around these two functions before it uploads the complete program to the Arduino.

The Arduino IDE contains a serial monitor, which allows you to send strings to the Arduino program or receive strings from it. The example program above shows how the Serial class works. In the setup() function, the serial communication is started at 9600 baud. In the loop() function, data is sent over the serial line with the method Serial.print(). If the serial line is actually connected, the program on the Arduino will halt until the serial monitor is opened. If the serial line is not connected, e.g. when you are done debugging, the program will run normally.

The ethernet library contains the Ethernet class. In the setup() function, the ethernet stack is started with Ethernet.begin(mac, ip). The mac and ip parameters are both byte arrays. If only these two parameters are present and the IP address has the form a.b.c.d, the gateway is assumed to be a.b.c.1 and the subnetmask is assumed to be 255.255.255.0. There are other constructors that take one or two extra parameters for people with different network setups.

After ethernet initialization, the class Client is used to create a TCP connection to the supplied server IP address and port number. The Client.connect() method tries to connect to the server and a call to Client.println() sends data over the TCP connection to the server. The Client.available() method tells if the server has sent data over the TCP connection to our client. If it has, a call to Client.read() gives these bytes.

Posted in Hardware, Networking, programming | Tagged | 2 Comments

Model View Controller (MVC) with JSP and JSTL

In this article we will create a small web application that uses the Model View Controller (MVC) pattern with Java Server Pages (JSP) and JSP Standard Template Library (JSTL). A container like Tomcat is needed to run this combination.

Thanks go out to the author of the JSP – MVC Tutorial, who thought of the sample application that we will use here, the coffee advisor. The user is first presented with a choice in coffee taste she prefers. Pressing a button moves on to a page with advise about the type of coffee to drink based on that taste.

An MVC application has three parts:

  • Model. The model is the domain-specific representation of the data upon which the application operates. In our case this is implemented in the CoffeeExpert class.
  • View. The view renders the model into a form suitable for interaction, typically a user interface element. In our case this is implemented in a JSP file called coffee.jsp.
  • Controller. The controller receives input and initiates a response by making calls on model objects. In our case this is implemented in the CoffeeSelect class.

In addition to these three file we need a web.xml file that tells the container how to map a URL (e.g. /CoffeeSelect.do) into a class to run (e.g. com.example.web.CoffeeSelect). We also need a start page for the user input, which we will call coffee.html. In total this means we should create a  .war file with the following structure:

coffee.html

<html>
    <body>
        <h2>Coffee Advisor Input</h2>
        <form method="POST" action="CoffeeSelect.do">
            <select name="taste" size=1">
                <option value="milky">Milky</option>
                <option value="froffy">Froffy</option>
                <option value="icey">Icey</option>
                <option value="strong">Spaced Out</option>
            </select>
            <br/><br/>
            <input type="Submit"/>
        </form>
    </body>
</html>

CoffeeExpert.java

package com.example.model;

import java.util.*;

public class CoffeeExpert {
    public List<String> getTypes(String taste) {
        List<String> result = new ArrayList<String>();
        if (taste.equals("milky")) {
            result.add("Latte");
            result.add("Cappuccino");
        } else if (taste.equals("froffy")) {
            result.add("Latte");
            result.add("Cappuccino");
            result.add("Frappuccino");
        } else if (taste.equals("icey")) {
            result.add("Frappuccino");
        } else if (taste.equals("strong")) {
            result.add("Espresso");
            result.add("Double espresso");
        } else {
            result.add("Vending machine");
        }
        return (result);
    }
}

CoffeeSelect.java

package com.example.web;

import java.io.IOException;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.example.model.CoffeeExpert;

public class CoffeeSelect extends HttpServlet {

    private static final long serialVersionUID = 1L;

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        List<String> types = new CoffeeExpert().getTypes(request.getParameter("taste"));
        request.setAttribute("types", types);
        RequestDispatcher view = request.getRequestDispatcher("coffee.jsp");
        view.forward(request, response);
    }
}

coffee.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
    <body>
        <h2>Coffee Advisor Output</h2>
        <c:forEach var="type" items="${types}">
            <c:out value="${type}"/>
            <br />
        </c:forEach>
    </body>
</html>

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

    <servlet>
        <servlet-name>Coffee</servlet-name>
        <servlet-class>com.example.web.CoffeeSelect</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Coffee</servlet-name>
        <url-pattern>/CoffeeSelect.do</url-pattern>
    </servlet-mapping>
</web-app>
Posted in programming | Tagged , , , | 3 Comments

Changing CPU and memory settings on XenServer VMs

You can change the CPU and memory settings of XenServer virtual machines using the API. XenServer allows you to set the priority of the virtual machine CPU (called weight), set a limit on the amount of CPU the virtual machine can use (called cap) and set the amount of memory the virtual machine gets.

CPU priority

You can set the weight parameter to a number between 1 and 65536. The default value is 256, meaning normal priority. A virtual machine with a weight of 512 will get twice as much CPU as a virtual machine with a weight of 256 on a contended XenServer host. The following code sets the weight to 128, meaning that it will get half as much CPU as a normal virtual machine.

Map<String, String> vcpuParameters = new HashMap<String, String>();
vcpuParameters.put("weight", "128");
vm.setVCPUsParams(connection, vcpuParameters);

CPU limit

You can set the cap parameter to a percentage number. The default value is 0, meaning there is no cap. The value 100 represents one virtual CPU, so half a virtual CPU can be set by using the value 50. The following code sets the cap to 35, meaning that it will not get more than 35 percent of one virtual CPU, even if the physical CPU is not used to its top.

Map<String, String> vcpuParameters = new HashMap<String, String>();
vcpuParameters.put("cap", "35");
vm.setVCPUsParams(connection, vcpuParameters);

Memory

You can set the amount of memory assigned to a virtual machine in bytes. The following code sets the amount of memory to 1 GB (1,073,741,824 bytes).

vm.setMemoryStaticMax(connection, new Long(1073741824));
Posted in Cloud computing, programming | Tagged , | 7 Comments

Installing Sun JDK 6 on Ubuntu 10.04

The Sun JDK can be installed on Ubuntu by adding a repository and installing with apt-get. The following works with the latest versions of both at the time of writing:

sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner"
sudo apt-get update
sudo apt-get install sun-java6-jdk

If the command add-apt-repository is missing, install it with the following command:

sudo apt-get install python-software-properties

With the alternatives system in place, the Java executable is automatically available:

/usr/bin/java -> /etc/alternatives/java
/etc/alternatives/java -> /usr/lib/jvm/java-6-sun/jre/bin/java

Setting up JAVA_HOME can now be done by editing /etc/profile and adding to the bottom:

export JAVA_HOME=/usr/lib/jvm/java-6-sun
Posted in programming | Tagged | 8 Comments

Getting CPU, memory, disk and network metrics from XenServer

In a previous article we looked at getting CPU and memory metrics from XenServer. As noted in that article, as of version 5.5 of XenServer, the preferred way of getting virtual machine metrics is through HTTP calls to get RRD XML files. We showed how to revert to the old way of doing things, but in this article we will look into the new, preferred way. This has the added benefit that disk and network metrics are also available, along with a history of metrics.

The first thing to note is that the metrics are not available through the XenServer API. The RRD XML files are stored on the physical machines that currently host the virtual machines. For example, if you have two physical machines (P1 and P2) with two virtual machines running on each of them (V1a, V1b, V2a and V2b), you need to query P1 for the metrics of V1a and V1b and query P2 for the metrics of V2a and V2b. If a virtual machine is not running at the moment, you can get the (old) metrics from the master in the pool.

Each physical machine has an HTTP interface at the following location:

http://machine_name_or_ip/rrd_updates?start=1234567890

The parameter start tells the server to only give metrics starting at this timestamp (seconds since January 1st 1970). The call requires a username and password, which will be asked if you connect through a webbrowser. The following code does this in Java:

Connection connection = new Connection(new URL(MASTER_HOST));
Session.loginWithPassword(connection, USERNAME, PASSWORD, APIVersion.latest().toString());

Map hostRecords = Host.getAllRecords(connection);
for (Host host : hostRecords.keySet()) {
    URL url = new URL("http://" + host.getAddress(connection) + "/rrd_updates?start=" + (System.currentTimeMillis() / 1000 - TIME_WINDOW));
    URLConnection urlConnection = url.openConnection();
    String encoding = new BASE64Encoder().encode((USERNAME + ":" + PASSWORD).getBytes());
    urlConnection.setRequestProperty ("Authorization", "Basic " + encoding);

    String rrdXportData = IOUtils.toString(urlConnection.getInputStream());
}

The code needs two libraries:

  • commons-io-1.4.jar. This package contains the code to get a string containing the data of an inputstream.
  • xenserver-5.5.0-1.jar. This package contains the code to connect to the XenServer pool.

The following constants are used in this piece of code and need to be filled in:

  • MASTER_HOST. This is the IP address or hostname of the master host in the pool.
  • TIME_WINDOW. XenServer will return RRD updates of the last TIME_WINDOW seconds.
  • USERNAME and PASSWORD. These are the credentials for connecting to the master host and the other physical hosts in the pool.

At the end of this piece of code we have the string rrdXportData containing the RRD XML data. Now we need to parse this data to get the metrics we want. The following listing contains the most important parts of this string (edited for clarity):

<xport>
   <meta>
      <start>1273342925</start>
      <step>5</step>
      <end>1273342980</end>
      <rows>13</rows>
      <columns>31</columns>
      <legend>
         <entry>AVERAGE:vm:19ef51bd-2cbc-50d1-3fa2-ad8878699203:cpu0</entry>
         <entry>AVERAGE:vm:19ef51bd-2cbc-50d1-3fa2-ad8878699203:vif_4_tx</entry>
         ...
      </legend>
   </meta>
   <data>
      <row>
         <t>1273342980</t>
         <v>0.0002</v>
         <v>0.0</v>
         ...
      </row>
      <row>
         <t>1273342975</t>
         <v>0.0003</v>
         <v>0.0</v>
         ...
      </row>
      ...
   </data>
</xport>

The following Java code will parse this RRD XML string and make it available in the four variables shown at the top. The variable metricsTimelines contains a hashmap of string to double[]. The string is the metric name (e.g. AVERAGE:vm:19ef51bd-2cbc-50d1-3fa2-ad8878699203:cpu0) and the double[] contains the values for this metric from startTime to endTime with a step size of step. Note that the value at endTime is at position 0 and the value at  startTime is at the end of the array.

int endTime = 0;
HashMap<String, double[]> metricsTimelines = null;
int startTime = 0;
int step = 0;

try {
   DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
   domFactory.setNamespaceAware(true);
   DocumentBuilder builder = domFactory.newDocumentBuilder();
   StringReader stringReader = new StringReader(rrdXportData);
   InputSource inputSource = new InputSource(stringReader);
   Document doc = builder.parse(inputSource);
   stringReader.close();

   ArrayList<ArrayList<String>> dataRows = new ArrayList<ArrayList<String>>();
   ArrayList<String> legends = new ArrayList<String>();
   NodeList xportChildNodes = doc.getDocumentElement().getChildNodes();
   for (int i = 0; i < xportChildNodes.getLength(); i++) {
      Node xportChildNode = xportChildNodes.item(i);
      if (xportChildNode.getNodeName().equals("meta")) {
         NodeList metaChildNodes = xportChildNode.getChildNodes();
         for (int j = 0; j < metaChildNodes.getLength(); j++) {
            Node metaChildNode = metaChildNodes.item(j);
            if (metaChildNode.getNodeName().equals("step")) {
               step = new Integer(metaChildNode.getTextContent()).intValue();
            } else if (metaChildNode.getNodeName().equals("start")) {
               startTime = new Integer(metaChildNode.getTextContent()).intValue();
            } else if (metaChildNode.getNodeName().equals("end")) {
               endTime = new Integer(metaChildNode.getTextContent()).intValue();
            } else if (metaChildNode.getNodeName().equals("legend")) {
               NodeList legendChildNodes = metaChildNode.getChildNodes();
               for (int k = 0; k < legendChildNodes.getLength(); k++) {
                  Node legendChildNode = legendChildNodes.item(k);
                  legends.add(k, legendChildNode.getTextContent());
               }
            }
         }
      } else if (xportChildNode.getNodeName().equals("data")) {
         NodeList dataChildNodes = xportChildNode.getChildNodes();
         for (int j = 0; j < dataChildNodes.getLength(); j++) {
            Node rowNode = dataChildNodes.item(j);
            NodeList rowChildNodes = rowNode.getChildNodes();
            ArrayList<String> dataRow = new ArrayList<String>();
            for (int k = 1; k < rowChildNodes.getLength(); k++) {
               Node rowChildNode = rowChildNodes.item(k);
               dataRow.add(k - 1, rowChildNode.getTextContent());
            }
            dataRows.add(dataRow);
         }
      }
   }

   int nrDataRows = dataRows.size();
   int nrLegends = legends.size();

   metricsTimelines = new HashMap<String, double[]>();
   for (int i = 0; i < nrLegends; i++) {
      metricsTimelines.put(legends.get(i), new double[nrDataRows]);
   }
   for (int i = 0; i < nrLegends; i++) {
      for (int j = 0; j < nrDataRows; j++) {
         double[] values = metricsTimelines.get(legends.get(i));
         values[j] = new Double(dataRows.get(j).get(i)).doubleValue();
      }
   }
} catch (Exception e) {
   e.printStackTrace();
}
Posted in Cloud computing, programming | Tagged , | 1 Comment

Using Google Visualization API with own data source

The Google Visualization API allows you to create charts and maps based on data you provide. This data can be in a Google Spreadsheet or be something you provide yourself. The visualizations themselves are mostly written in Javascript, although there are some written in Flash.

In this article we will create an HTML page with multiple charts in it and a data source that is generated by a Python program. We start with the HTML page that has two named div’s in it (visualization1 and visualization2), including the Javascript code that loads the two charts into these div’s.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
      <title>
         Google Visualization API
      </title>
      <script type="text/javascript" src="http://www.google.com/jsapi"></script>
      <script type="text/javascript">
         google.load('visualization', '1', {packages: ['columnchart', 'linechart']});
      </script>
      <script type="text/javascript">

         var query1, visualization1;
         var query2, visualization2;

         function initialize() {
            visualization1 = new google.visualization.ColumnChart(document.getElementById('visualization1'));
            query1 = new google.visualization.Query('http://jansipke.nl/res/visualization/chart-data.py');
            query1.setRefreshInterval(5);
            query1.send(drawVisualization1);

            visualization2 = new google.visualization.LineChart(document.getElementById('visualization2'));
            query2 = new google.visualization.Query('http://jansipke.nl/res/visualization/chart-data.py');
            query2.setRefreshInterval(5);
            query2.send(drawVisualization2);
         }

         function drawVisualization1(response) {
            if (response.isError()) {
               alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
               return;
            }
            visualization1.draw(response.getDataTable(), {legend: 'bottom', title: 'ColumnChart'});
         }

         function drawVisualization2(response) {
            if (response.isError()) {
               alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
               return;
            }
            visualization2.draw(response.getDataTable(), {legend: 'bottom', title: 'LineChart'});
         }

         google.setOnLoadCallback(initialize);
      </script>
   </head>
   <body>
      <div>
         <div id="visualization1" style="height: 250px; width: 400px; border: 1px solid; float: left;" />
      </div>
      <div>
         <div id="visualization2" style="height: 250px; width: 400px; border: 1px solid; float: left; margin-left: 10px" />
      </div>
   </body>
</html>

There are two things that you really need to do before this works correctly:

  • Make sure that the parent node of the div that holds your chart, is not also the parent of one of the other div’s that holds a chart. In the HTML page above we surrounded the chart div with another div to accomplish this.
  • The chart is updated with new data every 5 seconds. It is a known bug of the Google Visualization API that the data needs to be fetched from another server then the one that is hosting the HTML page for the refresh to actually work. In the HTML page above the data source chart-data.py is therefore fetched from jansipke.nl and the HTML is fetched from http://www.jansipke.nl.

We will create our own data source in Python. There is a description of how you should do this on the Writing Your Own Data Source page from Google. One of the most common mistakes in writing these data sources is the need for the program to read the request identifier (reqId) and return this value in the response. The API needs this to distinguish between responses for different charts on the same page.

import cgi, random

def index(req):
   reqId = None
   if (req.args):
      for arg in req.args.split("&"):
         (key, value) = arg.split("=")
         if (key == "tqx"):
            for parameter in value.split(";"):
               if (parameter.find("%3A") > 0):
                  (par_key, par_value) = parameter.split("%3A")
               if (par_key == "reqId"):
                  reqId = par_value

   a = str(random.randint(1, 3))
   b = str(random.randint(1, 3))
   c = str(random.randint(1, 3))
   d = str(random.randint(1, 3))

   s = ""
   s += "google.visualization.Query.setResponse(n"
   s += "{n"
   if (reqId != None):
      s += "   reqId:'" + reqId + "',n"
   s += "   status:'ok',n"
   s += "   table:n"
   s += "   {n"
   s += "      cols:n"
   s += "      [n"
   s += "         {id:'Col1',label:'',type:'string'},n"
   s += "         {id:'Col2',label:'Label1',type:'number'},n"
   s += "         {id:'Col3',label:'Label2',type:'number'},n"
   s += "         {id:'Col4',label:'Label3',type:'number'}n"
   s += "      ],n"
   s += "      rows:n"
   s += "      [n"
   s += "         {c:[{v:'a',f:'a'},{v:1.0,f:'1'},{v:1.0,f:'1'},{v:" + a + ",f:'1'}]},n"
   s += "         {c:[{v:'b',f:'b'},{v:2.0,f:'2'},{v:1.5,f:'1'},{v:" + b + ",f:'1'}]},n"
   s += "         {c:[{v:'c',f:'c'},{v:3.0,f:'3'},{v:2.5,f:'1'},{v:" + c + ",f:'1'}]},n"
   s += "         {c:[{v:'d',f:'d'},{v:4.0,f:'1'},{v:2.0,f:'1'},{v:" + d + ",f:'1'}]}n"
   s += "      ]n"
   s += "   }n"
   s += "});"

   return s

We can test this data source by following the link without parameters and following the link with the reqId parameter present:

Notice that the first one does not have reqId present in the response, but the second one does.

Update: it seems that the refreshing of data only happens correctly in Firefox and Opera. IE doesn’t refresh at all and Chrome only refreshes once. Oh joy!

Posted in programming | Tagged , , | 1 Comment

Creating web charts using Open Flash Chart

Open Flash Chart gives you the possibility to have Flash based charts on your website without the need to know Flash. All it takes is a JSON file that describes the chart. This JSON file can be a static file that you created yourself with a text editor or dynamically created by a program, e.g. written in Java or Python.

There are several different types of charts that can be displayed. Some of them are shown here:

We start by creating an HTML page that will contain the chart:

<html>
   <head>
      <script type="text/javascript" src="swfobject.js"></script>
      <script type="text/javascript">
         swfobject.embedSWF("open-flash-chart.swf", "my_chart1", "600", "250",
                            "9.0.0", "expressInstall.swf", {"data-file":"chart1.data"});
      </script>
   </head>
   <body>
      <div id="my_chart1"></div>
   </body>
</html>

Then we create a JSON file called chart1.data that describes how the chart should look:

{
  "title":{
    "text":  "Bar Glass Chart",
    "style": "{font-size: 20px; color:#0000ff; font-family: Verdana; text-align: center;}"
  },

  "bg_colour": "#FFFFFF",

  "x_axis":
  {
    "stroke":      1,
    "tick_height": 10,
    "colour":      "#CCCCCC",
    "grid_colour": "#00ff00",
    "labels":
    {
      "labels": ["Server 1", "Server 2", "Server 3"]
    }
  },

  "y_axis":
  {
    "steps":       20,
    "tick_length": 100,
    "colour":      "#CCCCCC",
    "grid_colour": "#00FF00",
    "offset":      0,
    "max":         100
  },

  "elements":
  [
    {
      "type":      "bar_glass",
      "alpha":     0.5,
      "colour":    "#9933CC",
      "text":      "CPU load",
      "font-size": 10,
      "values" :   [90, 60, 70]
    },
    {
      "type":      "bar_glass",
      "alpha":     0.5,
      "colour":    "#CC9933",
      "text":      "Memory usage",
      "font-size": 10,
      "values" :   [60, 70, 90]
    }
  ]
}

Copy the HTML file, the JSON file and the open-flash-chart.swf and swfobject.js files from the Open Flash Chart website to your own webserver and point your webbrowser to the HTML file. The result should look like the chart on this page.

Posted in programming | Tagged , , | Leave a comment

Google Maps icons and circles

It is quite easy to add content to Google Maps by using the API. In this article we will use some icons to mark points of interest and use polygons to emulate circles around a marker.

We want to give Google Maps an address and have a special icon and two circles around this point of interest. A picture says more than a thousand words, so here is the effect we want:

The Map Icon Collection hosts a large set of icons that are free to use in Google Maps. A small sample is shown below:

The following code achieves the effect we want. Take a look at the live result on this page.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Google Maps test</title>
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;sensor=false&amp;key=ABQIAAAArmrLJW3ZXq5P2DWexL7cFRSV6dnYUXLUEsXoSWmGp_LQX0Ln_BRimh_EXlhDUBj_zubkneJU9IiMuw" type="text/javascript"></script>
    <script src="gmaps-circle-overlay.js" type="text/javascript"></script>
    <script type="text/javascript">

    var ICON_LOCATION = "/res/";
    var ICON_BUSINESS = ICON_LOCATION + "business.png";
    var ICON_HOUSE = ICON_LOCATION + "house.png";
    var ICON_WIDTH = 32;
    var ICON_HEIGHT = 37;

    var map = null;
    var geocoder = null;

    function initialize() {
      if (GBrowserIsCompatible()) {
        map = new GMap2(document.getElementById("map_canvas"));
        map.setCenter(new GLatLng(53.199290, 6.523368), 15);
        map.setUIToDefault();

        geocoder = new GClientGeocoder();

        showAddress("Fivelgolaan 10, Groningen, Nederland", ICON_HOUSE);
        showAddress("Peizerweg 200, Groningen, Nederland", ICON_BUSINESS);
      }
    }

    function showAddress(address, image, width, height) {
      if (geocoder) {
        geocoder.getLatLng(address, function(point) {
          if (!point) {
            alert(address + " not found");
          } else {
            var myIcon = new GIcon(G_DEFAULT_ICON);
            myIcon.image = image;
            myIcon.iconSize = new GSize(ICON_WIDTH, ICON_HEIGHT);
            markerOptions = { icon:myIcon };
            marker = new GMarker(point, markerOptions);
            GEvent.addListener(marker, "click", function() {
              map.openInfoWindowHtml(point, address);
            });
            map.addOverlay(marker);
            map.addOverlay(new CircleOverlay(point, 0.2, "#444499", 1, 1, '#444499', 0.25));
            map.addOverlay(new CircleOverlay(point, 0.1, "#FFFF99", 1, 1, '#FFFF99', 0.25));
          }
        });
      }
    }

    </script>
  </head>

  <body onload="initialize()" onunload="GUnload()">
    <div id="map_canvas" style="width: 800px; height: 500px"></div>
  </body>
</html>

The drawing of circles is handled by a stand-alone piece of Javascript by Daws Design. I edited it a bit to have it use kilometres instead of miles.

Posted in programming | Tagged | 2 Comments