【java/awt】绘制直方图例子,此例适应于值有正有负的情况


注意当前只是初稿状态,还有待修改。

先上图:

代码:

package flexChart;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;





// 直方图的柱类
class Bar{
    String name;
    int value;
    
    public Bar(String name,int value) {
        this.name=name;
        this.value=value;
    }
}

// 点类
class Point{
    public Point(float x,float y) {
        this.x=x;
        this.y=y;
    }
    float x;
    float y;
}

/**
 * 直方图
 * @author ufo
 * 2022年2月15日
 */
public class FlexChartMaker {
    // 图片宽度
    private final int WIDTH;
    
    // 图片高度
    private final int HEIGHT;
    
    // 上下边界宽度
    private final int MARGIN;
    
    // 标题栏高度
    private final int TITLE_HEIGHT;
    
    // img对象
    private BufferedImage img;
    
    // 绘图环境
    private Graphics2D g2d;
    
    // 直方图数据
    private List bars;
    
    private int yStart=0;
    
    /**
     * 构造函数
     * @param width
     * @param height
     * @param margin
     * @param titleHeight
     */
    public FlexChartMaker(int width,int height,int margin,int titleHeight) {
        this.WIDTH=width;
        this.HEIGHT=height;
        this.MARGIN=margin;
        this.TITLE_HEIGHT=titleHeight;
        
        this.img=new BufferedImage(this.WIDTH,this.HEIGHT,BufferedImage.TYPE_INT_RGB);
        this.g2d=(Graphics2D)img.getGraphics();
    }
    
