Package org.thechiselgroup.choosel.visualization_component.chart.client.scatterplot

Source Code of org.thechiselgroup.choosel.visualization_component.chart.client.scatterplot.ScatterPlot$ShapeLegendProperty

/*******************************************************************************
* Copyright 2009, 2010 Lars Grammel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*    
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. 
*******************************************************************************/
package org.thechiselgroup.choosel.visualization_component.chart.client.scatterplot;

import java.util.Map;

import org.thechiselgroup.choosel.core.client.ui.Colors;
import org.thechiselgroup.choosel.core.client.ui.TextBoundsEstimator;
import org.thechiselgroup.choosel.core.client.util.DataType;
import org.thechiselgroup.choosel.core.client.util.collections.Delta;
import org.thechiselgroup.choosel.core.client.util.collections.LightweightCollection;
import org.thechiselgroup.choosel.core.client.visualization.model.Slot;
import org.thechiselgroup.choosel.core.client.visualization.model.ViewContentDisplayProperty;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItem;
import org.thechiselgroup.choosel.protovis.client.PV;
import org.thechiselgroup.choosel.protovis.client.PVAlignment;
import org.thechiselgroup.choosel.protovis.client.PVDot;
import org.thechiselgroup.choosel.protovis.client.PVEventHandler;
import org.thechiselgroup.choosel.protovis.client.PVLabel;
import org.thechiselgroup.choosel.protovis.client.PVLinearScale;
import org.thechiselgroup.choosel.protovis.client.PVMark;
import org.thechiselgroup.choosel.protovis.client.PVPanel;
import org.thechiselgroup.choosel.protovis.client.PVShape;
import org.thechiselgroup.choosel.protovis.client.jsutil.JsArgs;
import org.thechiselgroup.choosel.protovis.client.jsutil.JsDoubleFunction;
import org.thechiselgroup.choosel.protovis.client.jsutil.JsStringFunction;
import org.thechiselgroup.choosel.visualization_component.chart.client.ChartViewContentDisplay;
import org.thechiselgroup.choosel.visualization_component.chart.client.functions.TickFormatFunction;
import org.thechiselgroup.choosel.visualization_component.chart.client.functions.VisualItemColorSlotAccessor;
import org.thechiselgroup.choosel.visualization_component.chart.client.functions.VisualItemDoubleSlotAccessor;
import org.thechiselgroup.choosel.visualization_component.chart.client.functions.VisualItemStringSlotAccessor;

// TODO refactoring: use separate panel for dots that are added to scatter plot
public class ScatterPlot extends ChartViewContentDisplay {

    private class ShapeLegendProperty implements
            ViewContentDisplayProperty<Map<String, String>> {

        @Override
        public String getPropertyName() {
            return SHAPE_LEGEND_PROPERTY;
        }

        @Override
        public Map<String, String> getValue() {
            return getShapeLegend();
        }

        @Override
        public void setValue(Map<String, String> value) {
            setShapeLegend(value);
        }
    }

    private static final double BORDER_WIDTH = 1d;

    private static final int MIN_DOT_SIZE = 10;

    private static final int MAX_DOT_SIZE = 60;

    public final static String ID = "org.thechiselgroup.choosel.visualization_component.chart.ScatterPlot";

    public static final Slot Y_POSITION = new Slot("yPosition", "Y-Axis",
            DataType.NUMBER);

    public static final Slot X_POSITION = new Slot("xPosition", "X-Axis",
            DataType.NUMBER);

    public static final Slot COLOR = new Slot("color", "Color", DataType.COLOR);

    public static final Slot BORDER_COLOR = new Slot("borderColor",
            "Border Color", DataType.COLOR);

    public static final Slot SIZE = new Slot("size", "Size", DataType.NUMBER);

    /**
     * The shape slot should return a shape value (Strings, see {@link PVShape})
     * per {@link VisualItem}.
     */
    public static final Slot SHAPE = new Slot("shape", "Shape", DataType.SHAPE);

    public static final Slot[] SLOTS = new Slot[] { X_POSITION, Y_POSITION,
            SHAPE, COLOR, BORDER_COLOR, SIZE };

    /**
     * Shape legends are {@link Map}s of shape values (Strings, see
     * {@link PVShape}) to explaining texts. If the shape legend property is set
     * to <code>null</code>, no legend is displayed.
     */
    public static final String SHAPE_LEGEND_PROPERTY = "shapeLegend";

    private static final int SHAPE_LEGEND_PANEL_PADDING = 2;

    private static final int SHAPE_LEGEND_LABEL_SPACING = 20;

    /**
     * Offset that moves the y-axis label closer to the y-axis. This is required
     * because the label is rotate by 90 degrees and the text would only be half
     * visible without the offset.
     */
    private static final int Y_AXIS_LABEL_OFFSET = 10;

    // TODO move
    private static final double ANGLE_90_DEGREES = -Math.PI / 2;

