Posts tagged with 'OxyPlot'
Nov
23
2 years ago

C# native behaviors explained + charts with OxyPlot

0 comments

Today we will learn what are native behaviors and, as a DEMO, I've made a behavior for displaying charts which we can create natively with OxyPlot library.

Download source-code

Native behaviors explained

Sciter allows you to attach one or more native behaviors to a DOM element. A behavior is essentially a event-handler which allows you to participate in the event chain of that element and perform any needed task. This way you are adding code-logic behind a DOM element in the native layer.

The behavior is a name you register in the engine, specifying which event-handler class corresponds to this name:

// Registers the Chart behavior
host.RegisterBehaviorHandler("Chart", typeof(ChartBehavior));

In CSS you can then attach the given behavior name to elements:

<style>
chart
{
 behavior: Chart;// the name you registered
}
</style>

With this CSS declaration, any <chart> tag in our HTML will get the event-handler attached, or beeing more exact, any DOM element that matches the 'chart' CSS selector.

You can also manualy attach an event-handler for a single element entirely in C#:

private SciterWindow wnd;

public void SetupEvh()
{
    ChartBehavior evh = new ChartBehavior();

	SciterElement el = wnd.RootElement.SelectFirstById("main-chart");
	el.AttachEvh(evh);
}

This way you are able to attach the event-handler in an element-by-element basis natively.

Coding the SciterEventHandler

The event-handler is a class you derive from SciterEventHandler:

using System;
using SciterSharp;
					
public class ChartBehavior : SciterEventHandler
{
}

A event-handler receives every event targeting the element or its children, before it is dispatched to TIScript side. You receive events for children because events bubbles from the root element to the target element (event bubbling), so they need to pass through any parent element in middle of the DOM hierarchy.

Not all events are bubbling events. Only the target element receives a OnDraw request, for example.

You override the virtual methods of SciterEventHandler that correspond to the events you are interested. These are the methods you can override:

  • Behavior related events: Subscription, Attached, Detached
  • User input events: OnMouse, OnKey, OnScroll, OnGesture
  • UI events: OnFocus, OnSize, OnDraw
  • TIScript method call for the element: OnScriptCall
  • Others: OnMethodCall, OnDataArrived, OnTimer

For quickly overriding methods in Visual Studio, type override and then press SPACE: Visual Studio will display an auto-suggestion list of the methods you can override.

A SciterEventHandler might be attached to many DOM elements. In case you need to keep track of those elements, or need to do any kind of initialization, you should override the Attached() method, which is called be the begine as soon as it's attached to an element, receiving as paramater the DOM element being attached:

public class MyBehaviorEvh : SciterEventHandler
{
  protected override void Attached(SciterElement se)
  {
     // Initialize this behavior to the given 'se' element
     // Save 'se' variable
  }
}

When the element is removed from DOM, or you manually dettaches the behavior from it, the engine invokes the Detached() method, where you should do any needed deinitialization/cleanup.

Charting solution with OxyPlot

A complete script-based charting solution for Sciter seems to not exist yet. But for any kind of problem, you have the option to search for a native solution, which you can then integrate in your Sciter UI.

OxyPlot has almost anything you need for chart creation. This lib is able to generate a PNG image of the chart that you create programatically, which we can then draw on screen.

For that, I use a native behavior where in its event-handler, I handle the OnDraw event and draw the PNG image using a SciterGraphics instance which you get from DRAW_PARAMS:

class ChartBase : SciterEventHandler
{
	protected PlotModel _model;

	protected override bool OnDraw(SciterElement se, SciterXBehaviors.DRAW_PARAMS prms)
	{
		if(_model == null)
			return false;

		if(prms.cmd == SciterXBehaviors.DRAW_EVENTS.DRAW_CONTENT)
		{
			var pngExporter = new PngExporter()
			{
				Width = prms.area.Width,
				Height = prms.area.Height,
				Background = OxyColors.Transparent
			};

			using(var ms = new MemoryStream())
			{
				var bmp = pngExporter.ExportToBitmap(_model);

				using(var img = new SciterImage(bmp))
				{
					new SciterGraphics(prms.gfx).BlendImage(img, prms.area.left, prms.area.top);
				}
			}
				
			return true;
		}
		return false;
	}
}

Notice that this charting solution still needs some improvements:

  • OxyPlot does supports chart interaction, but I need to implement the UI backend which links OxyPlot <-> Sciter event handling
  • As it depends on System.Windows.Forms, it is Windows-only, although OxyPlot is a multi-platform library. Solution it to port OSX by using OxyPlot.Xamarin.Forms and to Linux port by using OxyPlot.GtkSharp
Nov
2
2 years ago

Weather data + chart in C# using OpenWeatherMap or Yahoo API

0 comments

If you need weather forecast data, OpenWeatherMap and Yahoo have free APIs to get you covered.

Here I will show how grab this data in C# and, just for fun, how to plot the temperature forecast over a line graph using OxyPlot.

Yahoo API request URL

Yahoo gives you a 7 days forecast in a daily fashion.