    // 写入图片
    public void write2File(String path) {
        try {
            ImageIO.write(this.img, "PNG", new FileOutputStream(path));
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
    
    // 绘制图案
    public void draw() {
        // 消除线条锯齿  
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        
        // 白色底漆
        g2d.setColor(Color.white);
        g2d.fillRect(0, 0, this.WIDTH, this.HEIGHT);
        // 画脚标
        g2d.setFont(new Font("黑体",Font.PLAIN,8));
        FontMetrics fm1=g2d.getFontMetrics();
        String text="逆火狂飙绘制";
        int textWidth=fm1.stringWidth(text);
        g2d.setColor(Color.black);
        g2d.drawString(text,(this.WIDTH-textWidth)/2,this.HEIGHT-this.MARGIN/2);
        
        // 标题栏区域
        g2d.setColor(new Color(135,206,200));
        g2d.fillRect(this.MARGIN, this.MARGIN, this.WIDTH-2*this.MARGIN, this.TITLE_HEIGHT);
        // 画标题
        g2d.setFont(new Font("宋体",Font.BOLD,36));
        FontMetrics fm2=g2d.getFontMetrics();
        text="g2d绘制直方图示例";
        textWidth=fm2.stringWidth(text);
        g2d.setColor(Color.white);
        g2d.drawString(text,(this.WIDTH-textWidth)/2,this.MARGIN+this.TITLE_HEIGHT/2+10);
        
        // 天蓝色可绘制区域
        g2d.setColor(new Color(135,206,235));
        g2d.fillRect(this.MARGIN, this.MARGIN+this.TITLE_HEIGHT, this.WIDTH-2*this.MARGIN, this.HEIGHT-2*this.MARGIN-this.TITLE_HEIGHT);
    
        // 算y起点
        int max=-this.HEIGHT;
        int min= this.HEIGHT;
        
        // 取极值
        for(Bar b:bars) {
            if(b.value>max) {
                max=b.value;
            }
            
            if(b.value<min) {
                min=b.value;
            }
        }
        System.out.println("max="+max+" min="+min);
        
        int yZero=0;
        if(max>0 && min>0) {
            yZero=this.HEIGHT-this.MARGIN;            
        }else if(max<0 && min<0) {
            yZero=this.TITLE_HEIGHT+this.MARGIN;
        }else if(max>0 && min<0) {
            yZero=(this.HEIGHT-2*this.MARGIN-this.TITLE_HEIGHT)/2+this.TITLE_HEIGHT+this.MARGIN;
            this.yStart=yZero;
            
            // 基准线
            g2d.setColor(Color.yellow);
            g2d.setStroke(new BasicStroke(1.0f));
            g2d.drawLine(this.MARGIN,yZero, this.WIDTH-this.MARGIN, yZero);
            
            // 进行坐标变换,上面为屏幕坐标系,下面为平面直角坐标系(笛卡尔坐标系)
            resetCoodinate(yZero);
            
            // 绘图区域的端点
            int xStart=0;
            int xEnd=xStart+this.WIDTH-2*this.MARGIN;      
            int yMin=yZero-(this.HEIGHT-this.MARGIN);
            int yMax=(this.HEIGHT)-yZero-this.MARGIN;
            System.out.println(String.format("xStart=%d,xEnd=%d,yMin=%d,yMax=%d,", xStart,xEnd,yMin,yMax));

            // 柱子数量
            final int barCnt=this.bars.size();
            
            // 竖向网格线开始
            final float stepx=(this.WIDTH-2*this.MARGIN)/barCnt;
            final float CW=stepx/3;// CW:Column Width
            
            // LINE_TYPE_DASHED   
            Stroke dashed=new BasicStroke(1,BasicStroke.CAP_BUTT,   
                                                      BasicStroke.JOIN_BEVEL,   0,   
                                                      new   float[]{16,   4},   0);
            g2d.setColor(Color.gray);
            
            for(int i=0;i) {
                float x=i*stepx;
                
                g2d.setStroke(new BasicStroke(1.0f));
                g2d.drawLine((int)x, yMin, (int)x, yMax);
                
                g2d.setStroke(dashed);
                g2d.drawLine((int)(x+CW), yMin, (int)(x+CW), yMax);
                g2d.drawLine((int)(x+2*CW), yMin, (int)(x+2*CW), yMax);
            }
            // 竖向网格线结束
            
            // 以最指定比例
            float maxCnt=-1;
            for(Bar b:bars) {
                int abs=Math.abs(b.value);
                if(abs>maxCnt) {
                    maxCnt=abs;
                }
            }
            final float ratio=yMax/maxCnt;
            
            // 横向网格线开始
            final float stepy=(yMax-yMin)/barCnt;
            final float GH=stepy/3;// GH:Grid Width
            
            for(int i=0;i<=barCnt;i++){
                float y=yMin+i*stepy;
                
                g2d.setStroke(new BasicStroke(1.0f));
                g2d.drawLine(xStart,(int)y, xEnd, (int)y);
                
                g2d.setFont(new Font("宋体",Font.BOLD,16));
                g2d.setColor(Color.black);
                int yValue=(int)(y*maxCnt/yMax);
                putString(g2d,yValue+"",15,(int)y);
                
                if(i>0) {
                    g2d.setStroke(dashed);
                    yValue=(int)((y-GH)*maxCnt/yMax);
                    g2d.drawLine(xStart,(int)(y-GH), xEnd, (int)(y-GH));
                    putString(g2d,yValue+"",xEnd+15,(int)(y-GH));
                    
                    g2d.drawLine(xStart,(int)(y-2*GH), xEnd, (int)(y-2*GH));
                }
            }
            // 横向网格线结束
            
            // --往下是画柱状图
            for(int i=0;i<this.bars.size();i++){
                Bar bar=this.bars.get(i);
                
                float x=i*stepx+(CW);
                float w=CW;
                
                float y=0,h=0;
                if(bar.value>0) {
                    y=0;            
                    h=bar.value*ratio;
                }else {
                    y=bar.value*ratio;
                    h=Math.abs(bar.value*ratio);
                }
                
                g2d.setColor(getColor(i));
                g2d.fillRect((int)x, (int)y, (int)(w), (int)(h));

                // 在柱子顶写文字
                float textX=x+CW/2+17;
                float textY=y+h/2;
                g2d.setFont(new Font("宋体",Font.BOLD,16));
                g2d.setColor(Color.black);
                putString(g2d,bar.name+"="+bar.value,(int)(textX),(int)(textY));
            }
        }
        
 
    }
    
    // 重置屏幕坐标系为笛卡尔坐标系
    private void resetCoodinate(int yStart) {
        AffineTransform trans = new AffineTransform();
        trans.translate(this.MARGIN,this.HEIGHT-yStart+this.TITLE_HEIGHT);
        trans.rotate(getRad(180.0),0,0);
        trans.scale(-1,1);        
        this.g2d.setTransform(trans);
    }
    
    // 添加一项直方图柱子
    public void addBar(String name,int value) {
        if(bars==null) {
            bars=new ArrayList();
        }
        
        bars.add(new Bar(name,value));
    }
    
    // 传入度数,返回弧度
    private static double getRad(double degree) {
        return degree*Math.PI/180.0f;
    }
    
    // 取一种颜色
    private static Color getColor(int idx) {
        Color[] colors= {Color.red,Color.yellow,Color.blue,Color.magenta,Color.green,Color.orange,Color.cyan};
        
        int i=idx % colors.length;
        return colors[i];
    }
    
    // 写文字
    private void putString(Graphics2D g2d,String text,int x,int y) {
        AffineTransform previousTrans = g2d.getTransform();
        
        AffineTransform newtrans = new AffineTransform();

        FontMetrics fm2=g2d.getFontMetrics();
        int textWidth=fm2.stringWidth(text);
        
        newtrans.translate(x-textWidth/2, (this.HEIGHT-this.yStart+this.TITLE_HEIGHT)-y);
        
        g2d.setTransform(newtrans);
        g2d.drawString(text,0,0);
        
        g2d.setTransform(previousTrans);
    }
    
    public static void main(String[] args) {
        FlexChartMaker fcm=new FlexChartMaker(1200,960,20,80);
        
        fcm.addBar("a", 180);
        fcm.addBar("b", 40);
        fcm.addBar("c", -122);
        fcm.addBar("d", 30);
        fcm.addBar("e", 30);
        fcm.addBar("f", 20);
        
        fcm.draw();
        fcm.write2File("c:\\hy\\220215.png");
        System.out.println("图片做成");
    }
}

END

相关