All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.hansolo.fx.charts.WaffleChart Maven / Gradle / Ivy

/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright 2016-2024 Gerrit Grunwald.
 *
 * 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
 *
 *     https://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 eu.hansolo.fx.charts;

import eu.hansolo.fx.charts.tools.Helper;
import javafx.beans.DefaultProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;

import java.awt.image.BufferedImage;


@DefaultProperty("children")
public class WaffleChart extends Region {
    public  static final Color                 DEFAULT_BACKGROUND_FILL = Color.web("#f0f0f0");
    public  static final Color                 DEFAULT_EMPTY_CELL_FILL = Color.web("#cdcdcd");
    public  static final Color                 DEFAULT_CELL_FILL       = Color.web("#3ca9e2");
    private static final double                PREFERRED_WIDTH         = 250;
    private static final double                PREFERRED_HEIGHT        = 250;
    private static final double                MINIMUM_WIDTH           = 50;
    private static final double                MINIMUM_HEIGHT          = 50;
    private static final double                MAXIMUM_WIDTH           = 4096;
    private static final double                MAXIMUM_HEIGHT          = 4096;
    private              double                size;
    private              double                width;
    private              double                height;
    private              Canvas                canvas;
    private              GraphicsContext       ctx;
    private              double                _value;
    private              DoubleProperty        value;
    private              Paint                 _backgroundFill;
    private              ObjectProperty backgroundFill;
    private              Paint                 _cellFill;
    private              ObjectProperty cellFill;
    private              Paint                 _emptyCellFill;
    private              ObjectProperty emptyCellFill;
    private              double                inset;
    private              double                chartSize;
    private              double                gap;
    private              double                cellSize;
    private              double                cellRadius;


    // ******************** Constructors **************************************
    public WaffleChart() {
        this(0);
    }
    public WaffleChart(final double value) {
        if (value < 0 || value > 1) { throw new IllegalArgumentException("Value must be between 0...1"); }
        this._value          = value;
        this._backgroundFill = DEFAULT_BACKGROUND_FILL;
        this._emptyCellFill  = DEFAULT_EMPTY_CELL_FILL;
        this._cellFill       = DEFAULT_CELL_FILL;
        this.inset           = 5;
        this.chartSize       = PREFERRED_WIDTH - 2 * inset;
        this.gap             = 1;
        this.cellSize        = (chartSize - (9 * gap)) / 10;
        this.cellRadius      = cellSize * 0.25;
        initGraphics();
        registerListeners();
    }


    // ******************** Initialization ************************************
    private void initGraphics() {
        if (Double.compare(getPrefWidth(), 0.0) <= 0 || Double.compare(getPrefHeight(), 0.0) <= 0 || Double.compare(getWidth(), 0.0) <= 0 ||
            Double.compare(getHeight(), 0.0) <= 0) {
            if (getPrefWidth() > 0 && getPrefHeight() > 0) {
                setPrefSize(getPrefWidth(), getPrefHeight());
            } else {
                setPrefSize(PREFERRED_WIDTH, PREFERRED_HEIGHT);
            }
        }

        getStyleClass().add("comparison-ring-chart");

        canvas = new Canvas(PREFERRED_WIDTH, PREFERRED_HEIGHT);
        ctx    = canvas.getGraphicsContext2D();

        getChildren().setAll(canvas);
    }

    private void registerListeners() {
        widthProperty().addListener(o -> resize());
        heightProperty().addListener(o -> resize());
    }


    // ******************** Methods *******************************************
    @Override public void layoutChildren() {
        super.layoutChildren();
    }

    @Override protected double computeMinWidth(final double height) { return MINIMUM_WIDTH; }
    @Override protected double computeMinHeight(final double width) { return MINIMUM_HEIGHT; }
    @Override protected double computePrefWidth(final double height) { return super.computePrefWidth(height); }
    @Override protected double computePrefHeight(final double width) { return super.computePrefHeight(width); }
    @Override protected double computeMaxWidth(final double height) { return MAXIMUM_WIDTH; }
    @Override protected double computeMaxHeight(final double width) { return MAXIMUM_HEIGHT; }

    @Override public ObservableList getChildren() { return super.getChildren(); }

    public double getValue() { return null == this.value ? this._value : this.value.get(); }
    public void setValue(final double value) {
        if (value < 0 || value > 1) { throw new IllegalArgumentException("Value must be between 0...1"); }
        if (null == this.value) {
            this._value = value;
            redraw();
        } else {
            this.value.set(value);
        }
    }
    public DoubleProperty valueProperty() {
        if (null == this.value) {
            this.value = new DoublePropertyBase(this._value) {
                @Override protected void invalidated() {
                    if (get() < 0 || get() > 1) { throw new IllegalArgumentException("Value must be between 0...1"); }
                    redraw();
                }
                @Override public Object getBean() { return WaffleChart.this; }
                @Override public String getName() { return "value"; }
            };
        }
        return this.value;
    }