Simply access the following URL which contains YQL query to fetch data from the weather.forecast table and return it in JSON format (or XML if you want):

https://query.yahooapis.com/v1/public/yql?q=select * from weather.forecast where woeid in (select woeid from geo.places(1) where text='caxias do sul') and u='c'&format=json

This sample URL returns forecast to my city, Caxias do Sul / Brazil.

Notice the u='c' which makes it returns temperatures in Celsius.

OpenWeatherMap API request URL

OpenWeatherMap free alternative offers a 5 day / 3 hour forecast.

First you need to register and get an API key to query their REST API.

Then you need the ID of the city you want the forecast:

  1. Go to http://openweathermap.org/find
  2. Search your city
  3. Click its name
  4. In the resulting page URL, like http://openweathermap.org/city/3466537, copy the numberic part, so 3466537 in this example

GET the following URL (replacing CITYID and APPID with the city ID and API key, respectively):

http://api.openweathermap.org/data/2.5/forecast?id={CITYID}&APPID={APPID}&units=metric

Note that I want temperature values in Celsius degrees, which means to use the metric system, hence the units=metric parameter.

Graph in C# / OxyPlot

The following graph is the live 5 day / 3 hour temperature forecast returned by OpenWeatherMap for my city:

Behind this image, I am using OxyPlot to generate a PNG image from the JSON data, and returning it through the /Weather/CxsForecastPlot URL.

Code behind the ASP.NET MVC action for this URL:

// GET: CxsForecastPlot
public ActionResult CxsForecastPlot()
{
	PlotModel model = new PlotModel()
	{
		PlotAreaBorderColor = OxyColor.Parse("#BBBDBE"),
		TextColor = OxyColor.Parse("#50606F"),
	};

	var temperatureAxis = new LinearAxis()
	{
		Minimum = 0,
		Maximum = 50,
		Title = "Temperature CÂș",
		MajorGridlineThickness = 1,
		MajorGridlineStyle = LineStyle.Solid,
		MajorGridlineColor = OxyColor.Parse("#E9ECEF"),
		CropGridlines = true,
		TickStyle = TickStyle.None
	};
	model.Axes.Add(temperatureAxis);

	var dateAxis = new DateTimeAxis()
	{
		StringFormat = "MMM dd",
		TickStyle = TickStyle.None
	};
	model.Axes.Add(dateAxis);

	LineSeries serie = new LineSeries()
	{
		MarkerType = MarkerType.Circle,
		MarkerSize = 4,
		MarkerStrokeThickness = 1,
		MarkerStroke = OxyColors.Black,
		MarkerFill = OxyColors.White,
		StrokeThickness = 3,
		ItemsSource = GetForecastData().Select(e => new DataPoint(DateTimeAxis.ToDouble(e.dt), e.temperatureC)).ToList()
	};
	model.Series.Add(serie);

	using(var ms = new MemoryStream())
	{
		var export = new PngExporter() { Width = 900 };
		export.Export(model, ms);
		return new ImageResult(ms.ToArray(), "image/png");
	}
}

class WeatherData
{
	public DateTime dt;
	public double temperatureC;
	public double humidity;
}

private List<WeatherData> GetForecastData()
{
	List<WeatherData> result = new List<WeatherData>();

	// GET the data from REST API
	// HttpClient need Microsoft.Net.Http NuGeT
	using(var client = new HttpClient())
	{
		HttpResponseMessage response = client.GetAsync($"http://api.openweathermap.org/data/2.5/forecast?id={CITY_ID}&APPID={OWM_APPID}&units=metric").Result;
		response.EnsureSuccessStatusCode();

		string json = Encoding.UTF8.GetString(response.Content.ReadAsByteArrayAsync().Result);
		dynamic dynjson = JSON.DeserializeObject(json);
		foreach(var item in dynjson.list)
		{
			result.Add(new WeatherData
			{
				dt = Utils.FromUnixTime((long) item.dt),
				temperatureC = (double) item.main.temp,
				humidity = (double) item.main.humidity
			});
		}
	}

	return result;
}

public class ImageResult : ActionResult
{
	public ImageResult(byte[] image, string contentType)
	{
		if(image == null)
			throw new ArgumentNullException("image");
		if(contentType == null)
			throw new ArgumentNullException("contentType");

		this.Buffer = image;
		this.ContentType = contentType;
	}

	public byte[] Buffer { get; private set; }
	public string ContentType { get; private set; }

	public override void ExecuteResult(ControllerContext context)
	{
		if(context == null)
			throw new ArgumentNullException("context");

		HttpResponseBase response = context.HttpContext.Response;

		response.ContentType = this.ContentType;
		response.OutputStream.Write(Buffer, 0, Buffer.Length);
		response.End();
	}
}

public static class Utils
{
	public static DateTime FromUnixTime(this long unixTime)
	{
		var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
		return epoch.AddSeconds(unixTime);
	}
}

Make sure to install Newtonsoft.Json and OxyPlot.WindowsForms NuGeTs. Each service has its own JSON format.

For easily navigating the returned JSON data, notice that I am using C# dynamic binding feature.