starburst master
starburst

file:b/json.php (new)
--- /dev/null
+++ b/json.php
@@ -1,1 +1,80 @@
+<?php
 
+/**
+ * @author      Razvan Stanga <git@razvi.ro>
+ */
+
+set_time_limit (0);
+
+$root = __DIR__;
+chdir ($root);
+define ("appRoot", $root."/");
+
+require (appRoot."app/autoload.php");
+
+function randomColor ($minVal = 0, $maxVal = 255)
+{
+
+    // Make sure the parameters will result in valid colours
+    $minVal = $minVal < 0 || $minVal > 255 ? 0 : $minVal;
+    $maxVal = $maxVal < 0 || $maxVal > 255 ? 255 : $maxVal;
+
+    // Generate 3 values
+    $r = mt_rand($minVal, $maxVal);
+    $g = mt_rand($minVal, $maxVal);
+    $b = mt_rand($minVal, $maxVal);
+
+    // Return a hex colour ID string
+    return sprintf('#%02X%02X%02X', $r, $g, $b);
+
+}
+
+use carstats\Database\Mysql as Db;
+
+$json = array ();
+$q = "SELECT b.`id`, b.`bodyName`, AVG(p.`price`) as `avgprice` FROM `prices` as p LEFT JOIN `body` as b ON p.`body_id`=b.`id` WHERE b.`active`=1 GROUP BY p.`body_id` ORDER BY b.`bodyName` ASC";
+$b = Db::query ($q);
+while ( $row = Db::fetchArray($b) ) {
+	$brands = array ();
+	$q1 = "SELECT b.`id`, b.`brandName`, AVG(p.`price`) as `avgprice`, p.`brand_id` FROM `prices` as p LEFT JOIN `brand` as b ON p.`brand_id`=b.`id` WHERE b.`active`=1 AND p.`body_id`='".$row['id']."' GROUP BY p.`brand_id` ORDER BY b.`brandName`";
+	$br = Db::query ($q1);
+	while ( $row1 = Db::fetchArray($br) ) {
+		$models = array ();
+		$q2 = "SELECT m.`id`, m.`modelName`, AVG(p.`price`) as `avgprice`, p.`brand_id` FROM `prices` as p LEFT JOIN `model` as m ON p.`model_id`=m.`id` WHERE p.`brand_id`='".$row1['brand_id']."' AND m.`active`=1 AND p.`body_id`='".$row['id']."' GROUP BY p.`model_id` ORDER BY m.`modelName`";
+		//$q2 = "SELECT * FROM `model` WHERE `brand_id`='".$row1['brand_id']."' AND `body_id`='".$row['id']."' ORDER BY `modelName` ASC";
+		$bm = Db::query ($q2);
+		while ( $row2 = Db::fetchArray($bm) ) {
+
+			$years = array ();
+			$q3 = "SELECT `year`, AVG(price) as `avgprice` FROM `prices` WHERE `brand_id`='".$row1['brand_id']."' AND `body_id`='".$row['id']."' AND `model_id`='".$row2['id']."' GROUP BY `year`";
+			$by = Db::query ($q3);
+			while ( $row4 = Db::fetchArray($by) ) {
+				$years[] = array (
+					"name" => $row4['year']."_".intval ($row4['avgprice'])." EUR",
+					"colour" => randomColor (100, 255),
+					"children" => array ()
+				);
+			}
+
+			$models[] = array (
+				"name" => $row2['modelName']."_".intval ($row2['avgprice'])." EUR",
+				"colour" => randomColor (180, 255),
+				"children" => $years
+			);
+		}
+		$brands[] = array (
+			"name" => $row1['brandName']."_".intval ($row1['avgprice'])." EUR",
+			"colour" => randomColor (140, 180),
+			"children" => $models
+		);
+	}
+	$json[] = array (
+		"name"	=>	$row['bodyName']."_".intval ($row['avgprice'])." EUR",
+		"colour" => randomColor (100, 140),
+		"children" => $brands
+	);
+}
+
+echo json_encode ($json);
+
+?>