    public Paint getBackgroundFill() { return null == backgroundFill ? _backgroundFill : backgroundFill.get(); }
    public void setBackgroundFill(final Paint backgroundFill) {
        if (null == this.backgroundFill) {
            _backgroundFill = backgroundFill;
            redraw();
        } else {
            this.backgroundFill.set(backgroundFill);
        }
    }
    public ObjectProperty backgroundFillProperty() {
        if (null == backgroundFill) {
            backgroundFill = new ObjectPropertyBase<>(_backgroundFill) {
                @Override protected void invalidated() { redraw(); }
                @Override public Object getBean() { return WaffleChart.this; }
                @Override public String getName() { return "backgroundFill"; }
            };
            _backgroundFill = null;
        }
        return backgroundFill;
    }

    public Paint getCellFill() { return null == cellFill ? _cellFill : cellFill.get(); }
    public void setCellFill(final Paint cellFill) {
        if (null == this.cellFill) {
            _cellFill = cellFill;
            redraw();
        } else {
            this.cellFill.set(cellFill);
        }
    }
    public ObjectProperty cellFillProperty() {
        if (null == cellFill) {
            cellFill = new ObjectPropertyBase<>(_cellFill) {
                @Override protected void invalidated() { redraw(); }
                @Override public Object getBean() { return WaffleChart.this; }
                @Override public String getName() { return "cellFill"; }
            };
            _cellFill = null;
        }
        return cellFill;
    }

    public Paint getEmptyCellFill() { return null == emptyCellFill ? _emptyCellFill : emptyCellFill.get(); }
    public void setEmptyCellFill(final Paint emptyCellFill) {
        if (null == this.emptyCellFill) {
            _emptyCellFill = emptyCellFill;
            redraw();
        } else {
            this.emptyCellFill.set(emptyCellFill);
        }
    }
    public ObjectProperty emptyCellFillProperty() {
        if (null == emptyCellFill) {
            emptyCellFill = new ObjectPropertyBase<>(_emptyCellFill) {
                @Override protected void invalidated() { redraw(); }
                @Override public Object getBean() { return WaffleChart.this; }
                @Override public String getName() { return "emptyCellFill"; }
            };
            _emptyCellFill = null;
        }
        return emptyCellFill;
    }

    /**
     * Calling this method will render this chart/plot to a png given of the given width and height
     * @param filename The path and name of the file  /Users/hansolo/Desktop/plot.png
     * @param width The width of the final image in pixels (if < 0 then 400 and if > 4096 then 4096)
     * @param height The height of the final image in pixels (if < 0 then 400 and if > 4096 then 4096)
     * @return True if the procedure was successful, otherwise false
     */
    public boolean renderToImage(final String filename, final int width, final int height) {
        return Helper.renderToImage(WaffleChart.this, width, height, filename);
    }

    /**
     * Calling this method will render this chart/plot to a png given of the given width and height
     * @param width The width of the final image in pixels (if < 0 then 400 and if > 4096 then 4096)
     * @param height The height of the final image in pixels (if < 0 then 400 and if > 4096 then 4096)
     * @return A BufferedImage of this chart in the given dimension
     */
    public BufferedImage renderToImage(final int width, final int height) {
        return Helper.renderToImage(WaffleChart.this, width, height);
    }


    // ******************** Drawing *******************************************
    private void drawChart() {
        ctx.clearRect(0, 0, width, height);
        final Paint  emptyCellFill = getEmptyCellFill();
        final Paint  cellFill      = getCellFill();
        final double value         = getValue();
        final long   boxToChange   = Math.round(100 * value);
        ctx.setFill(getBackgroundFill());
        ctx.fillRect(0, 0, width, height);
        double x        = inset;
        double y        = inset + (chartSize - cellSize);
        double xCounter = 0;
        for (int i = 0; i < 100; i++) {
            ctx.setFill(value > 0 && i <= boxToChange ? cellFill : emptyCellFill);
            ctx.fillRoundRect(x, y, cellSize, cellSize, cellRadius, cellRadius);
            x += (gap + cellSize);
            xCounter++;
            if (xCounter == 10) {
                xCounter =  0;
                x        =  inset;
                y        -= (gap + cellSize);
            }
        }
    }


    // ******************** Resizing ******************************************
    private void resize() {
        width  = getWidth() - getInsets().getLeft() - getInsets().getRight();
        height = getHeight() - getInsets().getTop() - getInsets().getBottom();
        size   = Math.min(width, height);

        if (width > 0 && height > 0) {
            this.chartSize  = this.size - 2 * inset;
            this.gap        = 1;
            this.cellSize   = (chartSize - (9 * gap)) / 10;
            this.cellRadius = cellSize * 0.25;

            this.canvas.setWidth(size);
            this.canvas.setHeight(size);
            this.canvas.relocate((getWidth() - size) * 0.5, (getHeight() - size) * 0.5);

            redraw();
        }
    }

    private void redraw() {
        drawChart();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy