r = o.getHierarchy().iterParents(o); r.valid(); r.advance()) {
if(r.get() instanceof OutlierResult) {
vis = false;
break;
}
}
final VisualizationTask task = new VisualizationTask(NAME, context, o, rel, BubbleVisualization.this);
task.level = VisualizationTask.LEVEL_DATA;
task.addUpdateFlags(VisualizationTask.ON_DATA | VisualizationTask.ON_SAMPLE | VisualizationTask.ON_STYLEPOLICY);
task.initDefaultVisibility(vis);
context.addVis(o, task);
context.addVis(p, task);
}
});
}
/**
* Factory for producing bubble visualizations
*
* @author Erich Schubert
*
* @apiviz.has OutlierResult oneway - - visualizes
*/
public class Instance extends AbstractScatterplotVisualization implements DataStoreListener {
/**
* The outlier result to visualize
*/
protected OutlierResult result;
/**
* Constructor.
*
* @param task Visualization task
* @param plot Plot to draw to
* @param width Embedding width
* @param height Embedding height
* @param proj Projection
*/
public Instance(VisualizationTask task, VisualizationPlot plot, double width, double height, Projection proj) {
super(task, plot, width, height, proj);
this.result = task.getResult();
addListeners();
}
@Override
public void fullRedraw() {
setupCanvas();
StyleLibrary style = context.getStyleLibrary();
StylingPolicy stylepolicy = context.getStylingPolicy();
// bubble size
final double bubble_size = style.getSize(StyleLibrary.BUBBLEPLOT);
if(stylepolicy instanceof ClassStylingPolicy) {
ClassStylingPolicy colors = (ClassStylingPolicy) stylepolicy;
setupCSS(svgp, colors);
// draw data
for(DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) {
final double radius = getScaledForId(objId);
if(radius > 0.01 && !Double.isInfinite(radius)) {
final NumberVector vec = rel.get(objId);
if(vec != null) {
double[] v = proj.fastProjectDataToRenderSpace(vec);
if(v[0] != v[0] || v[1] != v[1]) {
continue; // NaN!
}
Element circle = svgp.svgCircle(v[0], v[1], radius * bubble_size);
SVGUtil.addCSSClass(circle, BUBBLE + colors.getStyleForDBID(objId));
layer.appendChild(circle);
}
}
}
}
else {
// draw data
for(DBIDIter objId = sample.getSample().iter(); objId.valid(); objId.advance()) {
final double radius = getScaledForId(objId);
if(radius > 0.01 && !Double.isInfinite(radius)) {
final NumberVector vec = rel.get(objId);
if(vec != null) {
double[] v = proj.fastProjectDataToRenderSpace(vec);
if(v[0] != v[0] || v[1] != v[1]) {
continue; // NaN!
}
Element circle = svgp.svgCircle(v[0], v[1], radius * bubble_size);
int color = stylepolicy.getColorForDBID(objId);
final StringBuilder cssstyle = new StringBuilder();
if(settings.fill) {
cssstyle.append(SVGConstants.CSS_FILL_PROPERTY).append(':').append(SVGUtil.colorToString(color));
cssstyle.append(SVGConstants.CSS_FILL_OPACITY_PROPERTY).append(":0.5");
}
else {
cssstyle.append(SVGConstants.CSS_STROKE_VALUE).append(':').append(SVGUtil.colorToString(color));
cssstyle.append(SVGConstants.CSS_FILL_PROPERTY).append(':').append(SVGConstants.CSS_NONE_VALUE);
}
SVGUtil.setAtt(circle, SVGConstants.SVG_STYLE_ATTRIBUTE, cssstyle.toString());
layer.appendChild(circle);
}
}
}
}
}
/**
* Registers the Bubble-CSS-Class at a SVGPlot.
*
* @param svgp the SVGPlot to register the Tooltip-CSS-Class.
* @param policy Clustering to use
*/
private void setupCSS(SVGPlot svgp, ClassStylingPolicy policy) {
final StyleLibrary style = context.getStyleLibrary();
ColorLibrary colors = style.getColorSet(StyleLibrary.PLOT);
// creating IDs manually because cluster often return a null-ID.
for(int clusterID = policy.getMinStyle(); clusterID < policy.getMaxStyle(); clusterID++) {
CSSClass bubble = new CSSClass(svgp, BUBBLE + clusterID);
bubble.setStatement(SVGConstants.CSS_STROKE_WIDTH_PROPERTY, style.getLineWidth(StyleLibrary.PLOT));
String color = colors.getColor(clusterID);
if(settings.fill) {
bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, color);
bubble.setStatement(SVGConstants.CSS_FILL_OPACITY_PROPERTY, 0.5);
}
else {
// for diamond-shaped strokes, see bugs.sun.com, bug ID 6294396
bubble.setStatement(SVGConstants.CSS_STROKE_VALUE, color);
bubble.setStatement(SVGConstants.CSS_FILL_PROPERTY, SVGConstants.CSS_NONE_VALUE);
}
svgp.addCSSClassOrLogError(bubble);
}
}
/**
* Convenience method to apply scalings in the right order.
*
* @param id object ID to get scaled score for
* @return a Double representing a outlierness-score, after it has modified
* by the given scales.
*/
protected double getScaledForId(DBIDRef id) {
double d = result.getScores().doubleValue(id);
if(Double.isNaN(d) || Double.isInfinite(d)) {
return 0.0;
}
if(settings.scaling == null) {
return result.getOutlierMeta().normalizeScore(d);
}
else {
return settings.scaling.getScaled(d);
}
}
}
/**
* Parameterization class.
*
* @author Erich Schubert
*
* @apiviz.exclude
*/
public static class Parameterizer extends AbstractParameterizer {
/**
* Flag for half-transparent filling of bubbles.
*
*
* Key: {@code -bubble.fill}
*
*/
public static final OptionID FILL_ID = new OptionID("bubble.fill", "Half-transparent filling of bubbles.");
/**
* Parameter for scaling functions
*
*
* Key: {@code -bubble.scaling}
*
*/
public static final OptionID SCALING_ID = new OptionID("bubble.scaling", "Additional scaling function for bubbles.");
/**
* Fill parameter.
*/
protected boolean fill;
/**
* Scaling function to use for Bubbles
*/
protected ScalingFunction scaling;
@Override
protected void makeOptions(Parameterization config) {
super.makeOptions(config);
Flag fillF = new Flag(FILL_ID);
if(config.grab(fillF)) {
fill = fillF.isTrue();
}
ObjectParameter scalingP = new ObjectParameter<>(SCALING_ID, OutlierScalingFunction.class, true);
if(config.grab(scalingP)) {
scaling = scalingP.instantiateClass(config);
}
}
@Override
protected BubbleVisualization makeInstance() {
return new BubbleVisualization(this);
}
}
}