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

Source Code of org.thechiselgroup.choosel.visualization_component.chart.client.piechart.PieChart

/*******************************************************************************
* Copyright (C) 2011 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.piechart;

import org.thechiselgroup.choosel.core.client.ui.Colors;
import org.thechiselgroup.choosel.core.client.util.DataType;
import org.thechiselgroup.choosel.core.client.visualization.model.Slot;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItem;
import org.thechiselgroup.choosel.core.client.visualization.model.predicates.GreaterThanSlotValuePredicate;
import org.thechiselgroup.choosel.protovis.client.PV;
import org.thechiselgroup.choosel.protovis.client.PVAlignment;
import org.thechiselgroup.choosel.protovis.client.PVEventHandler;
import org.thechiselgroup.choosel.protovis.client.PVWedge;
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.barchart.BarChart;
import org.thechiselgroup.choosel.visualization_component.chart.client.functions.VisualItemColorSlotAccessor;
import org.thechiselgroup.choosel.visualization_component.chart.client.functions.VisualItemPredicateJsBooleanFunction;
import org.thechiselgroup.choosel.visualization_component.chart.client.functions.VisualItemStringSlotAccessor;

public class PieChart extends ChartViewContentDisplay {

    private static final String WEDGE_LABEL_COLOR = "#000000";

    private static final String RGBA_TRANSPARENT = "rgba(0,0,0,0)";

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

    public static final Slot VALUE = new Slot("value", "Value", DataType.NUMBER);

    public static final Slot LABEL = new Slot("label", "Label", DataType.TEXT);

    public static final Slot PARTIAL_VALUE = new Slot("partialValue",
            "Partial Value", 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 PARTIAL_COLOR = new Slot("partialColor",
            "Partial Color", DataType.COLOR);

    public static final Slot PARTIAL_BORDER_COLOR = new Slot(
            "partialBorderColor", "Partial Border Color", DataType.COLOR);

    public static final Slot[] SLOTS = new Slot[] { LABEL, VALUE,
            PARTIAL_VALUE, COLOR, BORDER_COLOR, PARTIAL_COLOR,
            PARTIAL_BORDER_COLOR };

    private final static int WEDGE_TEXT_ANGLE = 0;

    private JsDoubleFunction partialWedgeRadius = new JsDoubleFunction() {
        @Override
        public double f(JsArgs args) {
            VisualItem visualItem = args.getObject();

            double partialValue = visualItem.getValueAsDouble(PARTIAL_VALUE);
            double value = visualItem.getValueAsDouble(VALUE);

            // cannot divide by zero
            if (value == 0) {
                return 0;
            }

            assert 0 <= partialValue && partialValue <= value : "invalid partial value. Expected range: [0,"
                    + value + "]; got: " + partialValue;

            double partialPercentage = partialValue / value;

            assert 0 <= partialPercentage && partialPercentage <= 1 : "0 <= partialPercentage <= 1 (was: "
                    + partialPercentage + ")";

            /*
             * This was found to be a visually more accurate solution for both
             * small and large percentages compared to 'partialPercentage *
             * outerRadius' and to 'Math.sqrt(partialPercentage) * outerRadius'
             */
            return ((Math.sqrt(partialPercentage) + partialPercentage) / 2)
                    * outerRadius;
        }
    };

    /**
     * For each {@link VisualItem} index, it return the sum of the current and
     * all previous view items. This is required to calculate the start angle,
     * which we need for the partial wedges (because Protovis only automatically
     * calculates the correct start index if the sibling wedges are visible,
     * which is not the case for the partial wedges).
     */
    private double[] aggregatedValues;

    private JsDoubleFunction outerRadiusFunction = new JsDoubleFunction() {
        @Override
        public double f(JsArgs args) {
            return outerRadius;
        }
    };

    private int outerRadius;

    private PVWedge mainWedge;

    private PVWedge partialWedge;

    private JsDoubleFunction wedgeLeft = new JsDoubleFunction() {
        @Override
        public double f(JsArgs args) {
            return width / 2;
        }
    };

    private JsDoubleFunction wedgeBottom = new JsDoubleFunction() {
        @Override
        public double f(JsArgs args) {
            return height / 2;
        }
    };

    private JsDoubleFunction wedgeAngle = new JsDoubleFunction() {
        @Override
        public double f(JsArgs args) {
            VisualItem visualItem = args.getObject();
            return visualItem.getValueAsDouble(VALUE) * 2 * Math.PI
                    / getValueSum();
        }

    };

    private JsStringFunction regularMarkLabelText = new JsStringFunction() {
        @Override
        public String f(JsArgs args) {
            VisualItem visualItem = args.getObject();
            return Double.toString(visualItem.getValueAsDouble(VALUE)
                    - visualItem.getValueAsDouble(PARTIAL_VALUE));
        }
    };

    private JsStringFunction highlightedMarkLabelText = new JsStringFunction() {
        @Override
        public String f(JsArgs args) {
            VisualItem visualItem = args.getObject();
            return Double.toString(visualItem.getValueAsDouble(PARTIAL_VALUE));
        }
    };

    /**
     * The wedge position calculation using angle() requires the sibling wedges
     * to be visible, which is not the case for partial wedges. We thus need to
     * use startAngle to specify the position.
     */
    private JsDoubleFunction startAngle = new JsDoubleFunction() {
        @Override
        public double f(JsArgs args) {
            int index = args.<PVWedge> getThis().index();
            if (index == 0) {
                /*
                 * -Math.PI / 2 rotates the wedges so the 1st one starts at 12
                 * o'clock
                 */
                return -Math.PI / 2;
            }
            return aggregatedValues[index - 1] * 2 * Math.PI / getValueSum()
                    - Math.PI / 2;
        }
    };

    private PVWedge labelWedge;

    private PVWedge invisibleInteractionWedge;

    @Override
    protected void beforeRender() {
        super.beforeRender();

        calculateAggregatedValues();
        calculateRegularWedgeOuterRadius();
    }

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

        mainWedge = getChart().add(PV.Wedge).data(visualItemsJsArray)
                .left(wedgeLeft).bottom(wedgeBottom).startAngle(startAngle)
                .innerRadius(partialWedgeRadius)
                .outerRadius(outerRadiusFunction).angle(wedgeAngle)
                .fillStyle(new VisualItemColorSlotAccessor(COLOR))
                .strokeStyle(new VisualItemColorSlotAccessor(BORDER_COLOR));

        mainWedge.anchor(PVAlignment.CENTER).add(PV.Label)
                .textAngle(WEDGE_TEXT_ANGLE).text(regularMarkLabelText)
                .textStyle(Colors.WHITE);

        partialWedge = mainWedge
                .add(PV.Wedge)
                .visible(
                        new VisualItemPredicateJsBooleanFunction(
                                new GreaterThanSlotValuePredicate(
                                        PARTIAL_VALUE, 0)))
                .innerRadius(0)
                .outerRadius(partialWedgeRadius)
                .fillStyle(new VisualItemColorSlotAccessor(PARTIAL_COLOR))
                .strokeStyle(
                        new VisualItemColorSlotAccessor(PARTIAL_BORDER_COLOR));

        partialWedge.anchor(PVAlignment.CENTER).add(PV.Label)
                .textAngle(WEDGE_TEXT_ANGLE).text(highlightedMarkLabelText);

        labelWedge = getChart().add(PV.Wedge).data(visualItemsJsArray)
                .left(wedgeLeft).bottom(wedgeBottom).startAngle(startAngle)
                .innerRadius(0).outerRadius(outerRadiusFunction)
                .angle(wedgeAngle).fillStyle(RGBA_TRANSPARENT)
                .strokeStyle(RGBA_TRANSPARENT);

        labelWedge.anchor("start").add(PV.Label)
                .text(new VisualItemStringSlotAccessor(LABEL))
                .textStyle(WEDGE_LABEL_COLOR);

        /*
         * XXX we use alpha 0.0001 because Protovis removes the invisible
         * interaction wedge if alpha is 0. This should be changed when using
         * something other than Protovis for the rendering. (o")9
         */
        invisibleInteractionWedge = getChart().add(PV.Wedge)
                .data(visualItemsJsArray).left(wedgeLeft).bottom(wedgeBottom)
                .startAngle(startAngle).innerRadius(0)
                .outerRadius(outerRadiusFunction).angle(wedgeAngle)
                .cursor(BarChart.POINTER).events(BarChart.ALL)
                .fillStyle(PV.rgb(255, 255, 255).alpha(0.00001))
                .strokeStyle(PV.rgb(255, 255, 255).alpha(0.00001));

    }

    private void calculateAggregatedValues() {
        aggregatedValues = new double[visualItemsJsArray.length()];
        double sum = 0;
        for (int i = 0; i < visualItemsJsArray.length(); i++) {
            sum += visualItemsJsArray.get(i).getValueAsDouble(VALUE);
            aggregatedValues[i] = sum;
        }
    }

    private void calculateRegularWedgeOuterRadius() {
        outerRadius = Math.min(height, width) / 2 - 5;
    }

    @Override
    public String getName() {
        return "Pie Chart";
    }

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

    /**
     * @return sum of all values
     */
    private double getValueSum() {
        return aggregatedValues[aggregatedValues.length - 1];
    }

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

}
TOP

Related Classes of org.thechiselgroup.choosel.visualization_component.chart.client.piechart.PieChart

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.