Related
I've created a Histogram with JfreeChart that looks like this
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
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
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
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
I'm working on a project for my comp sci class which deals with image processing. My group and I are going to vertically flip and rotate a picture 90 degrees. The first part of my code is the sample from my teacher that turns the photo gray. I'm able to compile my code, but I get the error:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at Project6b.main(Project6b.java:10)
Press any key to continue...
I'm not even sure if what I've coded so far works, and I can't get this to run. Can anyone help me solve my runtime error?
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import java.awt.*;
public class Project6b
{
public static void main (String args[]) throws Exception
{
// I would use these, if the program is finished.
//grayscale(args[0], args[1]);
//verticalFlip(args[0], args[1]);
// I would comment the first two lines and
//uncomment these for testing purposes.
//grayscale("Vicc.jpg", "Vicc2.jpg");
verticalFlip("Vic.png", "Vic2.png");
}
/*public static void grayscale(String originalImage, String convertedImage) throws Exception
{
BufferedImage bufferImg = ImageIO.read(new File(originalImage));
int r,g,b;
Color color = null;
for( int x = 0; x <bufferImg.getWidth(); x++)
{
for(int y = 0; y<bufferImg.getHeight(); y++)
{
int rgb = bufferImg.getRGB(x,y);
color = new Color(rgb,true);
r = color.getRed();
g = color.getGreen();
b = color.getBlue();
color = new Color((r+g+b)/3, (r+g+b)/3, (r+g+b)/3);
bufferImg.setRGB(x,y, color.getRGB());
}
}
File outputfile = new File(convertedImage);
ImageIO.write(bufferImg, "png", outputfile);
} */
public static void verticalFlip(String originalImage, String convertedImage) throws Exception
{
BufferedImage bufferImg = ImageIO.read(new File(originalImage));
BufferedImage bufferImgOut = new BufferedImage(bufferImg.getWidth(),bufferImg.getHeight(), bufferImg.getType());
for (int x = 0; x < bufferImg.getWidth(); x++)
{
for (int y = 0; y < bufferImg.getHeight(); y++)
{
int px = bufferImg.getRGB(x, y);
int destY = bufferImg.getHeight() - y - 1;
bufferImg.setRGB(x, destY, px);
}
}
File outputfile = new File(convertedImage);
ImageIO.write(bufferImgOut, "png", outputfile);
}
//public static void Rotate(String originalImage, String convertedImage) throws Exception
//{
//}
}
The problem occurs when you don't pass any arguments at start up, like reimeus already mentioned.
If you are using an IDE like Eclipse or Netbeans, there should be some kind of start cfg where you can enter the arguments, which should be passed to the app. In case you are launching your app from the command line, use:
java Project6b Jack.jpg Jack2.jpg
java PROGRAM INPUT-IMAGE OUTPUT-IMAGE
Then you can change your main respectively:
public static void main (String args[]) throws Exception
{
// I would use these, if the program is finished.
grayscale(args[0], args[1]);
verticalFlip(args[0], args[1]);
// I would comment the first two lines and
//uncomment these for testing purposes.
//grayscale("Jack.jpg", "Jack.jpg");
//verticalFlip("Jack.jpg", "Jack.jpg");
}
(If you use the function calls at the bottom, you don't need to pass any arguments. This is good for quick debugging.)
Hope it helps!
Regarding verticalFlip:
public static void verticalFlip(String originalImage, String convertedImage)
throws Exception {
BufferedImage bufferImg = ImageIO.read(new File(originalImage));
// create a new Image, which will be your output img, so that
// you do NOT override some pixels in your source img - you'll need them.
BufferedImage bufferImgOut = new BufferedImage(bufferImg.getWidth(), bufferImg.getHeight(), bufferImg.getType());
for (int x = 0; x < bufferImg.getWidth(); x++) {
for (int y = 0; y < bufferImg.getHeight(); y++) {
int px = bufferImg.getRGB(x, y);
int destY = bufferImg.getHeight() - y - 1;
// your x-coordinate should stay the same, since you only flip vertically.
bufferImgOut.setRGB(x, destY, px);
}
}
File outputfile = new File(convertedImage);
ImageIO.write(bufferImgOut, "png", outputfile);
}
my below code saves the image..but its not black and white... its either black or white..
please help with it..if there is any error..help me out :(
and friends please try to help me with this same code itself.. i wont be understanding any other codes currently.. HELP...!!! (this code is a part of my whole code for implementing backpropagation algorithm)
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class PrintImageARGBPixels
{
public static void main(String args[])throws IOException
{
//extracting RGB values of each pixel of an image.
BufferedImage image = ImageIO.read(new
File("C:\\Users\\ark\\Desktop\\project_doc\\logo_1004.jpg"));
int total_pixels=1;
float sum=0;
ArrayList <Color> arr = new ArrayList<Color>();
ArrayList <Float> grey_f = new ArrayList<Float>();
System.out.println("Image Dimension: Height-" + h + ", Width-"+ w);
int w = image.getWidth();
int h = image.getHeight();
total_pixels=(h * w);
for(x=0;x<w;x++)
{
for(y=0;y<h;y++)
{
int rgb = image.getRGB(x, y);
Color c = new Color(rgb);
arr.add(c);
}
}
for(int i=0;i<total_pixels;i++)
{
sum=(arr.get(i).getRed()+arr.get(i).getGreen()+arr.get(i).getBlue())/(3*255);
grey_f.add(sum);
}
for(int i=0;i<total_pixels;i++)
{
Color co=new Color(grey_f.get(i),grey_f.get(i),grey_f.get(i));
int rgb_1=co.getRGB();
for(x=0;x<w;x++)
{
for(y=0;y<h;y++)
{
image.setRGB(x,y,rgb_1);
}
}
}
ImageIO.write(image, "png", new
File("C:\\Users\\ark\\Desktop\\project_doc\\multi_gray.png"));
}
}
For every RGB value you get, do
Color c = new Color(rgb);
int gr = (c.getRed() + c.getGreen() + c.getBlue())/3;
Color greyScale = new Color(gr, gr, gr);
Hope this helps.
I guess the problem is with sum=arr.get(i).getRed()+arr.get(i).getGreen()+arr.get(i).getBlue())/(3*255); as that returns an int of either 0 or 1. You actually only need to divide by 3 to get a value in the range of [0,255].
I'm fairly new to Java, and using NetBeans IDE 7.0.1.
Problem:
I'm trying to finish up a Java applet I've been working on that requires a pie chart. I've implemented the pie chart, but I've not been able to get the text labels to appear next to the data in the legend. Does anyone have any pointers?
package piechartapplet;
import javax.swing.*;
import java.awt.*;
public class PieChartApplet extends JApplet {
int TotalPieChartSlices = 7;
SliceValues[] pieSlice = new SliceValues[TotalPieChartSlices];
private int pieChartValueY;
public PieChartApplet()
{
//Source for input statisctics:
//Global Issues. (2012). World Military Spending. Retrieved from http://www.globalissues.org/article/75/world-military-spending
// Link: http://www.globalissues.org/article/75/world-military-spending
pieSlice[0] = new SliceValues(41.0, Color.RED,"United States");
pieSlice[1] = new SliceValues(8.2, Color.CYAN,"China");
pieSlice[2] = new SliceValues(4.1, Color.GREEN,"Russia");
pieSlice[3] = new SliceValues(3.6, Color.BLUE,"UK");
pieSlice[4] = new SliceValues(3.6, Color.PINK,"France");
pieSlice[5] = new SliceValues(21.3, Color.ORANGE,"Next 10 Countries Combined");
pieSlice[6] = new SliceValues(18.2, Color.LIGHT_GRAY,"Rest of the World");
}
// drawing the pir chart using the values in the array
public int drawPieChartValues(Graphics2D graphics, Rectangle pieChartArea, SliceValues[] pieSlice)
{
// setting font size/style
Font font = new Font("Arial", Font.BOLD, 24);
graphics.setFont(font);
// Title of Pie Chart
graphics.drawString("World Military Spending (% by Country)", 20, 20);
graphics.setFont(font);
// establishing inital area positioning
pieChartArea.x=10;
pieChartArea.y = 30;
// using the array values, rectangles, and color to draw the slices
for(int i=0; i<pieSlice.length;i++)
{
graphics. setColor(pieSlice[i].getSliceColor());
graphics.fillRect(pieChartArea.x, pieChartArea.y, 15, 10);
graphics.setColor(Color.BLACK);
pieChartArea.y+=20;
graphics.drawString(""+pieSlice[i].getSliceValue(), pieChartArea.x+25, pieChartArea.y-10);
}
return pieChartArea.y+=10;
}
//The code below was adapted from an example I found that enables me to pull from
// the array and use the values as the slice sizes, putting them into a 360* pie
// Walker, K. (2012). How to Draw a Pie Chart in Java. Retrieved from http://www.ehow.com/how_6647263_draw-pie-chart-java.html
public void drawPieChart(Graphics2D graphics, Rectangle pieChartArea, SliceValues[] pieSlice) {
// pulling array data for the individual slices
double total = 0.0;
for (int i=0; i<pieSlice.length; i++)
{
total += pieSlice[i].getSliceValue(); //pulling value
}
// drawing the slice and positioning it accordingly
double slice = 0.0D;
int StartAngle = 0;
pieChartArea.x = 20;
for (int i=0; i<pieSlice.length; i++) {
// finding initial and final angels
StartAngle = (int)(slice * 360 / total);
int finalAngle = (int)(pieSlice[i].getSliceValue() * 360 / total);
//loop for last slice
if (i == pieSlice.length-1)
{
finalAngle = 360 - StartAngle;
}
// Pulling color from array and setting accordingly
graphics.setColor(pieSlice[i].getSliceColor()); //pulling color
// drawing pie piece
graphics.fillArc(pieChartArea.x, pieChartValueY, pieChartArea.width/2, pieChartArea.height/2, StartAngle, finalAngle);
slice += pieSlice[i].getSliceValue();
}
}
public void paint(Graphics g)
{
super.paint(g);
pieChartValueY = drawPieChartValues((Graphics2D)g, getBounds(), pieSlice);
drawPieChart((Graphics2D)g, getBounds(), pieSlice);
}
public void init() {
// Sizing my applet
setSize(600,600);
// adding applet to pane
getContentPane().add(new PieChartApplet());
}
}
Here is the 'values' code
package piechartapplet;
import javax.swing.*;
import java.awt.*;
public class SliceValues
{
// Establishing values for the pir chart
private double Slicevalue;
private Color Slicecolor;
private String Slicestring;
// Construction begins...
public SliceValues(double value, Color color, String string) {
this.Slicevalue = value; //values from array
this.Slicecolor = color; //color from array
this.Slicestring = string; //string values
}
// calling slice values, colors, strings, and setting values, colors, strings for each slice
public double getSliceValue() {
return Slicevalue;
}
public void setSliceValue(double value) {
this.Slicevalue = value;
}
public Color getSliceColor() {
return Slicecolor;
}
public void setSliceColor(Color color) {
this.Slicecolor = color;
}
public String getSliceString() {
return Slicestring;
}
public void setSliceString(String string) {
this.Slicestring = string;
}
}
The source code for PieChartDemo1, illustrated here with labels, is included in the distribution.
After getting interested in the problem presented in the question
I tried to approach it few times and failed, and I do not like that :)
I think if the problem was split into sub issues it might help to solve it.
For simplicity lets assume the JTextArea will not change its size, so we do not need to worry about re-evaluation etc. I think the important issues are:
1.How to calculate the number of rows a certain text takes in a JTextArea?
2.What is the relation between the number of columns in a JTextArea and a number of characters it can fit in a row? So we can calculate row length.
Please find included below the sample code presenting the text area to process:
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class TextAreaLines
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JPanel p = new JPanel();
JFrame f = new JFrame();
JTextArea ta = new JTextArea("dadsad sasdasdasdasdasd");
ta.setWrapStyleWord(true);
ta.setLineWrap(true);
ta.setRows(5);
ta.setColumns(5);
p.add(ta);
f.setContentPane(p);
f.setSize(400, 300);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
//BTW the code below prints 1
System.out.println("ta.getLineCount()="+ta.getLineCount());
}
});
}
}
EDIT1: So I have come up with the following code but the problem is that the output is not what you see, i.e
//for input
//JTextArea ta = new JTextArea("alfred abcdefghijklmnoprstuwvxyz abcdefg");
//we have output
//s=alfred abcdefghijk
//s=lmnoprstuwvxyz a
//s=bcdefg
FontMetrics fm = ta.getFontMetrics(ta.getFont());
String text = ta.getText();
List<String> texts = new ArrayList<String>();
String line = "";
//no word wrap
for(int i = 0;i < text.length(); i++)
{
char c = text.charAt(i);
if(fm.stringWidth(line +c) <= ta.getPreferredSize().width)
{
//System.out.println("in; line+c ="+(line + c));
line += c;
}
else
{
texts.add(line);//store the text
line = ""+c;//empty the line, add the last char
}
}
texts.add(line);
for(String s: texts)
System.out.println("s="+s);
What am I doing wrong, what am I forgetting about? There is no word wrap on the text area.
EDIT2: #trashgod This is the output I am getting. Apparent from this is that we have different default fonts. And the problem in fact might be either font or even system dependent. (PS: I am on Win7).
line: Twas brillig and the slithy tovesD
line: id gyre and gimble in the wabe;
line count: 2
preferred: java.awt.Dimension[width=179,height=48]
bounds1: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.064453,w=170.0,h=15.09375]
layout1: java.awt.geom.Rectangle2D$Float[x=0.28125,y=-8.59375,w=168.25,h=11.125]
bounds2: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.064453,w=179.0,h=15.09375]
layout2: java.awt.geom.Rectangle2D$Float[x=0.921875,y=-8.59375,w=177.34375,h=11.125]
Compiling in my head what all of you guys are saying I think that the possibly reliable solution might be to hack the way in which the text area sets its text, and take a full control over it. By running the algorithm (above one, please notice, as suggested by #trashgod the '<' was changed to '<=') in the setText of the area.
What got me to think like this... for example in the sample I have provided if you
change text of the textarea to JTextArea ta = new JTextArea("alfred abcdefghijkl\nmnoprstuwvxyz ab\ncdefg"); as it is calculated in my case then it will fit perfectly into the textarea.
EDIT3: This is a kind of solution I quickly hacked, at least now the shown characters and calculated are exactly the same. Can someone else please check it out and let me know, possibly how it works on other machine then Win7? The example below is ready to use you should be able to resize the window and get the printout of lines the same as you see.
import java.awt.BorderLayout;
import java.awt.FontMetrics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class TextAreaLines
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JPanel p = new JPanel(new BorderLayout());
JFrame f = new JFrame();
final JTextArea ta = new JTextArea("alfred abcdefghijklmnoprstuwvxyz abcdefg");
ta.addComponentListener(new ComponentAdapter()
{
#Override
public void componentResized(ComponentEvent e)
{
super.componentResized(e);
System.out.println("ta componentResized");
reformatTextAreaText(ta);
}
});
//ta.setWrapStyleWord(true);
ta.setLineWrap(true);
p.add(ta);
f.setContentPane(p);
f.setSize(200, 100);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
private void reformatTextAreaText(JTextArea ta)
{
String text = ta.getText();
//remove all new line characters since we want to control line braking
text = text.replaceAll("\n", "");
FontMetrics fm = ta.getFontMetrics(ta.getFont());
List<String> texts = new ArrayList<String>();
String line = "";
//no word wrap
for(int i = 0; i < text.length(); i++)
{
char c = text.charAt(i);
if(fm.stringWidth(line + c) <= ta.getPreferredSize().width)
{
//System.out.println("in; line+c ="+(line + c));
line += c;
}
else
{
texts.add(line);//store the text
line = "" + c;//empty the line, add the last char
}
}
texts.add(line);
//print out of the lines
for(String s : texts)
System.out.println("s=" + s);
//build newText for the
String newText = "";
for(String s : texts)
newText += s + "\n";
ta.setText(newText);
}
});
}
}
Thanks in advance.
What am I doing wrong, what am I forgetting about?
Nothing, really. I modified your example to use "<=" on the width and to highlight a few features:
FontMetrics notes, "the advance of a String is not necessarily the sum of the advances of its characters measured in isolation…"
The preferred size of the text component matches the metric bounds pretty well for the widest line. This varies by font due to proportional spacing.
TextLayout shows even tighter bounds, but note the "baseline-relative coordinates."
The getLineCount() method counts line.separator delimited lines, not wrapped lines.
line: Twas brillig and the slithy toves
line: Did gyre and gimble in the wabe;
line count: 2
preferred: java.awt.Dimension[width=207,height=48]
bounds1: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.568359,w=205.0,h=15.310547]
layout1: java.awt.geom.Rectangle2D$Float[x=0.0,y=-10.0,w=200.0,h=13.0]
bounds2: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.568359,w=207.0,h=15.310547]
layout2: java.awt.geom.Rectangle2D$Float[x=1.0,y=-10.0,w=205.0,h=13.0]
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
/** #see http://stackoverflow.com/questions/5979795 */
public class TextAreaLine {
private static final String text1 =
"Twas brillig and the slithy toves\n";
private static final String text2 =
"Did gyre and gimble in the wabe;";
private static final JTextArea ta = new JTextArea(text1 + text2);
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
display();
}
});
}
static void display() {
JFrame f = new JFrame();
ta.setWrapStyleWord(false);
ta.setLineWrap(false);
ta.setRows(3);
f.add(ta);
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.setVisible(true);
FontMetrics fm = ta.getFontMetrics(ta.getFont());
List<String> texts = new ArrayList<String>();
Dimension d = ta.getPreferredSize();
String text = ta.getText();
String line = "";
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c != '\n') {
if (fm.stringWidth(line + c) <= d.width) {
line += c;
} else {
texts.add(line);
line = "" + c;
}
}
}
texts.add(line);
for (String s : texts) {
System.out.println("line: " + s);
}
System.out.println("line count: " + ta.getLineCount());
System.out.println("preferred: " + d);
System.out.println("bounds1: " + fm.getStringBounds(text1, null));
FontRenderContext frc = new FontRenderContext(null, false, false);
TextLayout layout = new TextLayout(text1, ta.getFont(), frc);
System.out.println("layout1: " + layout.getBounds());
System.out.println("bounds2: " + fm.getStringBounds(text2, null));
layout = new TextLayout(text2, ta.getFont(), frc);
System.out.println("layout2: " + layout.getBounds());
}
}
One thing you can do is use FontMetrics. I wrote some code for splitting JTextAreas up at certain line numbers. The setup code looked like:
Graphics2D g = (Graphics2D) g2;
FontMetrics m = g.getFontMetrics();
int lineHeight = m.getHeight();
This will tell you how tall a line of text is.
Unfortunately, letters have different widths in most fonts. But, you can use the following code to determine the width of a String.
int width = m.getStringBounds("Some String", g).getWidth();
I know this doesn't fully answer your question, but I hope it helps.
If you aren't using word wrap, here is the general algorithm you could use: (in the paint component method)
String text[] = getText().split("\n");
String newText = "";
for (String line: text) {
newText = line + "| " + line.length() + "\n";
}
setText(newText);
That's the general idea. Not sure how well it would work out. Let me know if you try it.
Not sure if this helps but you need to set the width of the text area so that the view knows when to wrap the text. Once you set the size you can determine the preferred height. When you know the preferred height you can use the font metrice line height to determine the total number of lines including the wrapped lines if any.
import java.awt.*;
import javax.swing.*;
public class TextAreaPreferredHeight extends JFrame
{
public TextAreaPreferredHeight()
{
JTextArea textArea = new JTextArea();
textArea.setText("one two three four five six seven eight nine ten");
textArea.setLineWrap( true );
textArea.setWrapStyleWord( true );
FontMetrics fm = textArea.getFontMetrics( textArea.getFont() );
int height = fm.getHeight();
System.out.println("000: " + textArea.getPreferredSize());
textArea.setSize(100, 1);
System.out.println("100: " + textArea.getPreferredSize());
System.out.println("lines : " + textArea.getPreferredSize().height / height);
textArea.setSize(200, 1);
System.out.println("200: " + textArea.getPreferredSize());
System.out.println("lines : " + textArea.getPreferredSize().height / height);
textArea.setSize(300, 1);
System.out.println("300: " + textArea.getPreferredSize());
System.out.println("lines : " + textArea.getPreferredSize().height / height);
add(textArea);
pack();
setVisible(true);
}
public static void main(String[] args)
{
new TextAreaPreferredHeight();
}
}
I've seen people using TextLayout for something like this.
Okay, I had written a program that you could load in an image, and it would convert it to ascii art. I wanted it to automatically make the text area the right aspect ration based on the image that was input.
However, I could never get it to work quite right. I gave up and remarked out my attempts, here is the snippet of what I tried. What I ended up doing was just having a textarea that sometimes didn't fill all the way.
//Graphics fontG = this.textBox4.CreateGraphics();
//fontG.PageUnit = GraphicsUnit.Point;
//SizeF fontSize = fontG.MeasureString(RowData, this.textBox4.Font,(SizeF) this.textBox4.ClientSize);
sb.AppendLine();
RowData = "";
//fontH += fontSize.Height + 1.2F;
//fontW = (int) fontSize.Width;
There was a similar question for android yesterday. The way I see it need to be solved is by an iterative approach:
Get JTextArea width
Use the FontMetrics to get the width of a string, as jjnguy suggested
split your string in words.
Start measuring the witdh of a string adding one word at a time until you reach the area width. Save that number.
Once you reached it, start a new iteration, adding one word at a time (beginning with the number saved).
The numbers of lines will be the number of iterations.
Unfortunately, row length will depend on the font and the particular string (not every character as the same width). You can count the number of characters in the words in each iteration, and return an array of lengths or a length average.
This is the related android question: How to find android TextView number of characters per line?
I found that counting the number of lines by text analysis only led to nothing. Instead my solution was calculating it from the preferred size of the text area ...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
public class LineNumbering extends JFrame {
private static final long serialVersionUID = 1L;
private static final Font fixedFont = new Font("Monospaced", Font.PLAIN, 12);
private static JTextArea jta;
private static JTextArea lines;
private static String lineSeparator = "\n";
private static int numRows = 10;
private static int numCols = 30;
public LineNumbering() {
super("Line Numbering Example");
}
public static void createAndShowGUI() {
JFrame frame = new LineNumbering();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane jsp = new JScrollPane();
jta = new JTextArea(numRows, numCols);
jta.setFont(fixedFont);
jta.setLineWrap(true);
jta.setWrapStyleWord(true);
lines = new JTextArea(numRows, 3);
lines.setEditable(false);
lines.setFocusable(false);
lines.setEnabled(false);
lines.setFont(fixedFont);
lines.setBackground(Color.LIGHT_GRAY);
lines.setDisabledTextColor(Color.BLACK);
// do initial line numbering
for (int i = 1; i <= lines.getRows(); i++) {
lines.append(i + System.getProperty("line.separator"));
}
final class DebugCaretListener implements CaretListener {
int rowHeight = jta.getFontMetrics(jta.getFont()).getHeight();
/**
* #return total of lines showed in the text area
*/
private int getTotalLinesInView() {
int insetsTotalHeight = jta.getInsets().top + jta.getInsets().bottom;
return (jta.getPreferredSize().height - insetsTotalHeight) / rowHeight;
}
/**
* #return text with line numbers
*/
public String getText() {
StringBuffer text = new StringBuffer();
int totalLines = getTotalLinesInView();
System.out.println("totalLines : " + totalLines);
for (int i = 1; i <= totalLines; i++) {
text.append(i);
if (i < totalLines) {
text.append(lineSeparator);
}
}
return text.toString();
}
/**
* <p>
* Reset line numbers on caret event. Since the total number of
* lines is calculated from preferred size of text area, we do this
* on an event that occurred after repainting of the text area.
* </p>
* (non-Javadoc)
*
* #see javax.swing.event.CaretListener#caretUpdate(javax.swing.event.CaretEvent)
*/
#Override
public void caretUpdate(CaretEvent e) {
int totalLines = getTotalLinesInView();
System.out.println("totalLines : " + totalLines);
if (totalLines >= numRows) {
lines.setText(getText());
}
}
}
jta.addCaretListener(new DebugCaretListener());
jsp.getViewport().add(jta);
jsp.setRowHeaderView(lines);
jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
jsp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
JPanel textPanel = new JPanel();
textPanel.add(jsp);
JPanel contentPanel = new JPanel();
contentPanel.add(textPanel);
frame.setContentPane(contentPanel);
contentPanel.setOpaque(true);
frame.pack();
frame.setPreferredSize(new Dimension(500, 500));
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}