Norbert
Norbert

Reputation: 879

How to display a single Histogram bar with a different color

I've created a Histogram with JfreeChart that looks like this enter image description here

I want to highlight a bar based on if a specific value is contained in the bin the bar represents. For example if the red bar below represents the number of values between 100-110 (inclusive) and the specific value i'm interested in is 103. I want to highlight the bar (change it to a different color than all the other bars) i.e red instead of blue

enter image description here

I've thought of subclassing

org.jfree.data.statistics.HistogramDataset

to use in concert with a subclass of XYBarRenderer in order to leverage the

org.jfree.chart.renderer.xy.XYItemRendererState#startSeriesPass

method. My thought here is that i could create two identical series with different base colors. And configure the startSeriesPass method to draw all the bars (bins) in the first series EXCEPT the bar that needs to be highlighted. Then draw only the bar that needs to be highlighted from the second series during the next iteration.

This has been proving quite difficult as org.jfree.data.statistics.HistogramDataset defines it's getBins method as package protected which I imagine is by design.

Based on that I am wondering is there a canonical way of changing the color of a specific bar in a histogramDataset

Upvotes: 2

Views: 857

Answers (2)

Norbert
Norbert

Reputation: 879

The below controller does several things in addition to highlighting a bar in a histogram data set

package com.capitalone.mobile.orx.jchartpoc.controller;

import java.awt.Color;
import java.awt.Font;
import java.awt.Paint;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import lombok.Getter;
import lombok.Setter;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYPointerAnnotation;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.statistics.HistogramBin;
import org.jfree.data.statistics.HistogramDataset;
import org.jfree.ui.RectangleInsets;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Norbert Powell
 * Created on Dec 21, 2016
 */
@RestController
public class HistogramController {

    public class HistogramPlotGenerator {
        private double upperBound;
        private double minimum;
        private double maximum;
        private int bins = 10;


         public class RelativeSpendRenderer extends XYBarRenderer{

            private static final long serialVersionUID = 1L;
            @Getter @Setter
            private int userSpendLevelBarColumn;
            @Getter @Setter
            private Color userSpendLevelBarColumnColor = new Color(208,48,39);

            public RelativeSpendRenderer(int usplCol) {
                this.userSpendLevelBarColumn = usplCol;
            }

            @Override
            public Paint getItemPaint(int row, int column) {
                if (column == userSpendLevelBarColumn){
                    return  userSpendLevelBarColumnColor;
                }
                return super.getItemPaint(row, column);
            }
         }

         public JFreeChart createHisto(){
                long chartCreationTime = System.currentTimeMillis();
                HistogramDataset histogramDataSet = new HistogramDataset();
                List<Number> spendLevels = 
                        Arrays.asList(12,21,34,3,24,56,7,8,9,100,75,555,65,32,566,700,800,900,307,1000,10201,222,323,444,201,111);
                double userSpendLevelValue = spendLevels.get(10).doubleValue(); // point to be highlighted  
                double [] spls = new double [spendLevels.size()];
                minimum = Double.MAX_VALUE;
                maximum = 0.0;

                for(int i=0; i < spendLevels.size(); i++){
                    double spl = spendLevels.get(i).doubleValue();
                    maximum = Math.max(maximum,spl);
                    minimum = Math.min(minimum, spl);   
                    spls[i] = spl;
                }

                upperBound = 0.0;
                histogramDataSet.addSeries("Spend", spls, bins,minimum,maximum);
                for ( int i=0; i <bins; i++){
                    upperBound = Math.max(histogramDataSet.getYValue(0, i), upperBound);
                }

                JFreeChart barGraph = ChartFactory.createHistogram(null, "$$$", null, histogramDataSet, PlotOrientation.VERTICAL, false, false, false);
                System.out.println("Time to create bar chart: " + (System.currentTimeMillis() - chartCreationTime)+"ms");
                int userSpendBarIndex = getHighlightBar(userSpendLevelValue);
                XYPlot plot = barGraph.getXYPlot();
                plot.setRenderer(new RelativeSpendRenderer(userSpendBarIndex));
                placePointer(histogramDataSet, userSpendBarIndex, plot);
                modifyChart(barGraph);
                return barGraph;            
        }

