I'll start by saying I'm very proficient in C# but pretty much a newbie in JS & Nancy. I'm trying to pass a 2-d array of doubles from a C#.Net Framework console app to the Graph3d package at http://visjs.org/docs/graph3d/. The Graph3d sample code works fine. I can modify their JS to do a simple 5x5 array with all array values set to 2 except the middle of the array (2,2) where the value is 22. It draws the graph as expected:
Here's the JS code that draws the above picture:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Graph 3D demo</title>
<style>
html, body {
font: 10pt arial;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
}
#mygraph {
position: absolute;
width: 100%;
height: 100%;
}
</style>
<!-- for mobile devices like android and iphone -->
<meta name="viewport" content="target-densitydpi=device-dpi, width=device-width" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"></script>
<script type="text/javascript">
var data = null;
var graph = null;
var request = new XMLHttpRequest();
request.open('GET', 'http://192.168.1.153:1234/', true);
request.onload = function () {
// Begin accessing JSON data here
//var data = JSON.parse(this.response);
console.log(this.response);
}
request.send();
// Called when the Visualization API is loaded.
function drawVisualization() {
// Create and populate a data table.
data = new vis.DataSet();
data.add([{x:0,y:0,z:2}]);
data.add([{x:0,y:1,z:2}]);
data.add([{x:0,y:2,z:2}]);
data.add([{x:0,y:3,z:2}]);
data.add([{x:0,y:4,z:2}]);
data.add([{x:1,y:0,z:2}]);
data.add([{x:1,y:1,z:2}]);
data.add([{x:1,y:2,z:2}]);
data.add([{x:1,y:3,z:2}]);
data.add([{x:1,y:4,z:2}]);
data.add([{x:2,y:0,z:2}]);
data.add([{x:2,y:1,z:2}]);
data.add([{x:2,y:2,z:22}]);
data.add([{x:2,y:3,z:2}]);
data.add([{x:2,y:4,z:2}]);
data.add([{x:3,y:0,z:2}]);
data.add([{x:3,y:1,z:2}]);
data.add([{x:3,y:2,z:2}]);
data.add([{x:3,y:3,z:2}]);
data.add([{x:3,y:4,z:2}]);
data.add([{x:4,y:0,z:2}]);
data.add([{x:4,y:1,z:2}]);
data.add([{x:4,y:2,z:2}]);
data.add([{x:4,y:3,z:2}]);
data.add([{x:4,y:4,z:2}]);
// specify options
var options = {
width: '100%',
height: '100%',
style: 'surface',
showPerspective: true,
showGrid: true,
showShadow: false,
keepAspectRatio: true,
verticalRatio: 0.5,
backgroundColor: {
strokeWidth: 0
}
};
// create our graph
var container = document.getElementById('mygraph');
graph = new vis.Graph3d(container, data, options);
}
</script>
</head>
<body onresize="graph.redraw();" onload="drawVisualization()">
<div id="mygraph"></div>
</body>
</html>
Pretty straightforward.
But when I try to send that same 5x5 array from C# through Nancy to JS, the picture doesn't look the same. The data array is correct, as verified by the Console window within Firefox. Here's the erroneous picture:
Here's the C# code that uses Nancy to run the web server and pass the array to JS:
using System;
using System.IO;
using System.Reflection;
using System.Text;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Conventions;
using Nancy.Hosting.Self;
namespace test3x3graph
{
class Program
{
static void Main(string[] args)
{
//Start Nancy web server
Nancy.Json.JsonSettings.MaxJsonLength = int.MaxValue;
string nancyUri = "http://localhost:4143/";
NancyHost host = new NancyHost(new Uri(nancyUri));
host.Start();
Console.WriteLine("NancyFx is running on " + nancyUri);
Console.WriteLine("\nPress any key to exit");
Console.ReadKey();
}
}
public class CustomRootPathProvider : IRootPathProvider
{
public string GetRootPath()
{
return Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
}
}
public class CustomBootstrapper : DefaultNancyBootstrapper
{
protected override IRootPathProvider RootPathProvider
{
get { return new CustomRootPathProvider(); }
}
protected override void ConfigureConventions(NancyConventions conventions)
{
base.ConfigureConventions(conventions);
conventions.StaticContentsConventions.Add(
StaticContentConventionBuilder.AddDirectory("public", @"public")
);
}
}
public class SurfaceModel
{
public static string SurfaceData = string.Empty;
//public static double[,] SurfaceData;
//static constructor to generate sample data
static SurfaceModel()
{
const int size = 5;
double[,] data = new double[size, size];
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
data[i, j] = 2;
}
}
data[2, 2] = 22;
//SurfaceData = data;
//convert to string representation
StringBuilder sb = new StringBuilder();
sb.Append("[");
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
sb.Append("[");
sb.Append(i.ToString());
sb.Append(",");
sb.Append(j.ToString());
sb.Append(",");
sb.Append(data[i, j].ToString());
sb.Append("]");
if (j < size - 1)
sb.Append(",");
}
if (i < size - 1)
sb.Append(",");
}
sb.Append("]");
SurfaceData = sb.ToString();
}
}
public class TestModule : Nancy.NancyModule
{
public TestModule()
{
Get["/"] = args =>
{
SurfaceModel model = this.Bind<SurfaceModel>();
return View["surface", model];
};
}
}
}
And finally, here's the JS code that receives the surface model from C#:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Graph3D test</title>
<style>
html, body {
font: 10pt arial;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
}
#mygraph {
position: absolute;
width: 100%;
height: 100%;
}
</style>
<!-- for mobile devices like android and iphone -->
<meta name="viewport" content="target-densitydpi=device-dpi, width=device-width" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"></script>
<script type="text/javascript">
var data = null;
var graph = null;
// inject surface model here...
var surfaceData = @Model.SurfaceData;
// Called when the Visualization API is loaded.
function drawVisualization() {
console.log(surfaceData);
// Create and populate a data table.
data = new vis.DataSet();
for(var x = 0; x < surfaceData.length; x++) {
for(var y = 0; y < surfaceData[x].length; y++) {
data.add([
{
x: x,
y: y,
z: surfaceData[x][y]
}
]);
}
}
// specify options
var options = {
width: '100%',
height: '100%',
style: 'surface',
showPerspective: true,
showGrid: true,
showShadow: false,
keepAspectRatio: true,
verticalRatio: 0.5,
backgroundColor: {
strokeWidth: 0
}
};
// create our graph
var container = document.getElementById('mygraph');
graph = new vis.Graph3d(container, data, options);
}
</script>
</head>
<body onresize="graph.redraw();" onload="drawVisualization()">
<div id="mygraph"></div>
</body>
</html>
That JS is nearly identical to the JS that works; only the data portion is different. All the Graph3d options are the same, so it's very confusing to me that the pictures don't render the same.
Also notice in the C# that the double array is converted to a string representation before sending to JS. When I try to pass it as an array of doubles, the resulting picture is blank. If a solution can be found using the string representation, fantastic. But it would be so much cleaner to be able to avoid that intermediate step and pass the array of doubles directly to JS.
Thanks for any advice.
I was able to get this working, using the Json.NET serializer. It was VERY surprising to me that JsonConvert.SerializeObject does not add the single-quotes that allow JSON.Parse to deserialize the string without error.
In any case, here's the relevant C# code:
//create a 1-dimensional array to send to Javascript.
//the first item is the number of rows; the 2nd item is the number of columns;
//the remaining items are the values from the 2-d array in row-major order
double[] data1d = new double[size*size + 2];
data1d[0] = data1d[1] = size;
int k = 2;
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
data1d[k++] = data[i, j];
}
}
SurfaceData = "'" + JsonConvert.SerializeObject(data1d) + "'";
And here's the relevant Javascript code:
// inject surface model here.
// Model.Surface data is a 2-dimensional array stored in a 1-dimensional array as follows:
// Array item [0] is the number of rows. Array item [1] is the number of columns.
// The remaining array items are the values from the 2-d array in row-major order
var surfaceData = JSON.parse(@Model.SurfaceData);
// Called when the Visualization API is loaded.
function drawVisualization() {
console.log(surfaceData);
// Create and populate a data table.
data = new vis.DataSet();
var k = 2;
for(var x = 0; x < surfaceData[0]; x++) {
for(var y = 0; y < surfaceData[1]; y++) {
data.add([{x: x, y: y, z: surfaceData[k++]}]);
}
}