file:b/wwwroot/demo.json (new)
--- /dev/null
+++ b/wwwroot/demo.json
@@ -1,1 +1,378 @@
-
+[
+  {
+    "name": "Aromas",
+    "children": [
+      {
+        "name": "Enzymatic",
+        "children": [
+          {
+            "name": "Flowery",
+            "children": [
+              {
+                "name": "Floral",
+                "children": [
+                  {
+                    "name": "Coffee Blossom", "colour": "#f9f0ab"
+                  },
+                  {
+                    "name": "Tea Rose", "colour": "#e8e596"
+                  }
+                ]
+              },
+              {
+                "name": "Fragrant",
+                "children": [
+                  {
+                    "name": "Cardamon Caraway", "colour": "#f0e2a3"
+                  },
+                  {
+                    "name": "Coriander Seeds", "colour": "#ede487"
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "Fruity",
+            "children": [
+              {
+                "name": "Citrus",
+                "children": [
+                  {
+                    "name": "Lemon", "colour": "#efd580"
+                  },
+                  {
+                    "name": "Apple", "colour": "#f1cb82"
+                  }
+                ]
+              },
+              {
+                "name": "Berry-like",
+                "children": [
+                  {
+                    "name": "Apricot", "colour": "#f1c298"
+                  },
+                  {
+                    "name": "Blackberry", "colour": "#e8b598"
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "Herby",
+            "children": [
+              {
+                "name": "Alliaceous",
+                "children": [
+                  {
+                    "name": "Onion", "colour": "#d5dda1"
+                  },
+                  {
+                    "name": "Garlic", "colour": "#c9d2b5"
+                  }
+                ]
+              },
+              {
+                "name": "Leguminous",
+                "children": [
+                  {
+                    "name": "Cucumber", "colour": "#aec1ad"
+                  },
+                  {
+                    "name": "Garden Peas", "colour": "#a7b8a8"
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "Sugar Browning",
+        "children": [
+          {
+            "name": "Nutty",
+            "children": [
+              {
+                "name": "Nut-like",
+                "children": [
+                  {
+                    "name": "Roasted Peanuts", "colour": "#b49a3d"
+                  },
+                  {
+                    "name": "Walnuts", "colour": "#b28647"
+                  }
+                ]
+              },
+              {
+                "name": "Malt-like",
+                "children": [
+                  {
+                    "name": "Balsamic Rice", "colour": "#a97d32"
+                  },
+                  {
+                    "name": "Toast", "colour": "#b68334"
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "Carmelly",
+            "children": [
+              {
+                "name": "Candy-like",
+                "children": [
+                  {
+                    "name": "Roasted Hazelnut", "colour": "#d6a680"
+                  },
+                  {
+                    "name": "Roasted Almond", "colour": "#dfad70"
+                  }
+                ]
+              },
+              {
+                "name": "Syrup-like",
+                "children": [
+                  {
+                    "name": "Honey", "colour": "#a2765d"
+                  },
+                  {
+                    "name": "Maple Syrup", "colour": "#9f6652"
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "Chocolatey",
+            "children": [
+              {
+                "name": "Chocolate-like",
+                "children": [
+                  {
+                    "name": "Bakers", "colour": "#b9763f"
+                  },
+                  {
+                    "name": "Dark Chocolate", "colour": "#bf6e5d"
+                  }
+                ]
+              },
+              {
+                "name": "Vanilla-like",
+                "children": [
+                  {
+                    "name": "Swiss", "colour": "#af643c"
+                  },
+                  {
+                    "name": "Butter", "colour": "#9b4c3f"
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "Dry Distillation",
+        "children": [
+          {
+            "name": "Resinous",
+            "children": [
+              {
+                "name": "Turpeny",
+                "children": [
+                  {
+                    "name": "Piney", "colour": "#72659d"
+                  },
+                  {
+                    "name": "Blackcurrant-like", "colour": "#8a6e9e"
+                  }
+                ]
+              },
+              {
+                "name": "Medicinal",
+                "children": [
+                  {
+                    "name": "Camphoric", "colour": "#8f5c85"
+                  },
+                  {
+                    "name": "Cineolic", "colour": "#934b8b"
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "Spicy",
+            "children": [
+              {
+                "name": "Warming",
+                "children": [
+                  {
+                    "name": "Cedar", "colour": "#9d4e87"
+                  },
+                  {
+                    "name": "Pepper", "colour": "#92538c"
+                  }
+                ]
+              },
+              {
+                "name": "Pungent",
+                "children": [
+                  {
+                    "name": "Clove", "colour": "#8b6397"
+                  },
+                  {
+                    "name": "Thyme", "colour": "#716084"
+                  }
+                ]
+              }
+            ]
+          },
+          {
+            "name": "Carbony",
+            "children": [
+              {
+                "name": "Smokey",
+                "children": [
+                  {
+                    "name": "Tarry", "colour": "#2e6093"
+                  },
+                  {
+                    "name": "Pipe Tobacco", "colour": "#3a5988"
+                  }
+                ]
+              },
+              {
+                "name": "Ashy",
+                "children": [
+                  {
+                    "name": "Burnt", "colour": "#4a5072"
+                  },
+                  {
+                    "name": "Charred", "colour": "#393e64"
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "name": "Tastes",
+    "children": [
+      {
+        "name": "Bitter",
+        "children": [
+          {
+            "name": "Pungent",
+            "children": [
+              {
+                "name": "Creosol", "colour": "#aaa1cc"
+              },
+              {
+                "name": "Phenolic", "colour": "#e0b5c9"
+              }
+            ]
+          },
+          {
+            "name": "Harsh",
+            "children": [
+              {
+                "name": "Caustic", "colour": "#e098b0"
+              },
+              {
+                "name": "Alkaline", "colour": "#ee82a2"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "Salt",
+        "children": [
+          {
+            "name": "Sharp",
+            "children": [
+              {
+                "name": "Astringent", "colour": "#ef91ac"
+              },
+              {
+                "name": "Rough", "colour": "#eda994"
+              }
+            ]
+          },
+          {
+            "name": "Bland",
+            "children": [
+              {
+                "name": "Neutral", "colour": "#eeb798"
+              },
+              {
+                "name": "Soft", "colour": "#ecc099"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "Sweet",
+        "children": [
+          {
+            "name": "Mellow",
+            "children": [
+              {
+                "name": "Delicate", "colour": "#f6d5aa"
+              },
+              {
+                "name": "Mild", "colour": "#f0d48a"
+              }
+            ]
+          },
+          {
+            "name": "Acidy",
+            "children": [
+              {
+                "name": "Nippy", "colour": "#efd95f"
+              },
+              {
+                "name": "Piquant", "colour": "#eee469"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "name": "Sour",
+        "children": [
+          {
+            "name": "Winey",
+            "children": [
+              {
+                "name": "Tangy", "colour": "#dbdc7f"
+              },
+              {
+                "name": "Tart", "colour": "#dfd961"
+              }
+            ]
+          },
+          {
+            "name": "Soury",
+            "children": [
+              {
+                "name": "Hard", "colour": "#ebe378"
+              },
+              {
+                "name": "Acrid", "colour": "#f5e351"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]

file:b/wwwroot/index.php (new)
--- /dev/null
+++ b/wwwroot/index.php
@@ -1,1 +1,180 @@
 
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Demo</title>
+
+<style>
+@import url(style.css);
+path {
+  stroke: #000;
+  stroke-width: 1.5;
+  cursor: pointer;
+}
+
+text {
+  font: 11px sans-serif;
+  cursor: pointer;
+}
+
+body {
+  width: 1280px;
+  margin: 0 auto;
+}
+
+h1 {
+  text-align: center;
+  margin: .5em 0;
+}
+
+p#intro {
+  text-align: center;
+  margin: 1em 0;
+}
+</style>
+<h1>Demo</h1>
+<div id="vis"><img src="full.png"></div>
+
+<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.4/d3.min.js"></script>
+<script>
+var width = 1280,
+    height = width,
+    radius = width / 2,
+    x = d3.scale.linear().range([0, 2 * Math.PI]),
+    y = d3.scale.pow().exponent(1.3).domain([0, 1]).range([0, radius]),
+    padding = 5,
+    duration = 1000;
+
+var div = d3.select("#vis");
+
+div.select("img").remove();
+
+var vis = div.append("svg")
+    .attr("width", width + padding * 2)
+    .attr("height", height + padding * 2)
+  .append("g")
+    .attr("transform", "translate(" + [radius + padding, radius + padding] + ")");
+
+div.append("p")
+    .attr("id", "intro")
+    .text("Click to zoom!");
+
+var partition = d3.layout.partition()
+    .sort(null)
+    .value(function(d) { return 5.8 - d.depth; });
+
+var arc = d3.svg.arc()
+    .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
+    .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
+    .innerRadius(function(d) { return Math.max(0, d.y ? y(d.y) : d.y); })
+    .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
+
+d3.json("../json.php", function(error, json) {
+  var nodes = partition.nodes({children: json});
+
+  var path = vis.selectAll("path").data(nodes);
+  path.enter().append("path")
+      .attr("id", function(d, i) { return "path-" + i; })
+      .attr("d", arc)
+      .attr("fill-rule", "evenodd")
+      .style("fill", colour)
+      .on("click", click);
+
+  var text = vis.selectAll("text").data(nodes);
+  var textEnter = text.enter().append("text")
+      .style("fill-opacity", 1)
+      .style("fill", function(d) {
+        return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000";
+      })
+      .attr("text-anchor", function(d) {
+        return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
+      })
+      .attr("dy", ".2em")
+      .attr("transform", function(d) {
+        var multiline = (d.name || "").split(" ").length > 3,
+            angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
+            rotate = angle + (multiline ? -.5 : 0);
+        return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
+      })
+      .on("click", click);
+  textEnter.append("tspan")
+      .attr("x", 0)
+      .text(function(d) { return d.depth ? d.name.split("_")[0] : ""; });
+  textEnter.append("tspan")
+      .attr("x", 0)
+      .attr("dy", "1em")
+      .text(function(d) { return d.depth ? d.name.split("_")[1] || "" : ""; });
+
+  function click(d) {
+    path.transition()
+      .duration(duration)
+      .attrTween("d", arcTween(d));
+
+    // Somewhat of a hack as we rely on arcTween updating the scales.
+    text.style("visibility", function(e) {
+          return isParentOf(d, e) ? null : d3.select(this).style("visibility");
+        })
+      .transition()
+        .duration(duration)
+        .attrTween("text-anchor", function(d) {
+          return function() {
+            return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
+          };
+        })
+        .attrTween("transform", function(d) {
+          var multiline = (d.name || "").split(" ").length > 3;
+          return function() {
+            var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
+                rotate = angle + (multiline ? -.5 : 0);
+            return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
+          };
+        })
+        .style("fill-opacity", function(e) { return isParentOf(d, e) ? 1 : 1e-6; })
+        .each("end", function(e) {
+          d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden");
+        });
+  }
+});
+
+function isParentOf(p, c) {
+  if (p === c) return true;
+  if (p.children) {
+    return p.children.some(function(d) {
+      return isParentOf(d, c);
+    });
+  }
+  return false;
+}
+
+function colour(d) {
+  if (d.children) {
+    // There is a maximum of two children!
+    var colours = d.children.map(colour),
+        a = d3.hsl(colours[0]),
+        b = d3.hsl(colours[1]);
+    // L*a*b* might be better here...
+    return d3.hsl((a.h + b.h) / 2, a.s * 1.2, a.l / 1.2);
+  }
+  return d.colour || "#fff";
+}
+
+// Interpolate the scales!
+function arcTween(d) {
+  var my = maxY(d),
+      xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
+      yd = d3.interpolate(y.domain(), [d.y, my]),
+      yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
+  return function(d) {
+    return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
+  };
+}
+
+function maxY(d) {
+  return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
+}
+
+// http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
+function brightness(rgb) {
+  return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
+}
+</script>
+

file:b/wwwroot/style.css (new)
--- /dev/null
+++ b/wwwroot/style.css
@@ -1,1 +1,73 @@
 
+.date {
+  font-size: small;
+  font-style: italic;
+  margin: 0;
+  width: 100%;
+  border-top: solid #eee 1px;
+}
+
+.breadcrumbs {
+  font-size: small;
+  font-family: sans-serif;
+  width: 100%;
+  margin: 0;
+  padding-top: 3px;
+  font-style: italic;
+  border-bottom: solid #eee 1px;
+  color: #999;
+}
+
+a {
+  color: steelblue;
+}
+
+a:not(:hover) {
+  text-decoration: none;
+}
+
+body {
+  margin: 0 auto;
+  font-family: 'PT Serif', Georgia, Times, 'Times New Roman', serif;
+  line-height: 1.5em;
+  color: #333;
+  background: #fefefe;
+  width: 960px;
+  position: relative;
+}
+
+p {
+  width: 720px;
+}
+
+h1, h2, h3 {
+  font-weight: 300;
+  text-rendering: optimizeLegibility;
+}
+
+h1 {
+  font-size: 2.5em;
+}
+
+aside {
+  font-size: small;
+  position: absolute;
+  right: 0;
+  width: 180px;
+}
+
+aside p {
+  width: auto;
+}
+
+p.cite, p.caption {
+  color: #666;
+}
+
+p.caption {
+  text-align: center;
+  font-size: small;
+  font-style: italic;
+  width: auto;
+}
+

comments