summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java')
-rw-r--r--src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java659
1 files changed, 331 insertions, 328 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java
index 6070361e..2806812a 100644
--- a/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java
+++ b/src/de/lmu/ifi/dbs/elki/visualization/visualizers/scatterplot/cluster/EMClusterVisualization.java
@@ -32,6 +32,7 @@ import org.w3c.dom.Element;
import de.lmu.ifi.dbs.elki.data.Cluster;
import de.lmu.ifi.dbs.elki.data.Clustering;
+import de.lmu.ifi.dbs.elki.data.DoubleVector;
import de.lmu.ifi.dbs.elki.data.NumberVector;
import de.lmu.ifi.dbs.elki.data.model.EMModel;
import de.lmu.ifi.dbs.elki.data.model.MeanModel;
@@ -69,114 +70,199 @@ import de.lmu.ifi.dbs.elki.visualization.visualizers.scatterplot.AbstractScatter
*
* @author Robert Rödler
*
- * @apiviz.has EMModel oneway - - visualizes
- * @apiviz.uses GrahamScanConvexHull2D
- *
- * @param <NV> Type of the NumberVector being visualized.
+ * @apiviz.stereotype factory
+ * @apiviz.uses EMClusterVisualization oneway - - «create»
*/
-// TODO: nicer stacking of n-fold hulls
-// TODO: can we find a proper sphere for 3+ dimensions?
-public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends AbstractScatterplotVisualization {
+public class EMClusterVisualization extends AbstractVisFactory {
/**
* A short name characterizing this Visualizer.
*/
private static final String NAME = "EM Cluster Visualization";
/**
- * Generic tags to indicate the type of element. Used in IDs, CSS-Classes etc.
+ * Constants for quantiles of standard deviation
*/
- public static final String EMBORDER = "EMClusterBorder";
+ final static double[] sigma = new double[] { 0.41, 0.223, 0.047 };
/**
- * The result we work on
+ * Constructor
*/
- Clustering<EMModel<NV>> clustering;
+ public EMClusterVisualization() {
+ super();
+ }
- private static final double KAPPA = SVGHyperSphere.EUCLIDEAN_KAPPA;
+ @Override
+ public Instance<DoubleVector> makeVisualization(VisualizationTask task) {
+ return new Instance<DoubleVector>(task);
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result result) {
+ // Find clusterings we can visualize:
+ Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
+ for(Clustering<?> c : clusterings) {
+ if(c.getAllClusters().size() > 0) {
+ // Does the cluster have a model with cluster means?
+ Clustering<MeanModel<DoubleVector>> mcls = findMeanModel(c);
+ if(mcls != null) {
+ Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
+ for(ScatterPlotProjector<?> p : ps) {
+ final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
+ task.level = VisualizationTask.LEVEL_DATA + 3;
+ baseResult.getHierarchy().add(c, task);
+ baseResult.getHierarchy().add(p, task);
+ }
+ }
+ }
+ }
+ }
/**
- * StyleParameter:
+ * Test if the given clustering has a mean model.
+ *
+ * @param <NV> Vector type
+ * @param c Clustering to inspect
+ * @return the clustering cast to return a mean model, null otherwise.
*/
- private int times = 3;
+ @SuppressWarnings("unchecked")
+ private static <NV extends NumberVector<?>> Clustering<MeanModel<NV>> findMeanModel(Clustering<?> c) {
+ final Model firstModel = c.getAllClusters().get(0).getModel();
+ if(c.getAllClusters().get(0).getModel() instanceof MeanModel<?> && firstModel instanceof EMModel<?>) {
+ return (Clustering<MeanModel<NV>>) c;
+ }
+ return null;
+ }
- private int opacStyle = 1;
+ /**
+ * Instance.
+ *
+ * @author Robert Rödler
+ *
+ * @apiviz.has EMModel oneway - - visualizes
+ * @apiviz.uses GrahamScanConvexHull2D
+ *
+ * @param <NV> Type of the NumberVector being visualized.
+ */
+ // TODO: nicer stacking of n-fold hulls
+ // TODO: can we find a proper sphere for 3+ dimensions?
+ public class Instance<NV extends NumberVector<?>> extends AbstractScatterplotVisualization {
+ /**
+ * Generic tags to indicate the type of element. Used in IDs, CSS-Classes
+ * etc.
+ */
+ public static final String EMBORDER = "EMClusterBorder";
- private int softBorder = 1;
+ /**
+ * The result we work on
+ */
+ Clustering<EMModel<NV>> clustering;
- private int drawStyle = 0;
+ private static final double KAPPA = SVGHyperSphere.EUCLIDEAN_KAPPA;
- final static double[] sigma = new double[] { 0.41, 0.223, 0.047 };
+ /**
+ * StyleParameter:
+ */
+ private int times = 3;
- /**
- * Constructor
- *
- * @param task VisualizationTask
- */
- public EMClusterVisualization(VisualizationTask task) {
- super(task);
- this.clustering = task.getResult();
- incrementalRedraw();
- }
+ private int opacStyle = 1;
- @Override
- protected void redraw() {
- // set styles
- addCSSClasses(svgp);
-
- // PCARunner
- PCARunner<NV> pcarun = ClassGenericsUtil.parameterizeOrAbort(PCARunner.class, new EmptyParameterization());
-
- Iterator<Cluster<EMModel<NV>>> ci = clustering.getAllClusters().iterator();
- for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
- Cluster<EMModel<NV>> clus = ci.next();
- DBIDs ids = clus.getIDs();
-
- if(ids.size() > 0) {
- Matrix covmat = clus.getModel().getCovarianceMatrix();
- NV centroid = clus.getModel().getMean();
- Vector cent = new Vector(proj.fastProjectDataToRenderSpace(centroid));
-
- // Compute the eigenvectors
- SortedEigenPairs eps = pcarun.processCovarMatrix(covmat).getEigenPairs();
-
- Vector[] pc = new Vector[eps.size()];
- for(int i = 0; i < eps.size(); i++) {
- EigenPair ep = eps.getEigenPair(i);
- Vector sev = ep.getEigenvector().times(Math.sqrt(ep.getEigenvalue()));
- pc[i] = new Vector(proj.fastProjectRelativeDataToRenderSpace(sev.getArrayRef()));
- }
- if(drawStyle != 0 || eps.size() == 2) {
- drawSphere2D(cnum, cent, pc);
+ private int softBorder = 1;
+
+ private int drawStyle = 0;
+
+ /**
+ * Constructor
+ *
+ * @param task VisualizationTask
+ */
+ public Instance(VisualizationTask task) {
+ super(task);
+ this.clustering = task.getResult();
+ incrementalRedraw();
+ }
+
+ @Override
+ protected void redraw() {
+ // set styles
+ addCSSClasses(svgp);
+
+ // PCARunner
+ PCARunner<NV> pcarun = ClassGenericsUtil.parameterizeOrAbort(PCARunner.class, new EmptyParameterization());
+
+ Iterator<Cluster<EMModel<NV>>> ci = clustering.getAllClusters().iterator();
+ for(int cnum = 0; cnum < clustering.getAllClusters().size(); cnum++) {
+ Cluster<EMModel<NV>> clus = ci.next();
+ DBIDs ids = clus.getIDs();
+
+ if(ids.size() > 0) {
+ Matrix covmat = clus.getModel().getCovarianceMatrix();
+ NV centroid = clus.getModel().getMean();
+ Vector cent = new Vector(proj.fastProjectDataToRenderSpace(centroid));
+
+ // Compute the eigenvectors
+ SortedEigenPairs eps = pcarun.processCovarMatrix(covmat).getEigenPairs();
+
+ Vector[] pc = new Vector[eps.size()];
+ for(int i = 0; i < eps.size(); i++) {
+ EigenPair ep = eps.getEigenPair(i);
+ Vector sev = ep.getEigenvector().times(Math.sqrt(ep.getEigenvalue()));
+ pc[i] = new Vector(proj.fastProjectRelativeDataToRenderSpace(sev.getArrayRef()));
+ }
+ if(drawStyle != 0 || eps.size() == 2) {
+ drawSphere2D(cnum, cent, pc);
+ }
+ else {
+ Polygon chres = makeHullComplex(pc);
+ drawHullLines(cnum, cent, chres);
+ }
}
- else {
- Polygon chres = makeHullComplex(pc);
- drawHullLines(cnum, cent, chres);
+ }
+ }
+
+ protected void drawSphere2D(int cnum, Vector cent, Vector[] pc) {
+ for(int dim1 = 0; dim1 < pc.length - 1; dim1++) {
+ for(int dim2 = dim1 + 1; dim2 < pc.length; dim2++) {
+ for(int i = 1; i <= times; i++) {
+ SVGPath path = new SVGPath();
+
+ Vector direction1 = pc[dim1].times(KAPPA * i);
+ Vector direction2 = pc[dim2].times(KAPPA * i);
+
+ Vector p1 = cent.plusTimes(pc[dim1], i);
+ Vector p2 = cent.plusTimes(pc[dim2], i);
+ Vector p3 = cent.minusTimes(pc[dim1], i);
+ Vector p4 = cent.minusTimes(pc[dim2], i);
+
+ path.moveTo(p1);
+ path.cubicTo(p1.plus(direction2), p2.plus(direction1), p2);
+ path.cubicTo(p2.minus(direction1), p3.plus(direction2), p3);
+ path.cubicTo(p3.minus(direction2), p4.minus(direction1), p4);
+ path.cubicTo(p4.plus(direction1), p1.minus(direction2), p1);
+ path.close();
+
+ Element ellipse = path.makeElement(svgp);
+ SVGUtil.addCSSClass(ellipse, EMBORDER + cnum);
+ if(opacStyle == 1) {
+ CSSClass cls = new CSSClass(null, "temp");
+ double s = (i >= 1 && i <= sigma.length) ? sigma[i - 1] : 0.0;
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, s);
+ SVGUtil.setAtt(ellipse, SVGConstants.SVG_STYLE_ATTRIBUTE, cls.inlineCSS());
+ }
+ layer.appendChild(ellipse);
+ }
}
}
}
- }
- protected void drawSphere2D(int cnum, Vector cent, Vector[] pc) {
- for(int dim1 = 0; dim1 < pc.length - 1; dim1++) {
- for(int dim2 = dim1 + 1; dim2 < pc.length; dim2++) {
+ protected void drawHullLines(int cnum, Vector cent, Polygon chres) {
+ if(chres.size() > 1) {
for(int i = 1; i <= times; i++) {
SVGPath path = new SVGPath();
-
- Vector direction1 = pc[dim1].times(KAPPA * i);
- Vector direction2 = pc[dim2].times(KAPPA * i);
-
- Vector p1 = cent.plusTimes(pc[dim1], i);
- Vector p2 = cent.plusTimes(pc[dim2], i);
- Vector p3 = cent.minusTimes(pc[dim1], i);
- Vector p4 = cent.minusTimes(pc[dim2], i);
-
- path.moveTo(p1);
- path.cubicTo(p1.plus(direction2), p2.plus(direction1), p2);
- path.cubicTo(p2.minus(direction1), p3.plus(direction2), p3);
- path.cubicTo(p3.minus(direction2), p4.minus(direction1), p4);
- path.cubicTo(p4.plus(direction1), p1.minus(direction2), p1);
+ for(int p = 0; p < chres.size(); p++) {
+ Vector cur = cent.plusTimes(chres.get(p), i);
+ path.drawTo(cur);
+ }
path.close();
-
Element ellipse = path.makeElement(svgp);
SVGUtil.addCSSClass(ellipse, EMBORDER + cnum);
if(opacStyle == 1) {
@@ -189,18 +275,99 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends Abst
}
}
}
- }
- protected void drawHullLines(int cnum, Vector cent, Polygon chres) {
- if(chres.size() > 1) {
+ protected Polygon makeHull(Vector[] pc) {
+ GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
+
+ Vector diag = new Vector(0, 0);
+ for(int j = 0; j < pc.length; j++) {
+ hull.add(pc[j]);
+ hull.add(pc[j].times(-1));
+ for(int k = j + 1; k < pc.length; k++) {
+ Vector q = pc[k];
+ Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF);
+ Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF);
+ hull.add(ppq);
+ hull.add(ppq.times(-1));
+ hull.add(pmq);
+ hull.add(pmq.times(-1));
+ }
+ diag.plusEquals(pc[j]);
+ }
+ diag.timesEquals(1.0 / Math.sqrt(pc.length));
+ hull.add(diag);
+ hull.add(diag.times(-1));
+
+ Polygon chres = hull.getHull();
+ return chres;
+ }
+
+ protected Polygon makeHullComplex(Vector[] pc) {
+ GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
+
+ Vector diag = new Vector(0, 0);
+ for(int j = 0; j < pc.length; j++) {
+ hull.add(pc[j]);
+ hull.add(pc[j].times(-1));
+ for(int k = j + 1; k < pc.length; k++) {
+ Vector q = pc[k];
+ Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF);
+ Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF);
+ hull.add(ppq);
+ hull.add(ppq.times(-1));
+ hull.add(pmq);
+ hull.add(pmq.times(-1));
+ for(int l = k + 1; l < pc.length; l++) {
+ Vector r = pc[k];
+ Vector ppqpr = ppq.plus(r).timesEquals(Math.sqrt(1 / 3.));
+ Vector pmqpr = pmq.plus(r).timesEquals(Math.sqrt(1 / 3.));
+ Vector ppqmr = ppq.minus(r).timesEquals(Math.sqrt(1 / 3.));
+ Vector pmqmr = pmq.minus(r).timesEquals(Math.sqrt(1 / 3.));
+ hull.add(ppqpr);
+ hull.add(ppqpr.times(-1));
+ hull.add(pmqpr);
+ hull.add(pmqpr.times(-1));
+ hull.add(ppqmr);
+ hull.add(ppqmr.times(-1));
+ hull.add(pmqmr);
+ hull.add(pmqmr.times(-1));
+ }
+ }
+ diag.plusEquals(pc[j]);
+ }
+ diag.timesEquals(1.0 / Math.sqrt(pc.length));
+ hull.add(diag);
+ hull.add(diag.times(-1));
+ Polygon chres = hull.getHull();
+ return chres;
+ }
+
+ protected void drawHullArc(int cnum, Vector cent, Polygon chres) {
for(int i = 1; i <= times; i++) {
SVGPath path = new SVGPath();
+
+ ArrayList<Vector> delta = new ArrayList<Vector>(chres.size());
+ for(int p = 0; p < chres.size(); p++) {
+ Vector prev = chres.get((p - 1 + chres.size()) % chres.size());
+ Vector curr = chres.get(p);
+ Vector next = chres.get((p + 1) % chres.size());
+ Vector d1 = next.minus(curr).normalize();
+ Vector d2 = curr.minus(prev).normalize();
+ delta.add(d1.plus(d2));
+ // delta.add(next.minus(prev));
+ }
+
for(int p = 0; p < chres.size(); p++) {
- Vector cur = cent.plusTimes(chres.get(p), i);
- path.drawTo(cur);
+ Vector cur = cent.plus(chres.get(p));
+ Vector nex = cent.plus(chres.get((p + 1) % chres.size()));
+ Vector dcur = delta.get(p);
+ Vector dnex = delta.get((p + 1) % chres.size());
+ drawArc(path, cent, cur, nex, dcur, dnex, i);
}
path.close();
+
Element ellipse = path.makeElement(svgp);
+
SVGUtil.addCSSClass(ellipse, EMBORDER + cnum);
if(opacStyle == 1) {
CSSClass cls = new CSSClass(null, "temp");
@@ -211,262 +378,98 @@ public class EMClusterVisualization<NV extends NumberVector<NV, ?>> extends Abst
layer.appendChild(ellipse);
}
}
- }
-
- protected Polygon makeHull(Vector[] pc) {
- GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
-
- Vector diag = new Vector(0, 0);
- for(int j = 0; j < pc.length; j++) {
- hull.add(pc[j]);
- hull.add(pc[j].times(-1));
- for(int k = j + 1; k < pc.length; k++) {
- Vector q = pc[k];
- Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF);
- Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF);
- hull.add(ppq);
- hull.add(ppq.times(-1));
- hull.add(pmq);
- hull.add(pmq.times(-1));
- }
- diag.plusEquals(pc[j]);
- }
- diag.timesEquals(1.0 / Math.sqrt(pc.length));
- hull.add(diag);
- hull.add(diag.times(-1));
-
- Polygon chres = hull.getHull();
- return chres;
- }
-
- protected Polygon makeHullComplex(Vector[] pc) {
- GrahamScanConvexHull2D hull = new GrahamScanConvexHull2D();
-
- Vector diag = new Vector(0, 0);
- for(int j = 0; j < pc.length; j++) {
- hull.add(pc[j]);
- hull.add(pc[j].times(-1));
- for(int k = j + 1; k < pc.length; k++) {
- Vector q = pc[k];
- Vector ppq = pc[j].plus(q).timesEquals(MathUtil.SQRTHALF);
- Vector pmq = pc[j].minus(q).timesEquals(MathUtil.SQRTHALF);
- hull.add(ppq);
- hull.add(ppq.times(-1));
- hull.add(pmq);
- hull.add(pmq.times(-1));
- for(int l = k + 1; l < pc.length; l++) {
- Vector r = pc[k];
- Vector ppqpr = ppq.plus(r).timesEquals(Math.sqrt(1 / 3.));
- Vector pmqpr = pmq.plus(r).timesEquals(Math.sqrt(1 / 3.));
- Vector ppqmr = ppq.minus(r).timesEquals(Math.sqrt(1 / 3.));
- Vector pmqmr = pmq.minus(r).timesEquals(Math.sqrt(1 / 3.));
- hull.add(ppqpr);
- hull.add(ppqpr.times(-1));
- hull.add(pmqpr);
- hull.add(pmqpr.times(-1));
- hull.add(ppqmr);
- hull.add(ppqmr.times(-1));
- hull.add(pmqmr);
- hull.add(pmqmr.times(-1));
- }
- }
- diag.plusEquals(pc[j]);
- }
- diag.timesEquals(1.0 / Math.sqrt(pc.length));
- hull.add(diag);
- hull.add(diag.times(-1));
- Polygon chres = hull.getHull();
- return chres;
- }
- protected void drawHullArc(int cnum, Vector cent, Polygon chres) {
- for(int i = 1; i <= times; i++) {
- SVGPath path = new SVGPath();
-
- ArrayList<Vector> delta = new ArrayList<Vector>(chres.size());
- for(int p = 0; p < chres.size(); p++) {
- Vector prev = chres.get((p - 1 + chres.size()) % chres.size());
- Vector curr = chres.get(p);
- Vector next = chres.get((p + 1) % chres.size());
- Vector d1 = next.minus(curr).normalize();
- Vector d2 = curr.minus(prev).normalize();
- delta.add(d1.plus(d2));
- // delta.add(next.minus(prev));
- }
-
- for(int p = 0; p < chres.size(); p++) {
- Vector cur = cent.plus(chres.get(p));
- Vector nex = cent.plus(chres.get((p + 1) % chres.size()));
- Vector dcur = delta.get(p);
- Vector dnex = delta.get((p + 1) % chres.size());
- drawArc(path, cent, cur, nex, dcur, dnex, i);
+ /**
+ * Draw an arc to simulate the hyper ellipse.
+ *
+ * @param path Path to draw to
+ * @param cent Center
+ * @param pre Previous point
+ * @param nex Next point
+ * @param scale Scaling factor
+ */
+ private void drawArc(SVGPath path, Vector cent, Vector pre, Vector nex, Vector oPrev, Vector oNext, double scale) {
+ // Delta vectors
+ final Vector rPrev = pre.minus(cent);
+ final Vector rNext = nex.minus(cent);
+ final Vector rPrNe = pre.minus(nex);
+ // Scaled fix points
+ final Vector sPrev = cent.plusTimes(rPrev, scale);
+ final Vector sNext = cent.plusTimes(rNext, scale);
+ // Orthogonal vectors to the relative vectors
+ // final Vector oPrev = new Vector(rPrev.get(1), -rPrev.get(0));
+ // final Vector oNext = new Vector(-rNext.get(1), rNext.get(0));
+
+ // Compute the intersection of rPrev+tp*oPrev and rNext+tn*oNext
+ // rPrNe == rPrev - rNext
+ final double zp = rPrNe.get(0) * oNext.get(1) - rPrNe.get(1) * oNext.get(0);
+ final double zn = rPrNe.get(0) * oPrev.get(1) - rPrNe.get(1) * oPrev.get(0);
+ final double n = oPrev.get(1) * oNext.get(0) - oPrev.get(0) * oNext.get(1);
+ if(n == 0) {
+ LoggingUtil.warning("Parallel?!?");
+ path.drawTo(sNext.get(0), sNext.get(1));
+ return;
}
- path.close();
+ final double tp = Math.abs(zp / n);
+ final double tn = Math.abs(zn / n);
+ // LoggingUtil.warning("tp: "+tp+" tn: "+tn);
- Element ellipse = path.makeElement(svgp);
+ // Guide points
+ final Vector gPrev = sPrev.plusTimes(oPrev, KAPPA * scale * tp);
+ final Vector gNext = sNext.minusTimes(oNext, KAPPA * scale * tn);
- SVGUtil.addCSSClass(ellipse, EMBORDER + cnum);
- if(opacStyle == 1) {
- CSSClass cls = new CSSClass(null, "temp");
- double s = (i >= 1 && i <= sigma.length) ? sigma[i - 1] : 0.0;
- cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, s);
- SVGUtil.setAtt(ellipse, SVGConstants.SVG_STYLE_ATTRIBUTE, cls.inlineCSS());
+ if(!path.isStarted()) {
+ path.moveTo(sPrev);
}
- layer.appendChild(ellipse);
+ // path.drawTo(sPrev);
+ // path.drawTo(gPrev);
+ // path.drawTo(gNext);
+ // path.drawTo(sNext));
+ // path.moveTo(sPrev);
+ // if(tp < 0 || tn < 0) {
+ // path.drawTo(sNext);
+ // }
+ // else {
+ path.cubicTo(gPrev, gNext, sNext);
+ // }
}
- }
- /**
- * Draw an arc to simulate the hyper ellipse.
- *
- * @param path Path to draw to
- * @param cent Center
- * @param pre Previous point
- * @param nex Next point
- * @param scale Scaling factor
- */
- private void drawArc(SVGPath path, Vector cent, Vector pre, Vector nex, Vector oPrev, Vector oNext, double scale) {
- // Delta vectors
- final Vector rPrev = pre.minus(cent);
- final Vector rNext = nex.minus(cent);
- final Vector rPrNe = pre.minus(nex);
- // Scaled fix points
- final Vector sPrev = cent.plusTimes(rPrev, scale);
- final Vector sNext = cent.plusTimes(rNext, scale);
- // Orthogonal vectors to the relative vectors
- // final Vector oPrev = new Vector(rPrev.get(1), -rPrev.get(0));
- // final Vector oNext = new Vector(-rNext.get(1), rNext.get(0));
-
- // Compute the intersection of rPrev+tp*oPrev and rNext+tn*oNext
- // rPrNe == rPrev - rNext
- final double zp = rPrNe.get(0) * oNext.get(1) - rPrNe.get(1) * oNext.get(0);
- final double zn = rPrNe.get(0) * oPrev.get(1) - rPrNe.get(1) * oPrev.get(0);
- final double n = oPrev.get(1) * oNext.get(0) - oPrev.get(0) * oNext.get(1);
- if(n == 0) {
- LoggingUtil.warning("Parallel?!?");
- path.drawTo(sNext.get(0), sNext.get(1));
- return;
- }
- final double tp = Math.abs(zp / n);
- final double tn = Math.abs(zn / n);
- // LoggingUtil.warning("tp: "+tp+" tn: "+tn);
-
- // Guide points
- final Vector gPrev = sPrev.plusTimes(oPrev, KAPPA * scale * tp);
- final Vector gNext = sNext.minusTimes(oNext, KAPPA * scale * tn);
-
- if(!path.isStarted()) {
- path.moveTo(sPrev);
- }
- // path.drawTo(sPrev);
- // path.drawTo(gPrev);
- // path.drawTo(gNext);
- // path.drawTo(sNext));
- // path.moveTo(sPrev);
- // if(tp < 0 || tn < 0) {
- // path.drawTo(sNext);
- // }
- // else {
- path.cubicTo(gPrev, gNext, sNext);
- // }
- }
-
- /**
- * Adds the required CSS-Classes
- *
- * @param svgp SVG-Plot
- */
- private void addCSSClasses(SVGPlot svgp) {
- if(!svgp.getCSSClassManager().contains(EMBORDER)) {
- ColorLibrary colors = context.getStyleLibrary().getColorSet(StyleLibrary.PLOT);
- String color;
- int clusterID = 0;
-
- for(@SuppressWarnings("unused")
- Cluster<?> cluster : clustering.getAllClusters()) {
- CSSClass cls = new CSSClass(this, EMBORDER + clusterID);
- cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, context.getStyleLibrary().getLineWidth(StyleLibrary.PLOT) / 2);
-
- if(clustering.getAllClusters().size() == 1) {
- color = "black";
- }
- else {
- color = colors.getColor(clusterID);
- }
- if(softBorder == 0) {
- cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
- }
- cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
- cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.15);
-
- svgp.addCSSClassOrLogError(cls);
- if(opacStyle == 0) {
- break;
- }
- clusterID++;
- }
- }
- }
-
- /**
- * Visualizer for generating SVG-Elements containing ellipses for first,
- * second and third standard deviation
- *
- * @author Robert Rödler
- *
- * @apiviz.stereotype factory
- * @apiviz.uses EMClusterVisualization oneway - - «create»
- *
- * @param <NV> Type of the NumberVector being visualized.
- */
- public static class Factory<NV extends NumberVector<NV, ?>> extends AbstractVisFactory {
/**
- * Constructor
+ * Adds the required CSS-Classes
+ *
+ * @param svgp SVG-Plot
*/
- public Factory() {
- super();
- }
-
- @Override
- public EMClusterVisualization<NV> makeVisualization(VisualizationTask task) {
- return new EMClusterVisualization<NV>(task);
- }
+ private void addCSSClasses(SVGPlot svgp) {
+ if(!svgp.getCSSClassManager().contains(EMBORDER)) {
+ final StyleLibrary style = context.getStyleResult().getStyleLibrary();
+ ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
+ String color;
+ int clusterID = 0;
+
+ for(@SuppressWarnings("unused")
+ Cluster<?> cluster : clustering.getAllClusters()) {
+ CSSClass cls = new CSSClass(this, EMBORDER + clusterID);
+ cls.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT) * .5);
+
+ if(clustering.getAllClusters().size() == 1) {
+ color = "black";
+ }
+ else {
+ color = colors.getColor(clusterID);
+ }
+ if(softBorder == 0) {
+ cls.setStatement(SVGConstants.CSS_STROKE_PROPERTY, color);
+ }
+ cls.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
+ cls.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.15);
- @Override
- public void processNewResult(HierarchicalResult baseResult, Result result) {
- // Find clusterings we can visualize:
- Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
- for(Clustering<?> c : clusterings) {
- if(c.getAllClusters().size() > 0) {
- // Does the cluster have a model with cluster means?
- Clustering<MeanModel<NV>> mcls = findMeanModel(c);
- if(mcls != null) {
- Collection<ScatterPlotProjector<?>> ps = ResultUtil.filterResults(baseResult, ScatterPlotProjector.class);
- for(ScatterPlotProjector<?> p : ps) {
- final VisualizationTask task = new VisualizationTask(NAME, c, p.getRelation(), this);
- task.put(VisualizationTask.META_LEVEL, VisualizationTask.LEVEL_DATA + 3);
- baseResult.getHierarchy().add(c, task);
- baseResult.getHierarchy().add(p, task);
- }
+ svgp.addCSSClassOrLogError(cls);
+ if(opacStyle == 0) {
+ break;
}
+ clusterID++;
}
}
}
-
- /**
- * Test if the given clustering has a mean model.
- *
- * @param <NV> Vector type
- * @param c Clustering to inspect
- * @return the clustering cast to return a mean model, null otherwise.
- */
- @SuppressWarnings("unchecked")
- private static <NV extends NumberVector<NV, ?>> Clustering<MeanModel<NV>> findMeanModel(Clustering<?> c) {
- final Model firstModel = c.getAllClusters().get(0).getModel();
- if(c.getAllClusters().get(0).getModel() instanceof MeanModel<?> && firstModel instanceof EMModel<?>) {
- return (Clustering<MeanModel<NV>>) c;
- }
- return null;
- }
}
-}
+} \ No newline at end of file