    private static final int OUTER_BORDER = 5;

    private static final int AXIS_LEGEND_SPACE = 30;

    /**
     * Height of the shape legend in pixels. This is only relevant if the shape
     * legend is actually displayed. Use {@link #getShapeLegendVerticalSpace()}
     * instead.
     */
    private static final int SHAPE_LEGEND_HEIGHT = 35;

    /**
     * Distance between X axis label and shape legend.
     */
    private static final int SHAPE_LEGEND_OFFSET = 5;

    private static final int SHAPE_SIZE = 15;

    /**
     * Color of the grid lines.
     */
    private static final String GRIDLINE_COLOR = Colors.GRAY_1;

    /**
     * Color of the axis lines.
     */
    private static final String AXIS_COLOR = Colors.GRAY_2;

    private static final String SHAPE_LEGEND_BACKGROUND_COLOR = "#EEE";

    private static final String FONT_WEIGHT = "normal";

    private static final String FONT_SIZE = "10px";

    private static final String FONT_STYLE = "normal";

    private static final String FONT_FAMILY = "sans-serif";

    private static final String FONT = FONT_SIZE + " " + FONT_FAMILY;

    private Map<String, String> shapeLegend;

    protected int chartHeight;

    protected int chartWidth;

    private PVLinearScale scaleX;

    private PVLinearScale scaleY;

    /**
     * Configuration of the dots that get painted in the scatter plot.
     */
    private PVDot dots;

    private String xAxisLabel = "";

    private String yAxisLabel = "";

    private String shapeLegendLabel = "";

    public ScatterPlot() {
        registerProperty(new ShapeLegendProperty());
    }

    @Override
    public void buildChart() {
        assert visualItemsJsArray.length() >= 1;

        initChart();
        initScales();

        drawXAxisAndVerticalGridlines();
        drawYAxisAndHorizontalGridlines();
        drawShapeLegend();
        drawDots();
    }

    private void drawDots() {
        /*
         * TODO use scale for size; problem: PV.Scale.Linear did not work with
         * single value.
         */
        dots = getChart().add(PV.Dot).data(visualItemsJsArray)
                .shape(new VisualItemStringSlotAccessor(SHAPE))
                .bottom(scaleY.fd(new VisualItemDoubleSlotAccessor(Y_POSITION)))
                .left(scaleX.fd(new VisualItemDoubleSlotAccessor(X_POSITION)))
                .size(new VisualItemDoubleSlotAccessor(SIZE))
                .fillStyle(new VisualItemColorSlotAccessor(COLOR))
                .strokeStyle(new VisualItemColorSlotAccessor(BORDER_COLOR))
                .lineWidth(BORDER_WIDTH);
    }

    private void drawShapeLegend() {
        if (shapeLegend == null) {
            return;
        }

        // calculate widths
        TextBoundsEstimator estimator = new TextBoundsEstimator(FONT_FAMILY,
                FONT_STYLE, FONT_WEIGHT, FONT_SIZE);

        final Map<String, Integer> textWidths = estimator.getWidths(shapeLegend
                .values());

        int descriptionsWidth = 0;
        for (Integer integer : textWidths.values()) {
            descriptionsWidth += integer;
        }
        descriptionsWidth += textWidths.size()
                * (SHAPE_SIZE + SHAPE_LEGEND_LABEL_SPACING);
        int legendLabelWidth = estimator.getWidth(shapeLegendLabel);
        int shapePanelWidth = legendLabelWidth > descriptionsWidth ? legendLabelWidth
                : descriptionsWidth;

        // panel for legend
        PVPanel shapePanel = getChart()
                .add(PV.Panel)
                .bottom(-AXIS_LEGEND_SPACE - SHAPE_LEGEND_HEIGHT
                        - SHAPE_LEGEND_OFFSET).left(0)
                .fillStyle(SHAPE_LEGEND_BACKGROUND_COLOR)
                .height(SHAPE_LEGEND_HEIGHT).width(shapePanelWidth);

        // shape legend title
        shapePanel.add(PV.Label).bottom(SHAPE_LEGEND_PANEL_PADDING)
                .left(shapePanelWidth / 2).font(FONT).text(shapeLegendLabel)
                .textAlign(PVAlignment.CENTER);

        shapePanel
                .add(PV.Dot)
                .data(shapeLegend.entrySet())
                .top(2 + (SHAPE_SIZE / 2))
                .shape(new JsStringFunction() {
                    @Override
                    public String f(JsArgs args) {
                        Map.Entry<String, String> entry = args.getObject();
                        return entry.getKey();
                    }
                })
                .size(SHAPE_SIZE)
                .left(new JsDoubleFunction() {

                    private int currentWidth = 0;

                    @Override
                    public double f(JsArgs args) {
                        PVMark _this = args.getThis();
                        Map.Entry<String, String> entry = args.getObject();

                        if (_this.index() == 0) {
                            currentWidth = 0;
                        }

                        int left = currentWidth + SHAPE_SIZE;

                        currentWidth += textWidths.get(entry.getValue())
                                + SHAPE_SIZE + SHAPE_LEGEND_LABEL_SPACING;

                        return left;
                    }
                }).anchor(PVAlignment.RIGHT).add(PV.Label).font(FONT)
                .text(new JsStringFunction() {
                    @Override
                    public String f(JsArgs args) {
                        Map.Entry<String, String> entry = args.getObject();
                        return entry.getValue();
                    }
                });
    }