         private void placePointer(HistogramDataset histogramDataSet,int userSpendBarIndex, XYPlot plot) {
            double x =histogramDataSet.getX(0, userSpendBarIndex).doubleValue();
            double y = histogramDataSet.getY(0, userSpendBarIndex).doubleValue();
            double angle = (3*Math.PI/2);
            XYPointerAnnotation arrow = new XYPointerAnnotation(" ", x, y, angle); 
            arrow.setTipRadius(0); // distance of arrow head from bar  
            arrow.setBaseRadius(10);// distance from arrow head to end of arrow if arrowLength and BaseRadius are > 0 and arrowLength > BaseRadius only the arrow head will be shown
            if (y == upperBound){
                plot.getRangeAxis().setUpperBound(upperBound + arrow.getBaseRadius());
            }else{
                plot.getRangeAxis().setUpperBound(upperBound);
            }
            plot.addAnnotation(arrow);
        }

         private int getHighlightBar(double userSpendValue){
            int highlightBarIndex=0;
            double binWidth = (maximum - minimum) / bins;
            double lower = minimum;
            double upper;
            ArrayList<HistogramBin> binList = new ArrayList<HistogramBin>(bins);
            for (int i = 0; i < bins; i++) {
                HistogramBin bin;
                // make sure bins[bins.length]'s upper boundary ends at maximum
                // to avoid the rounding issue. the bins[0] lower boundary is
                // guaranteed start from min
                if (i == bins - 1) {
                    bin = new HistogramBin(lower, maximum);
                }
                else {
                    upper = minimum + (i + 1) * binWidth;
                    bin = new HistogramBin(lower, upper);
                    lower = upper;
                }
                binList.add(bin);
            }

            for(HistogramBin bin : binList){
                if (userSpendValue >= bin.getStartBoundary() && userSpendValue <= bin.getEndBoundary()){
                    return highlightBarIndex;
                }
                highlightBarIndex++;
            }
            return -1;
        }

         private void modifyChart(JFreeChart chart) {
                Color lineChartColor = new Color(1, 158, 213);

                // plot manipulations
                XYPlot xyPlotModifier = chart.getXYPlot();
                xyPlotModifier.setOutlineVisible(false);
                xyPlotModifier.setRangeMinorGridlinesVisible(false);
                xyPlotModifier.setRangeCrosshairVisible(false);
                xyPlotModifier.setRangeGridlinesVisible(false);
                xyPlotModifier.setRangeZeroBaselineVisible(false);
                xyPlotModifier.setBackgroundPaint(Color.WHITE);
                xyPlotModifier.getDataset().getSeriesCount();

                //Axis modifications
                xyPlotModifier.getRangeAxis().setVisible(false);
                xyPlotModifier.getDomainAxis().setTickLabelsVisible(false);
                xyPlotModifier.getDomainAxis().setMinorTickMarksVisible(false);
                xyPlotModifier.getDomainAxis().setTickMarksVisible(false);
                xyPlotModifier.getDomainAxis().setLabelFont(new Font("SansSerif", Font.PLAIN, 1));  
                xyPlotModifier.setAxisOffset(new RectangleInsets(0.0,0.0,0.0,0.0));

//              Actual data point manipulations
                XYBarRenderer renderer = (XYBarRenderer) xyPlotModifier.getRenderer();
                renderer.setSeriesPaint(0,lineChartColor, true);
                renderer.setBaseOutlinePaint(Color.BLACK, true);
                renderer.setDrawBarOutline(true);
                chart.removeLegend();
            }

    }

    @RequestMapping(value = "getHisto", method = RequestMethod.GET, produces = MediaType.IMAGE_PNG_VALUE)
    public ResponseEntity<byte[]> getPNGChart(@RequestHeader HttpHeaders headers)throws Exception {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            HistogramPlotGenerator generator = new HistogramPlotGenerator();
            ChartUtilities.writeBufferedImageAsPNG(baos, generator.createHisto().createBufferedImage(352, 90));
            return new ResponseEntity<byte[]>(baos.toByteArray(), HttpStatus.OK);
        }


}

The

getHighLightBar

and the

RelativeSpendRenderer class

work in tandem to produce the bar index that needs highlighting and search for the index when printing to highlight it.

The placePointer method takes into account the pointer being put on the highest bar and work to ensure that the pointer doesn't get truncated

Upvotes: 1

Md. Nasir Uddin Bhuiyan
Md. Nasir Uddin Bhuiyan

Reputation: 1596

Check this Link

    final CategoryItemRenderer renderer = new CustomRenderer(
        new Paint[] {Color.blue, Color.blue, Color.blue,
            Color.red, Color.blue, Color.blue,
            Color.blue, Color.blue}
    );

just give the specific color for the special bar

you can check this also

Upvotes: 0

Related Questions