Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/* ===========================================================
* JFreeChart : a free chart library for the Java(tm) platform
* ===========================================================
*
* (C) Copyright 2000-present, by David Gilbert and Contributors.
*
* Project Info: http://www.jfree.org/jfreechart/index.html
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.]
*
* -------------
* FlowPlot.java
* -------------
* (C) Copyright 2021-present, by David Gilbert and Contributors.
*
* Original Author: David Gilbert;
* Contributor(s): -;
*
*/
package org.jfree.chart.plot.flow;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.entity.FlowEntity;
import org.jfree.chart.entity.NodeEntity;
import org.jfree.chart.labels.FlowLabelGenerator;
import org.jfree.chart.labels.StandardFlowLabelGenerator;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.PlotState;
import org.jfree.chart.text.TextUtils;
import org.jfree.chart.ui.RectangleInsets;
import org.jfree.chart.ui.TextAnchor;
import org.jfree.chart.ui.VerticalAlignment;
import org.jfree.chart.util.Args;
import org.jfree.chart.util.PaintUtils;
import org.jfree.chart.util.PublicCloneable;
import org.jfree.data.flow.FlowDataset;
import org.jfree.data.flow.FlowDatasetUtils;
import org.jfree.data.flow.FlowKey;
import org.jfree.data.flow.NodeKey;
/**
* A plot for visualising flows defined in a {@link FlowDataset}. This enables
* the production of a type of Sankey chart. The example shown here is
* produced by the {@code FlowPlotDemo1.java} program included in the JFreeChart
* Demo Collection:
*
*
* @since 1.5.3
*/
public class FlowPlot extends Plot implements Cloneable, PublicCloneable,
Serializable {
/** The source of data. */
private FlowDataset dataset;
/**
* The node width in Java 2D user-space units.
*/
private double nodeWidth = 20.0;
/** The gap between nodes (expressed as a percentage of the plot height). */
private double nodeMargin = 0.01;
/**
* The percentage of the plot width to assign to a gap between the nodes
* and the flow representation.
*/
private double flowMargin = 0.005;
/**
* Stores colors for specific nodes - if there isn't a color in here for
* the node, the default node color will be used (unless the color swatch
* is active).
*/
private Map nodeColorMap;
private List nodeColorSwatch;
/** A pointer into the color swatch. */
private int nodeColorSwatchPointer = 0;
/** The default node color if nothing is defined in the nodeColorMap. */
private Color defaultNodeColor;
private Font defaultNodeLabelFont;
private Paint defaultNodeLabelPaint;
private VerticalAlignment nodeLabelAlignment;
/** The x-offset for node labels. */
private double nodeLabelOffsetX;
/** The y-offset for node labels. */
private double nodeLabelOffsetY;
/** The tool tip generator - if null, no tool tips will be displayed. */
private FlowLabelGenerator toolTipGenerator;
/**
* Creates a new instance that will source data from the specified dataset.
*
* @param dataset the dataset.
*/
public FlowPlot(FlowDataset dataset) {
this.dataset = dataset;
if (dataset != null) {
dataset.addChangeListener(this);
}
this.nodeColorMap = new HashMap<>();
this.nodeColorSwatch = new ArrayList<>();
this.defaultNodeColor = Color.GRAY;
this.defaultNodeLabelFont = new Font(Font.DIALOG, Font.BOLD, 12);
this.defaultNodeLabelPaint = Color.BLACK;
this.nodeLabelAlignment = VerticalAlignment.CENTER;
this.nodeLabelOffsetX = 2.0;
this.nodeLabelOffsetY = 2.0;
this.toolTipGenerator = new StandardFlowLabelGenerator();
}
/**
* Returns a string identifying the plot type.
*
* @return A string identifying the plot type.
*/
@Override
public String getPlotType() {
return "FlowPlot";
}
/**
* Returns a reference to the dataset.
*
* @return A reference to the dataset (possibly {@code null}).
*/
public FlowDataset getDataset() {
return this.dataset;
}
/**
* Sets the dataset for the plot and sends a change notification to all
* registered listeners.
*
* @param dataset the dataset ({@code null} permitted).
*/
public void setDataset(FlowDataset dataset) {
this.dataset = dataset;
fireChangeEvent();
}
/**
* Returns the node margin (expressed as a percentage of the available
* plotting space) which is the gap between nodes (sources or destinations).
* The initial (default) value is {@code 0.01} (1 percent).
*
* @return The node margin.
*/
public double getNodeMargin() {
return this.nodeMargin;
}
/**
* Sets the node margin and sends a change notification to all registered
* listeners.
*
* @param margin the margin (expressed as a percentage).
*/
public void setNodeMargin(double margin) {
Args.requireNonNegative(margin, "margin");
this.nodeMargin = margin;
fireChangeEvent();
}
/**
* Returns the flow margin. This determines the gap between the graphic
* representation of the nodes (sources and destinations) and the curved
* flow representation. This is expressed as a percentage of the plot
* width so that it remains proportional as the plot is resized. The
* initial (default) value is {@code 0.005} (0.5 percent).
*
* @return The flow margin.
*/
public double getFlowMargin() {
return this.flowMargin;
}
/**
* Sets the flow margin and sends a change notification to all registered
* listeners.
*
* @param margin the margin (must be 0.0 or higher).
*/
public void setFlowMargin(double margin) {
Args.requireNonNegative(margin, "margin");
this.flowMargin = margin;
fireChangeEvent();
}
/**
* Returns the width of the source and destination nodes, expressed in
* Java2D user-space units. The initial (default) value is {@code 20.0}.
*
* @return The width.
*/
public double getNodeWidth() {
return this.nodeWidth;
}
/**
* Sets the width for the source and destination nodes and sends a change
* notification to all registered listeners.
*
* @param width the width.
*/
public void setNodeWidth(double width) {
this.nodeWidth = width;
fireChangeEvent();
}
/**
* Returns the list of colors that will be used to auto-populate the node
* colors when they are first rendered. If the list is empty, no color
* will be assigned to the node so, unless it is manually set, the default
* color will apply. This method returns a copy of the list, modifying
* the returned list will not affect the plot.
*
* @return The list of colors (possibly empty, but never {@code null}).
*/
public List getNodeColorSwatch() {
return new ArrayList<>(this.nodeColorSwatch);
}
/**
* Sets the color swatch for the plot.
*
* @param colors the list of colors ({@code null} not permitted).
*/
public void setNodeColorSwatch(List colors) {
Args.nullNotPermitted(colors, "colors");
this.nodeColorSwatch = colors;
}
/**
* Returns the fill color for the specified node.
*
* @param nodeKey the node key ({@code null} not permitted).
*
* @return The fill color (possibly {@code null}).
*/
public Color getNodeFillColor(NodeKey nodeKey) {
return this.nodeColorMap.get(nodeKey);
}
/**
* Sets the fill color for the specified node and sends a change
* notification to all registered listeners.
*
* @param nodeKey the node key ({@code null} not permitted).
* @param color the fill color ({@code null} permitted).
*/
public void setNodeFillColor(NodeKey nodeKey, Color color) {
this.nodeColorMap.put(nodeKey, color);
fireChangeEvent();
}
/**
* Returns the default node color. This is used when no specific node color
* has been specified. The initial (default) value is {@code Color.GRAY}.
*
* @return The default node color (never {@code null}).
*/
public Color getDefaultNodeColor() {
return this.defaultNodeColor;
}
/**
* Sets the default node color and sends a change event to registered
* listeners.
*
* @param color the color ({@code null} not permitted).
*/
public void setDefaultNodeColor(Color color) {
Args.nullNotPermitted(color, "color");
this.defaultNodeColor = color;
fireChangeEvent();
}
/**
* Returns the default font used to display labels for the source and
* destination nodes. The initial (default) value is
* {@code Font(Font.DIALOG, Font.BOLD, 12)}.
*
* @return The default font (never {@code null}).
*/
public Font getDefaultNodeLabelFont() {
return this.defaultNodeLabelFont;
}
/**
* Sets the default font used to display labels for the source and
* destination nodes and sends a change notification to all registered
* listeners.
*
* @param font the font ({@code null} not permitted).
*/
public void setDefaultNodeLabelFont(Font font) {
Args.nullNotPermitted(font, "font");
this.defaultNodeLabelFont = font;
fireChangeEvent();
}
/**
* Returns the default paint used to display labels for the source and
* destination nodes. The initial (default) value is {@code Color.BLACK}.
*
* @return The default paint (never {@code null}).
*/
public Paint getDefaultNodeLabelPaint() {
return this.defaultNodeLabelPaint;
}
/**
* Sets the default paint used to display labels for the source and
* destination nodes and sends a change notification to all registered
* listeners.
*
* @param paint the paint ({@code null} not permitted).
*/
public void setDefaultNodeLabelPaint(Paint paint) {
Args.nullNotPermitted(paint, "paint");
this.defaultNodeLabelPaint = paint;
fireChangeEvent();
}
/**
* Returns the vertical alignment of the node labels relative to the node.
* The initial (default) value is {@link VerticalAlignment#CENTER}.
*
* @return The alignment (never {@code null}).
*/
public VerticalAlignment getNodeLabelAlignment() {
return this.nodeLabelAlignment;
}
/**
* Sets the vertical alignment of the node labels and sends a change
* notification to all registered listeners.
*
* @param alignment the new alignment ({@code null} not permitted).
*/
public void setNodeLabelAlignment(VerticalAlignment alignment) {
Args.nullNotPermitted(alignment, "alignment");
this.nodeLabelAlignment = alignment;
fireChangeEvent();
}
/**
* Returns the x-offset for the node labels.
*
* @return The x-offset for the node labels.
*/
public double getNodeLabelOffsetX() {
return this.nodeLabelOffsetX;
}
/**
* Sets the x-offset for the node labels and sends a change notification
* to all registered listeners.
*
* @param offsetX the node label x-offset in Java2D units.
*/
public void setNodeLabelOffsetX(double offsetX) {
this.nodeLabelOffsetX = offsetX;
fireChangeEvent();
}
/**
* Returns the y-offset for the node labels.
*
* @return The y-offset for the node labels.
*/
public double getNodeLabelOffsetY() {
return nodeLabelOffsetY;
}
/**
* Sets the y-offset for the node labels and sends a change notification
* to all registered listeners.
*
* @param offsetY the node label y-offset in Java2D units.
*/
public void setNodeLabelOffsetY(double offsetY) {
this.nodeLabelOffsetY = offsetY;
fireChangeEvent();
}
/**
* Returns the tool tip generator that creates the strings that are
* displayed as tool tips for the flows displayed in the plot.
*
* @return The tool tip generator (possibly {@code null}).
*/
public FlowLabelGenerator getToolTipGenerator() {
return this.toolTipGenerator;
}
/**
* Sets the tool tip generator and sends a change notification to all
* registered listeners. If the generator is set to {@code null}, no tool
* tips will be displayed for the flows.
*
* @param generator the new generator ({@code null} permitted).
*/
public void setToolTipGenerator(FlowLabelGenerator generator) {
this.toolTipGenerator = generator;
fireChangeEvent();
}
/**
* Draws the flow plot within the specified area of the supplied graphics
* target {@code g2}.
*
* @param g2 the graphics target ({@code null} not permitted).
* @param area the plot area ({@code null} not permitted).
* @param anchor the anchor point (ignored).
* @param parentState the parent state (ignored).
* @param info the plot rendering info.
*/
@Override
public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState, PlotRenderingInfo info) {
Args.nullNotPermitted(g2, "g2");
Args.nullNotPermitted(area, "area");
EntityCollection entities = null;
if (info != null) {
info.setPlotArea(area);
entities = info.getOwner().getEntityCollection();
}
RectangleInsets insets = getInsets();
insets.trim(area);
if (info != null) {
info.setDataArea(area);
}
// use default JFreeChart background handling
drawBackground(g2, area);
// we need to ensure there is space to show all the inflows and all
// the outflows at each node group, so first we calculate the max
// flow space required - for each node in the group, consider the
// maximum of the inflow and the outflow
double flow2d = Double.POSITIVE_INFINITY;
double nodeMargin2d = this.nodeMargin * area.getHeight();
int stageCount = this.dataset.getStageCount();
for (int stage = 0; stage < this.dataset.getStageCount(); stage++) {
List sources = this.dataset.getSources(stage);
int nodeCount = sources.size();
double flowTotal = 0.0;
for (Comparable source : sources) {
double inflow = FlowDatasetUtils.calculateInflow(this.dataset, source, stage);
double outflow = FlowDatasetUtils.calculateOutflow(this.dataset, source, stage);
flowTotal = flowTotal + Math.max(inflow, outflow);
}
if (flowTotal > 0.0) {
double availableH = area.getHeight() - (nodeCount - 1) * nodeMargin2d;
flow2d = Math.min(availableH / flowTotal, flow2d);
}
if (stage == this.dataset.getStageCount() - 1) {
// check inflows to the final destination nodes...
List destinations = this.dataset.getDestinations(stage);
int destinationCount = destinations.size();
flowTotal = 0.0;
for (Comparable destination : destinations) {
double inflow = FlowDatasetUtils.calculateInflow(this.dataset, destination, stage + 1);
flowTotal = flowTotal + inflow;
}
if (flowTotal > 0.0) {
double availableH = area.getHeight() - (destinationCount - 1) * nodeMargin2d;
flow2d = Math.min(availableH / flowTotal, flow2d);
}
}
}
double stageWidth = (area.getWidth() - ((stageCount + 1) * this.nodeWidth)) / stageCount;
double flowOffset = area.getWidth() * this.flowMargin;
Map nodeRects = new HashMap<>();
boolean hasNodeSelections = FlowDatasetUtils.hasNodeSelections(this.dataset);
boolean hasFlowSelections = FlowDatasetUtils.hasFlowSelections(this.dataset);
// iterate over all the stages, we can render the source node rects and
// the flows ... we should add the destination node rects last, then
// in a final pass add the labels
for (int stage = 0; stage < this.dataset.getStageCount(); stage++) {
double stageLeft = area.getX() + (stage + 1) * this.nodeWidth + (stage * stageWidth);
double stageRight = stageLeft + stageWidth;
// calculate the source node and flow rectangles
Map sourceFlowRects = new HashMap<>();
double nodeY = area.getY();
for (Object s : this.dataset.getSources(stage)) {
Comparable source = (Comparable) s;
double inflow = FlowDatasetUtils.calculateInflow(dataset, source, stage);
double outflow = FlowDatasetUtils.calculateOutflow(dataset, source, stage);
double nodeHeight = (Math.max(inflow, outflow) * flow2d);
Rectangle2D nodeRect = new Rectangle2D.Double(stageLeft - nodeWidth, nodeY, nodeWidth, nodeHeight);
if (entities != null) {
entities.add(new NodeEntity(new NodeKey<>(stage, source), nodeRect, source.toString()));
}
nodeRects.put(new NodeKey<>(stage, source), nodeRect);
double y = nodeY;
for (Object d : this.dataset.getDestinations(stage)) {
Comparable destination = (Comparable) d;
Number flow = this.dataset.getFlow(stage, source, destination);
if (flow != null) {
double height = flow.doubleValue() * flow2d;
Rectangle2D rect = new Rectangle2D.Double(stageLeft - nodeWidth, y, nodeWidth, height);
sourceFlowRects.put(new FlowKey<>(stage, source, destination), rect);
y = y + height;
}
}
nodeY = nodeY + nodeHeight + nodeMargin2d;
}
// calculate the destination rectangles
Map destFlowRects = new HashMap<>();
nodeY = area.getY();
for (Object d : this.dataset.getDestinations(stage)) {
Comparable destination = (Comparable) d;
double inflow = FlowDatasetUtils.calculateInflow(dataset, destination, stage + 1);
double outflow = FlowDatasetUtils.calculateOutflow(dataset, destination, stage + 1);
double nodeHeight = Math.max(inflow, outflow) * flow2d;
nodeRects.put(new NodeKey<>(stage + 1, destination), new Rectangle2D.Double(stageRight, nodeY, nodeWidth, nodeHeight));
double y = nodeY;
for (Object s : this.dataset.getSources(stage)) {
Comparable source = (Comparable) s;
Number flow = this.dataset.getFlow(stage, source, destination);
if (flow != null) {
double height = flow.doubleValue() * flow2d;
Rectangle2D rect = new Rectangle2D.Double(stageRight, y, nodeWidth, height);
y = y + height;
destFlowRects.put(new FlowKey<>(stage, source, destination), rect);
}
}
nodeY = nodeY + nodeHeight + nodeMargin2d;
}
for (Object s : this.dataset.getSources(stage)) {
Comparable source = (Comparable) s;
NodeKey nodeKey = new NodeKey<>(stage, source);
Rectangle2D nodeRect = nodeRects.get(nodeKey);
Color ncol = lookupNodeColor(nodeKey);
if (hasNodeSelections) {
if (!Boolean.TRUE.equals(dataset.getNodeProperty(nodeKey, NodeKey.SELECTED_PROPERTY_KEY))) {
int g = (ncol.getRed() + ncol.getGreen() + ncol.getBlue()) / 3;
ncol = new Color(g, g, g, ncol.getAlpha());
}
}
g2.setPaint(ncol);
g2.fill(nodeRect);
for (Object d : this.dataset.getDestinations(stage)) {
Comparable destination = (Comparable) d;
FlowKey flowKey = new FlowKey<>(stage, source, destination);
Rectangle2D sourceRect = sourceFlowRects.get(flowKey);
if (sourceRect == null) {
continue;
}
Rectangle2D destRect = destFlowRects.get(flowKey);
Path2D connect = new Path2D.Double();
connect.moveTo(sourceRect.getMaxX() + flowOffset, sourceRect.getMinY());
connect.curveTo(stageLeft + stageWidth / 2.0, sourceRect.getMinY(), stageLeft + stageWidth / 2.0, destRect.getMinY(), destRect.getX() - flowOffset, destRect.getMinY());
connect.lineTo(destRect.getX() - flowOffset, destRect.getMaxY());
connect.curveTo(stageLeft + stageWidth / 2.0, destRect.getMaxY(), stageLeft + stageWidth / 2.0, sourceRect.getMaxY(), sourceRect.getMaxX() + flowOffset, sourceRect.getMaxY());
connect.closePath();
Color nc = lookupNodeColor(nodeKey);
if (hasFlowSelections) {
if (!Boolean.TRUE.equals(dataset.getFlowProperty(flowKey, FlowKey.SELECTED_PROPERTY_KEY))) {
int g = (ncol.getRed() + ncol.getGreen() + ncol.getBlue()) / 3;
nc = new Color(g, g, g, ncol.getAlpha());
}
}
GradientPaint gp = new GradientPaint((float) sourceRect.getMaxX(), 0, nc, (float) destRect.getMinX(), 0, new Color(nc.getRed(), nc.getGreen(), nc.getBlue(), 128));
Composite saved = g2.getComposite();
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f));
g2.setPaint(gp);
g2.fill(connect);
if (entities != null) {
String toolTip = null;
if (this.toolTipGenerator != null) {
toolTip = this.toolTipGenerator.generateLabel(this.dataset, flowKey);
}
entities.add(new FlowEntity(flowKey, connect, toolTip, ""));
}
g2.setComposite(saved);
}
}
}
// now draw the destination nodes
int lastStage = this.dataset.getStageCount() - 1;
for (Object d : this.dataset.getDestinations(lastStage)) {
Comparable destination = (Comparable) d;
NodeKey nodeKey = new NodeKey<>(lastStage + 1, destination);
Rectangle2D nodeRect = nodeRects.get(nodeKey);
if (nodeRect != null) {
Color ncol = lookupNodeColor(nodeKey);
if (hasNodeSelections) {
if (!Boolean.TRUE.equals(dataset.getNodeProperty(nodeKey, NodeKey.SELECTED_PROPERTY_KEY))) {
int g = (ncol.getRed() + ncol.getGreen() + ncol.getBlue()) / 3;
ncol = new Color(g, g, g, ncol.getAlpha());
}
}
g2.setPaint(ncol);
g2.fill(nodeRect);
if (entities != null) {
entities.add(new NodeEntity(new NodeKey<>(lastStage + 1, destination), nodeRect, destination.toString()));
}
}
}
// now draw all the labels over top of everything else
g2.setFont(this.defaultNodeLabelFont);
g2.setPaint(this.defaultNodeLabelPaint);
for (NodeKey key : nodeRects.keySet()) {
Rectangle2D r = nodeRects.get(key);
if (key.getStage() < this.dataset.getStageCount()) {
TextUtils.drawAlignedString(key.getNode().toString(), g2,
(float) (r.getMaxX() + flowOffset + this.nodeLabelOffsetX),
(float) labelY(r), TextAnchor.CENTER_LEFT);
} else {
TextUtils.drawAlignedString(key.getNode().toString(), g2,
(float) (r.getX() - flowOffset - this.nodeLabelOffsetX),
(float) labelY(r), TextAnchor.CENTER_RIGHT);
}
}
}
/**
* Performs a lookup on the color for the specified node.
*
* @param nodeKey the node key ({@code null} not permitted).
*
* @return The node color.
*/
protected Color lookupNodeColor(NodeKey nodeKey) {
Color result = this.nodeColorMap.get(nodeKey);
if (result == null) {
// if the color swatch is non-empty, we use it to autopopulate
// the node colors...
if (!this.nodeColorSwatch.isEmpty()) {
// look through previous stages to see if this source key is already seen
for (int s = 0; s < nodeKey.getStage(); s++) {
for (Object key : dataset.getSources(s)) {
if (nodeKey.getNode().equals(key)) {
Color color = this.nodeColorMap.get(new NodeKey<>(s, (Comparable) key));
setNodeFillColor(nodeKey, color);
return color;
}
}
}
result = this.nodeColorSwatch.get(Math.min(this.nodeColorSwatchPointer, this.nodeColorSwatch.size() - 1));
this.nodeColorSwatchPointer++;
if (this.nodeColorSwatchPointer > this.nodeColorSwatch.size() - 1) {
this.nodeColorSwatchPointer = 0;
}
setNodeFillColor(nodeKey, result);
return result;
} else {
result = this.defaultNodeColor;
}
}
return result;
}
/**
* Computes the y-coordinate for a node label taking into account the
* current alignment settings.
*
* @param r the node rectangle.
*
* @return The y-coordinate for the label.
*/
private double labelY(Rectangle2D r) {
if (this.nodeLabelAlignment == VerticalAlignment.TOP) {
return r.getY() + this.nodeLabelOffsetY;
} else if (this.nodeLabelAlignment == VerticalAlignment.BOTTOM) {
return r.getMaxY() - this.nodeLabelOffsetY;
} else {
return r.getCenterY();
}
}
/**
* Tests this plot for equality with an arbitrary object. Note that, for
* the purposes of this equality test, the dataset is ignored.
*
* @param obj the object ({@code null} permitted).
*
* @return A boolean.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof FlowPlot)) {
return false;
}
FlowPlot that = (FlowPlot) obj;
if (!this.defaultNodeColor.equals(that.defaultNodeColor)) {
return false;
}
if (!this.nodeColorMap.equals(that.nodeColorMap)) {
return false;
}
if (!this.nodeColorSwatch.equals(that.nodeColorSwatch)) {
return false;
}
if (!this.defaultNodeLabelFont.equals(that.defaultNodeLabelFont)) {
return false;
}
if (!PaintUtils.equal(this.defaultNodeLabelPaint, that.defaultNodeLabelPaint)) {
return false;
}
if (this.flowMargin != that.flowMargin) {
return false;
}
if (this.nodeMargin != that.nodeMargin) {
return false;
}
if (this.nodeWidth != that.nodeWidth) {
return false;
}
if (this.nodeLabelOffsetX != that.nodeLabelOffsetX) {
return false;
}
if (this.nodeLabelOffsetY != that.nodeLabelOffsetY) {
return false;
}
if (this.nodeLabelAlignment != that.nodeLabelAlignment) {
return false;
}
if (!Objects.equals(this.toolTipGenerator, that.toolTipGenerator)) {
return false;
}
return super.equals(obj);
}
/**
* Returns a hashcode for this instance.
*
* @return A hashcode.
*/
@Override
public int hashCode() {
int hash = 3;
hash = 83 * hash + (int) (Double.doubleToLongBits(this.nodeWidth) ^ (Double.doubleToLongBits(this.nodeWidth) >>> 32));
hash = 83 * hash + (int) (Double.doubleToLongBits(this.nodeMargin) ^ (Double.doubleToLongBits(this.nodeMargin) >>> 32));
hash = 83 * hash + (int) (Double.doubleToLongBits(this.flowMargin) ^ (Double.doubleToLongBits(this.flowMargin) >>> 32));
hash = 83 * hash + Objects.hashCode(this.nodeColorMap);
hash = 83 * hash + Objects.hashCode(this.nodeColorSwatch);
hash = 83 * hash + Objects.hashCode(this.defaultNodeColor);
hash = 83 * hash + Objects.hashCode(this.defaultNodeLabelFont);
hash = 83 * hash + Objects.hashCode(this.defaultNodeLabelPaint);
hash = 83 * hash + Objects.hashCode(this.nodeLabelAlignment);
hash = 83 * hash + (int) (Double.doubleToLongBits(this.nodeLabelOffsetX) ^ (Double.doubleToLongBits(this.nodeLabelOffsetX) >>> 32));
hash = 83 * hash + (int) (Double.doubleToLongBits(this.nodeLabelOffsetY) ^ (Double.doubleToLongBits(this.nodeLabelOffsetY) >>> 32));
hash = 83 * hash + Objects.hashCode(this.toolTipGenerator);
return hash;
}
/**
* Returns an independent copy of this {@code FlowPlot} instance (note,
* however, that the dataset is NOT cloned).
*
* @return A close of this instance.
*
* @throws CloneNotSupportedException
*/
@Override
public Object clone() throws CloneNotSupportedException {
FlowPlot clone = (FlowPlot) super.clone();
clone.nodeColorMap = new HashMap<>(this.nodeColorMap);
return clone;
}
}