    // TODO convert grid line color into property
    // TODO convert axis color into property
    private void drawXAxisAndVerticalGridlines() {
        // x axis label
        getChart().add(PV.Label).bottom(-AXIS_LEGEND_SPACE)
                .left(chartWidth / 2).text(xAxisLabel)
                .textAlign(PVAlignment.CENTER).textBaseline(PVLabel.BOTTOM);

        // x axis grid labels
        getChart().add(PV.Rule).data(scaleX.ticks()).bottom(0).left(scaleX)
                .strokeStyle(GRIDLINE_COLOR).height(chartHeight)
                .anchor(PVAlignment.BOTTOM).add(PV.Label)
                .text(new TickFormatFunction(scaleX));

        // vertical grid lines
        getChart().add(PV.Rule).height(chartHeight).bottom(0).left(0)
                .strokeStyle(AXIS_COLOR);

    }

    private void drawYAxisAndHorizontalGridlines() {
        // y axis label
        getChart().add(PV.Label).bottom(chartHeight / 2)
                .left(-AXIS_LEGEND_SPACE)
                // + Y_AXIS_LABEL_OFFSET
                .text(yAxisLabel).textAngle(ANGLE_90_DEGREES)
                .textAlign(PVAlignment.CENTER).textBaseline(PVLabel.TOP);

        // y axis grid labels
        getChart().add(PV.Rule).data(scaleY.ticks()).bottom(scaleY).left(0)
                .strokeStyle(GRIDLINE_COLOR).width(chartWidth).add(PV.Label)
                .text(new TickFormatFunction(scaleY))
                .textAngle(ANGLE_90_DEGREES).textAlign(PVAlignment.CENTER)
                .textBaseline(PVAlignment.BOTTOM);

        // horizontal grid lines
        getChart().add(PV.Rule).width(chartWidth).bottom(0).left(0)
                .strokeStyle(AXIS_COLOR);
    }

    @Override
    public String getName() {
        return "Scatter Plot";
    }

    public Map<String, String> getShapeLegend() {
        return shapeLegend;
    }

    /**
     * @return vertical space consumed by the shape legend, or 0, if it is not
     *         displayed.
     */
    private int getShapeLegendVerticalSpace() {
        return shapeLegend == null ? 0 : SHAPE_LEGEND_HEIGHT
                + SHAPE_LEGEND_OFFSET;
    }

    @Override
    public Slot[] getSlots() {
        return SLOTS;
    }

    private void initChart() {
        chartWidth = width - AXIS_LEGEND_SPACE - 2 * OUTER_BORDER;
        chartHeight = height - AXIS_LEGEND_SPACE - 2 * OUTER_BORDER
                - getShapeLegendVerticalSpace();

        getChart().left(AXIS_LEGEND_SPACE + OUTER_BORDER).bottom(
                AXIS_LEGEND_SPACE + OUTER_BORDER
                        + getShapeLegendVerticalSpace());
    }

    private void initScales() {
        scaleX = PV.Scale.linear(visualItemsJsArray,
                new VisualItemDoubleSlotAccessor(X_POSITION))
                .range(0, chartWidth);
        scaleY = PV.Scale.linear(visualItemsJsArray,
                new VisualItemDoubleSlotAccessor(Y_POSITION)).range(0,
                chartHeight);
    }

    @Override
    protected void registerEventHandler(String eventType, PVEventHandler handler) {
        dots.event(eventType, handler);
    }

    public void setShapeLegend(Map<String, String> shapeLegend) {
        this.shapeLegend = shapeLegend;
        updateChart(true);
    }

    @Override
    public void update(Delta<VisualItem> delta,
            LightweightCollection<Slot> changedSlots) {

        // TODO re-enable
        // if (!changedSlots.isEmpty()) {
        // TODO expose protovis label and change immediately, if possible
        this.yAxisLabel = callback.getSlotResolverDescription(Y_POSITION);
        this.xAxisLabel = callback.getSlotResolverDescription(X_POSITION);
        this.shapeLegendLabel = callback.getSlotResolverDescription(SHAPE);
        // }

        super.update(delta, changedSlots);
    }

}
TOP

Related Classes of org.thechiselgroup.choosel.visualization_component.chart.client.scatterplot.ScatterPlot$ShapeLegendProperty

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.