diff --git a/viz/ts/svg/svg.sln b/viz/ts/svg/svg.sln
index d32d2a5..50bd8ef 100644
--- a/viz/ts/svg/svg.sln
+++ b/viz/ts/svg/svg.sln
@@ -3,12 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27428.2027
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "svg", "svg\svg.njsproj", "{8C2DAE18-8B11-4B37-A4EC-0234D7FCCF2A}"
-EndProject
Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "thinkChart", "thinkChart\thinkChart.njsproj", "{F3C0F5AB-B554-4EF8-9DF0-482240FE020D}"
- ProjectSection(ProjectDependencies) = postProject
- {8C2DAE18-8B11-4B37-A4EC-0234D7FCCF2A} = {8C2DAE18-8B11-4B37-A4EC-0234D7FCCF2A}
- EndProjectSection
EndProject
Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "Linq.ts", "..\TsLinq\Linq.ts\Linq.ts.njsproj", "{E0AEC189-6D05-47E4-9F69-DAFE43AC8398}"
EndProject
@@ -18,10 +13,6 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {8C2DAE18-8B11-4B37-A4EC-0234D7FCCF2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8C2DAE18-8B11-4B37-A4EC-0234D7FCCF2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8C2DAE18-8B11-4B37-A4EC-0234D7FCCF2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8C2DAE18-8B11-4B37-A4EC-0234D7FCCF2A}.Release|Any CPU.Build.0 = Release|Any CPU
{F3C0F5AB-B554-4EF8-9DF0-482240FE020D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3C0F5AB-B554-4EF8-9DF0-482240FE020D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3C0F5AB-B554-4EF8-9DF0-482240FE020D}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/viz/ts/svg/thinkChart/Math/Geometry.ts b/viz/ts/svg/thinkChart/Math/Geometry.ts
new file mode 100644
index 0000000..46b9cee
--- /dev/null
+++ b/viz/ts/svg/thinkChart/Math/Geometry.ts
@@ -0,0 +1,26 @@
+namespace Math2D.Geometry {
+
+ /**
+ * returns true if two lines intersect, else false
+ * from http://paulbourke.net/geometry/lineline2d/
+ */
+ export function intersect(x1: number, x2: number, x3: number, x4: number,
+ y1: number, y2: number, y3: number, y4: number): boolean {
+
+ var mua, mub;
+ var denom, numera, numerb;
+
+ denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
+ numera = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
+ numerb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
+
+ /* Is the intersection along the the segments */
+ mua = numera / denom;
+ mub = numerb / denom;
+ if (!(mua < 0 || mua > 1 || mub < 0 || mub > 1)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/viz/ts/svg/thinkChart/Math/Layout/Labeler.ts b/viz/ts/svg/thinkChart/Math/Layout/Labeler.ts
new file mode 100644
index 0000000..39ba248
--- /dev/null
+++ b/viz/ts/svg/thinkChart/Math/Layout/Labeler.ts
@@ -0,0 +1,260 @@
+namespace Layout {
+
+ export class Labeler {
+
+ private lab = [];
+ private anc = [];
+ private w = 1; // box width
+ private h = 1; // box width
+
+ private max_move = 5.0;
+ private max_angle = 0.5;
+ private acc = 0;
+ private rej = 0;
+
+ //#region "weights"
+ private w_len = 0.2; // leader line length
+ private w_inter = 1.0; // leader line intersection
+ private w_lab2 = 30.0; // label-label overlap
+ private w_lab_anc = 30.0; // label-anchor overlap
+ private w_orient = 3.0; // orientation bias
+ //#endregion
+
+ //#region ""
+ private energy_function: (index: number) => number;
+ private schedule_function: (currT: number, initialT: number, nsweeps: number) => number;
+ //#endregion
+
+ public constructor(
+ energy: (index: number) => number = null,
+ schedule: (currT: number, initialT: number, nsweeps: number) => number = Labeler.cooling_schedule) {
+
+ this.energy_function = energy == null ? this.energy : energy;
+ this.schedule_function = schedule;
+ }
+
+ /**
+ * linear cooling
+ */
+ private static cooling_schedule(currT: number, initialT: number, nsweeps: number): number {
+ return (currT - (initialT / nsweeps));
+ }
+
+ /**
+ * energy function, tailored for label placement
+ */
+ energy(index: number): number {
+ var lab = this.lab;
+ var anc = this.anc;
+ var m = lab.length,
+ ener = 0,
+ dx = lab[index].x - anc[index].x,
+ dy = anc[index].y - lab[index].y,
+ dist = Math.sqrt(dx * dx + dy * dy),
+ overlap = true,
+ amount = 0;
+ var theta = 0;
+
+ // penalty for length of leader line
+ if (dist > 0) ener += dist * w_len;
+
+ // label orientation bias
+ dx /= dist;
+ dy /= dist;
+ if (dx > 0 && dy > 0) { ener += 0 * w_orient; }
+ else if (dx < 0 && dy > 0) { ener += 1 * w_orient; }
+ else if (dx < 0 && dy < 0) { ener += 2 * w_orient; }
+ else { ener += 3 * w_orient; }
+
+ var x21 = lab[index].x,
+ y21 = lab[index].y - lab[index].height + 2.0,
+ x22 = lab[index].x + lab[index].width,
+ y22 = lab[index].y + 2.0;
+ var x11, x12, y11, y12, x_overlap, y_overlap, overlap_area;
+
+ for (var i = 0; i < m; i++) {
+ if (i != index) {
+
+ // penalty for intersection of leader lines
+ overlap = Math2D.Geometry.intersect(anc[index].x, lab[index].x, anc[i].x, lab[i].x,
+ anc[index].y, lab[index].y, anc[i].y, lab[i].y);
+ if (overlap) ener += w_inter;
+
+ // penalty for label-label overlap
+ x11 = lab[i].x;
+ y11 = lab[i].y - lab[i].height + 2.0;
+ x12 = lab[i].x + lab[i].width;
+ y12 = lab[i].y + 2.0;
+ x_overlap = Math.max(0, Math.min(x12, x22) - Math.max(x11, x21));
+ y_overlap = Math.max(0, Math.min(y12, y22) - Math.max(y11, y21));
+ overlap_area = x_overlap * y_overlap;
+ ener += (overlap_area * w_lab2);
+ }
+
+ // penalty for label-anchor overlap
+ x11 = anc[i].x - anc[i].r;
+ y11 = anc[i].y - anc[i].r;
+ x12 = anc[i].x + anc[i].r;
+ y12 = anc[i].y + anc[i].r;
+ x_overlap = Math.max(0, Math.min(x12, x22) - Math.max(x11, x21));
+ y_overlap = Math.max(0, Math.min(y12, y22) - Math.max(y11, y21));
+ overlap_area = x_overlap * y_overlap;
+ ener += (overlap_area * w_lab_anc);
+
+ }
+ return ener;
+ }
+
+ /**
+ * Monte Carlo translation move
+ */
+ mcmove(currT: number) {
+ var lab = this.lab;
+ var anc = this.anc;
+
+ // select a random label
+ var i = Math.floor(Math.random() * lab.length);
+
+ // save old coordinates
+ var x_old = lab[i].x;
+ var y_old = lab[i].y;
+
+ // old energy
+ var old_energy;
+ if (user_energy) { old_energy = user_defined_energy(i, lab, anc) }
+ else { old_energy = energy(i) }
+
+ // random translation
+ lab[i].x += (Math.random() - 0.5) * max_move;
+ lab[i].y += (Math.random() - 0.5) * max_move;
+
+ // hard wall boundaries
+ if (lab[i].x > w) lab[i].x = x_old;
+ if (lab[i].x < 0) lab[i].x = x_old;
+ if (lab[i].y > h) lab[i].y = y_old;
+ if (lab[i].y < 0) lab[i].y = y_old;
+
+ // new energy
+ var new_energy;
+ if (user_energy) { new_energy = user_defined_energy(i, lab, anc) }
+ else { new_energy = energy(i) }
+
+ // delta E
+ var delta_energy = new_energy - old_energy;
+
+ if (Math.random() < Math.exp(-delta_energy / currT)) {
+ acc += 1;
+ } else {
+ // move back to old coordinates
+ lab[i].x = x_old;
+ lab[i].y = y_old;
+ rej += 1;
+ }
+ }
+
+ /**
+ * Monte Carlo rotation move
+ */
+ mcrotate(currT: number) {
+ var lab = this.lab;
+ var anc = this.anc;
+
+ // select a random label
+ var i = Math.floor(Math.random() * lab.length);
+
+ // save old coordinates
+ var x_old = lab[i].x;
+ var y_old = lab[i].y;
+
+ // old energy
+ var old_energy;
+ if (user_energy) { old_energy = user_defined_energy(i, lab, anc) }
+ else { old_energy = energy(i) }
+
+ // random angle
+ var angle = (Math.random() - 0.5) * max_angle;
+
+ var s = Math.sin(angle);
+ var c = Math.cos(angle);
+
+ // translate label (relative to anchor at origin):
+ lab[i].x -= anc[i].x
+ lab[i].y -= anc[i].y
+
+ // rotate label
+ var x_new = lab[i].x * c - lab[i].y * s,
+ y_new = lab[i].x * s + lab[i].y * c;
+
+ // translate label back
+ lab[i].x = x_new + anc[i].x
+ lab[i].y = y_new + anc[i].y
+
+ // hard wall boundaries
+ if (lab[i].x > w) lab[i].x = x_old;
+ if (lab[i].x < 0) lab[i].x = x_old;
+ if (lab[i].y > h) lab[i].y = y_old;
+ if (lab[i].y < 0) lab[i].y = y_old;
+
+ // new energy
+ var new_energy;
+ if (user_energy) { new_energy = user_defined_energy(i, lab, anc) }
+ else { new_energy = energy(i) }
+
+ // delta E
+ var delta_energy = new_energy - old_energy;
+
+ if (Math.random() < Math.exp(-delta_energy / currT)) {
+ acc += 1;
+ } else {
+ // move back to old coordinates
+ lab[i].x = x_old;
+ lab[i].y = y_old;
+ rej += 1;
+ }
+
+ }
+
+ start(nsweeps) {
+ // main simulated annealing function
+ var m = lab.length,
+ currT = 1.0,
+ initialT = 1.0;
+
+ for (var i = 0; i < nsweeps; i++) {
+ for (var j = 0; j < m; j++) {
+ if (Math.random() < 0.5) { mcmove(currT); }
+ else { mcrotate(currT); }
+ }
+ currT = cooling_schedule(currT, initialT, nsweeps);
+ }
+ }
+
+ width(x) {
+ // users insert graph width
+ if (!arguments.length) return w;
+ w = x;
+ return labeler;
+ }
+
+ height(x) {
+ // users insert graph height
+ if (!arguments.length) return h;
+ h = x;
+ return labeler;
+ }
+
+ label(x) {
+ // users insert label positions
+ if (!arguments.length) return lab;
+ lab = x;
+ return labeler;
+ }
+
+ anchor(x) {
+ // users insert anchor positions
+ if (!arguments.length) return anc;
+ anc = x;
+ return labeler;
+ }
+ }
+}
\ No newline at end of file
diff --git a/viz/ts/svg/thinkChart/thinkChart.njsproj b/viz/ts/svg/thinkChart/thinkChart.njsproj
index 464f9a1..4729ece 100644
--- a/viz/ts/svg/thinkChart/thinkChart.njsproj
+++ b/viz/ts/svg/thinkChart/thinkChart.njsproj
@@ -40,11 +40,19 @@
+
+ Code
+
+
+ Code
+
+
+