0

I am trying to implement a spider chart in my application. For this, I used the Spiderwebplot patch file. This solved my problem of range axis displaying. But here I am unable to change the axis values dynamically based on min and max values of datasets. I am expecting output as the second picture in the below image. I want to change the range axis values and graph structure based on the dataset values.

enter image description here

For more info, I am sharing my sample snippet below.

package com.test;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.FileImageOutputStream;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.SpiderWebPlot;
import org.jfree.data.category.DefaultCategoryDataset;

public class DemoChart {
    public static JFreeChart createChart1() {
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        dataset.addValue(100.0, "Group A", "Jan 19");
        dataset.addValue(99.97, "Group A", "Aug 19");
        dataset.addValue(99.96, "Group A", "Sep 19");
        dataset.addValue(99.98, "Group A", "Oct 19");
        dataset.addValue(99.99, "Group A", "Jul 19");
        SpiderWebPlotPatch o = new SpiderWebPlotPatch();
        SpiderWebPlot plot = o.getPlot(dataset);
        JFreeChart chart = new JFreeChart(plot);
        return chart;
    }
    public static void main(String args[]) throws IOException {
        JFreeChart jfreechart = createChart1();
        BufferedImage createBufferedImage = jfreechart.createBufferedImage(500, 500);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(createBufferedImage, "png", bos);
        byte[] byteArray = bos.toByteArray();
        try (FileImageOutputStream fos = new FileImageOutputStream(new File("C:\\Users\\abc\\Desktop\\abc.png"))) {
            fos.write(byteArray);
        }
    }
}

SpiderWebPlotPatch.java

package com.test;

import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.NumberFormat;

import org.jfree.chart.plot.SpiderWebPlot;
import org.jfree.data.category.CategoryDataset;

public class SpiderWebPlotPatch {

    public SpiderWebPlot getPlot(CategoryDataset data) {
        final SpiderWebPlot plot = new SpiderWebPlot(data) {
            // put this many labels on each axis.
            private int ticks = DEFAULT_TICKS;
            private static final int DEFAULT_TICKS = 5;
            private NumberFormat format = NumberFormat.getInstance();
            // constant for creating perpendicular tick marks.
            private static final double PERPENDICULAR = 90;
            // the size of a tick mark, as a percentage of the entire line length.
            private static final double TICK_SCALE = 0.015;
            // the gap between the axis line and the numeric label itself.
            private int valueLabelGap = DEFAULT_GAP;
            private static final int DEFAULT_GAP = 10;
            // the threshold used for determining if something is "on" the axis
            private static final double THRESHOLD = 15;

            /**
             * {@inheritDoc}
             */
            @Override
            protected void drawLabel(final Graphics2D g2, final Rectangle2D plotArea, final double value, final int cat,
                    final double startAngle, final double extent) {
                super.drawLabel(g2, plotArea, value, cat, startAngle, extent);
                final FontRenderContext frc = g2.getFontRenderContext();
                final double[] transformed = new double[2];
                final double[] transformer = new double[2];
                final Arc2D arc1 = new Arc2D.Double(plotArea, startAngle, 0, Arc2D.OPEN);
                for (int i = 1; i <= ticks; i++) {

                    final Point2D point1 = arc1.getEndPoint();

                    final double deltaX = plotArea.getCenterX();
                    final double deltaY = plotArea.getCenterY();
                    double labelX = point1.getX() - deltaX;
                    double labelY = point1.getY() - deltaY;

                    final double scale = ((double) i / (double) ticks);
                    final AffineTransform tx = AffineTransform.getScaleInstance(scale, scale);
                    // for getting the tick mark start points.
                    final AffineTransform pointTrans = AffineTransform.getScaleInstance(scale + TICK_SCALE,
                            scale + TICK_SCALE);
                    transformer[0] = labelX;
                    transformer[1] = labelY;
                    pointTrans.transform(transformer, 0, transformed, 0, 1);
                    final double pointX = transformed[0] + deltaX;
                    final double pointY = transformed[1] + deltaY;
                    tx.transform(transformer, 0, transformed, 0, 1);
                    labelX = transformed[0] + deltaX;
                    labelY = transformed[1] + deltaY;

                    double rotated = (PERPENDICULAR);

                    AffineTransform rotateTrans = AffineTransform.getRotateInstance(Math.toRadians(rotated), labelX,
                            labelY);
                    transformer[0] = pointX;
                    transformer[1] = pointY;
                    rotateTrans.transform(transformer, 0, transformed, 0, 1);
                    final double x1 = transformed[0];
                    final double y1 = transformed[1];

                    rotated = (-PERPENDICULAR);
                    rotateTrans = AffineTransform.getRotateInstance(Math.toRadians(rotated), labelX, labelY);

                    rotateTrans.transform(transformer, 0, transformed, 0, 1);

                    final Composite saveComposite = g2.getComposite();
                    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));

                    g2.draw(new Line2D.Double(transformed[0], transformed[1], x1, y1));

                    if (startAngle == this.getStartAngle()) {
                        final String label = format.format(((double) i / (double) ticks) * this.getMaxValue());
                        final Rectangle2D labelBounds = getLabelFont().getStringBounds(label, frc);

                        final LineMetrics lm = getLabelFont().getLineMetrics(label, frc);
                        final double ascent = lm.getAscent();

                        // move based on quadrant.
                        if (Math.abs(labelX - plotArea.getCenterX()) < THRESHOLD) {
                            // on Y Axis, label to right.
                            labelX += valueLabelGap;
                            // center vertically.
                            labelY += ascent / (float) 2;
                        } else if (Math.abs(labelY - plotArea.getCenterY()) < THRESHOLD) {
                            // on X Axis, label underneath.
                            labelY += valueLabelGap;
                        } else if (labelX >= plotArea.getCenterX()) {
                            if (labelY < plotArea.getCenterY()) {
                                // quadrant 1
                                labelX += valueLabelGap;
                                labelY += valueLabelGap;
                            } else {
                                // quadrant 2
                                labelX -= valueLabelGap;
                                labelY += valueLabelGap;
                            }
                        } else {
                            if (labelY > plotArea.getCenterY()) {
                                // quadrant 3
                                labelX -= valueLabelGap;
                                labelY -= valueLabelGap;
                            } else {
                                // quadrant 4
                                labelX += valueLabelGap;
                                labelY -= valueLabelGap;
                            }
                        }
                        g2.setPaint(getLabelPaint());
                        g2.setFont(getLabelFont());
                        g2.drawString(label, (float) labelX, (float) labelY);
                    }
                    g2.setComposite(saveComposite);
                }
            }

