BufferedImage drawGraph(java.util.List<DataSource> dataSources, int width, int height, boolean withMeta)
	{
		BufferedImage output = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		Graphics g = output.getGraphics();

		g.setColor(Color.WHITE);
		g.fillRect(0, 0, width, height);

//		Color [] colors = { new Color(0x59432E), Color.RED, Color.BLACK, Color.GREEN, Color.BLUE };
//		int colorIndex = 0;

		for(DataSource dataSource : dataSources)
		{
			DataInfo stats = dataSource.getStats();
			double min = stats.getMin();
			double max = stats.getMax();
			double avg = stats.getAvg();
			double sd  = stats.getSd();
			double scale = 1.0, half, quarter, quarter3;
			if (config.getSparklineGraphMode() == SparklineGraphMode.AVG_SD)
			{
				double two_sd = 2.0 * sd;
				if (two_sd != 0.0)
					scale = height / two_sd;
				half = avg;
				quarter = avg - (sd / 2.0);
				quarter3 = avg + (sd / 2.0);
			}
			else
			{
				double diff = max - min;
				if (diff != 0.0)
					scale = height / diff;
				half = (min + max) / 2.0;
				quarter = (max - min) / 4.0 + min;
				quarter3 = ((max - min) / 4.0) * 3.0 + min;
			}
			java.util.List<Double> values = dataSource.getValues();
			int px = -1, py = -1;

			g.setColor(config.getGraphColor());
//			g.setColor(colors[colorIndex++]);
//			if (colorIndex == colors.length)
//				colorIndex = 0;

			double sumX = 0.0, sumY = 0.0, sumXY = 0.0, sumX2 = 0.0;
			Integer firstOffset = null;
			int nElements = 0;
			for(int offset=0; offset<width; offset++)
			{
				int dataOffset = offset + (values.size() - width) - 1;
				if (dataOffset >= 0)
				{
					if (firstOffset == null)
						firstOffset = offset;
					nElements++;
					double value = values.get(dataOffset);
					double scaledValue = calcY(value, min, max, avg, sd, scale, height);
					int y = (int)scaledValue;
					int x = offset;

					if (px == -1 || py == -1)
						output.setRGB(x, y, g.getColor().getRGB());
					else
						g.drawLine(px, py, x, y);

					px = x;
					py = y;

					// least squares line
					int timeStamp = dataOffset;
					sumX += timeStamp;
					sumY += values.get(dataOffset);
					sumXY += timeStamp * values.get(dataOffset);
					sumX2 += Math.pow(timeStamp, 2.0);
				}
			}

			if (withMeta)
			{
				int y = calcY(half, min, max, avg, sd, scale, height);
				g.setColor(Color.RED);
				dottedLine(output, y, width, g.getColor().getRGB());
				g.drawLine(0, y, 2, y);
				g.setFont(new Font(config.getFontName(), Font.BOLD, (int)(height / 7.5)));
				g.drawString(String.format("%g", half), 5, y + ((Graphics2D)g).getFontMetrics().getAscent() / 2);

				y = calcY(quarter, min, max, avg, sd, scale, height);
				dottedLine(output, y, width, g.getColor().getRGB());
				g.drawLine(0, y, 2, y);
				g.drawString(String.format("%g", quarter), 5, y + ((Graphics2D)g).getFontMetrics().getAscent() / 2);

				y = calcY(quarter3, min, max, avg, sd, scale, height);
				dottedLine(output, y, width, g.getColor().getRGB());
				g.drawLine(0, y, 2, y);
				g.drawString(String.format("%g", quarter3), 5, y + ((Graphics2D)g).getFontMetrics().getAscent() / 2);

				int usedNElements = nElements;
				double dummy = (usedNElements * sumX2 - sumX * sumX);
				if (usedNElements != 0 && dummy != 0.0)
				{
					double b = (usedNElements * sumXY - sumX * sumY) / dummy;
					double a = (sumY / usedNElements) - b * (sumX / usedNElements);
					for(int offset=firstOffset; offset<width; offset+=2)
					{
						int dataOffset = offset + (values.size() - width) - 1;
						double value = a + (double)dataOffset * b;
						y = calcY(value, min, max, avg, sd, scale, height);

						output.setRGB(offset, Math.max(0, Math.min(y, height - 1)), Color.GREEN.getRGB());
					}
				}
			}
		}

		return output;
	}