            /**
             * sets the number of tick marks on this spider chart.
             * 
             * @param ticks the new number of tickmarks.
             */
            public void setTicks(final int ticks) {
                this.ticks = ticks;
            }

            /**
             * sets the numberformat for the tick labels on this spider chart.
             * 
             * @param format the new number format object.
             */
            public void setFormat(final NumberFormat format) {
                this.format = format;
            }

        };
        return plot;
    }

}

With different dataset values

// dataset 1
dataset.addValue(100.0, "Group A", "Jan 19");
dataset.addValue(99.97, "Group A", "Aug 19");
dataset.addValue(99.96, "Group A", "Sep 19");
dataset.addValue(99.98, "Group A", "Oct 19");
dataset.addValue(99.99, "Group A", "Jul 19");   
//dataset 2
dataset.addValue(100.0, "Group A", "Jan 19");
dataset.addValue(99.97, "Group A", "Aug 19");
dataset.addValue(99.96, "Group A", "Sep 19");
dataset.addValue(99.98, "Group A", "Oct 19");
dataset.addValue(50.0, "Group A", "Jul 19");

enter image description here

In patch file I seen following lines are producing axis range values .

 final String label = format.format(((double)i/(double)ticks)*this.getMaxValue());
 final Rectangle2D labelBounds = getLabelFont().getStringBounds(label, frc);
 final LineMetrics lm = getLabelFont().getLineMetrics(label, frc);
Vijay Raju
  • 150
  • 1
  • 12
  • I don't understand your data; `SpiderWebPlot` is typically used to display multivariate data. Does this related [example](https://stackoverflow.com/a/32885067/230513) offer any insight? – trashgod Aug 31 '20 at 22:21
  • @trashgod. Iet me explain my dataset values. I am getting list of percentages for each month of year as a map like month name as key and percentage as double. According to front end they are representing dataset values as spider chart. Same I am trying to replicate in java layer as pdf file. Is it possible to produce the output like above picture? – Vijay Raju Sep 01 '20 at 09:52
  • Your dataset only has one series with an empty string for `rowKey`. Have you tried adding a second series to see the effect? I don't see support for scaling or translating the radial axis. Can you scale the values? – trashgod Sep 01 '20 at 23:44
  • I updated the question and changed the dataset values, removed the empty string for rowKey even though it's giving still the same output. What I observed is in the dataset all values are near to dataset max values. Suppose I am changing anyone rowKey to 40 or 50 graphs I observed the difference Because the scale is taking from 20-100. format.format(((double)i/(double)ticks)*this.getMaxValue()) how to change this any idea? – Vijay Raju Sep 02 '20 at 08:22
  • I see the same result. The radial axis ranges from 0 to the maximum dataset value. You want to see small differences more easily, but that's not supported. Change dataset two's `rowKey` to `"Group B"` to see one polygon overlay the other. Consider how you might translate the values to amplify small differences. – trashgod Sep 02 '20 at 18:16
  • A typical interpolation scheme is examined [here](https://stackoverflow.com/q/5731863/230513); for example, you might map [min…max] onto [0…1]. – trashgod Sep 03 '20 at 00:45

1 Answers1

0

You only need to change the follwoing code in the drawing routines "drawRadarPoly" and "drawLabel"

Original code line in "draw":

Point2D point = getWebPoint(plotArea, angle, value / this.getMaxValue());

into

Plot2D point = getWebPoint(plotArea, angle, (value-this.getMinValue()) / this.getMaxValue() - this.getMinValue))

For this you need obiously to determine the minimum of the dataset(s) you want to use, but this can be done like the maximum determination.

For the "drawLabel" method you need to change

final String label = format.format((double)i/(double)ticks)*this.getMaxValue());

into

final String label = format.format(this.getMinValue()+((this.getMaxValue()-this.getMinValue))/ticks)*i);

It might be also a good idea to add a switch to change back to the zero point definition if required...

Diz
  • 1
  • 1
  • tq for the answer @Diz. can u explain me what is required to pass in value param place for the below line. Plot2D point = getWebPoint(plotArea, angle, (value-this.getMinValue()) / this.getMaxValue() - this.getMinValue)); – Vijay Raju Feb 22 '21 at 15:34
  • the "value" is comming from `double value = dataValue.doubleValue();` and "dataValue" is from `Number dataValue = getPlotValue(series, cat);` or do I missunderstood your question? – Diz Feb 23 '21 at 16:25