So i have a barchart in MPandroid, and i need to put the names on the chart, but there is no space where i want them, the solution is to put them on top of the chart. Like this:
can someone help me achieve this ?
There isn't a built-in way of putting the text in a non-standard location, but you can do it by writing a custom renderer for your chart. If you extend the HorizontalBarChartRenderer class and override the drawValues method (making just a few modifications to the original version in the code linked) you can change where the label gets drawn.
Custom Renderer
Code copied from drawValues in HorizontalBarChartRenderer and simplified to only include required parts for this use-case (removed logic for stacked charts, icons, toggling values on and off, etc). The only change is to the x,y values passed to drawValue()
private static class MyRenderer extends HorizontalBarChartRenderer {
public MyRenderer(BarDataProvider chart, ChartAnimator animator,
ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
#Override
public void drawValues(Canvas c) {
List<IBarDataSet> dataSets = mChart.getBarData().getDataSets();
final float valueOffsetPlus = Utils.convertDpToPixel(5f);
for (int i = 0; i < mChart.getBarData().getDataSetCount(); i++) {
IBarDataSet dataSet = dataSets.get(i);
// apply the text-styling defined by the DataSet
applyValueTextStyle(dataSet);
ValueFormatter formatter = dataSet.getValueFormatter();
// get the buffer
BarBuffer buffer = mBarBuffers[i];
for (int j = 0; j < buffer.buffer.length * mAnimator.getPhaseX(); j += 4) {
if (!mViewPortHandler.isInBoundsTop(buffer.buffer[j + 1]))
break;
if (!mViewPortHandler.isInBoundsX(buffer.buffer[j]))
continue;
if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1]))
continue;
BarEntry entry = dataSet.getEntryForIndex(j / 4);
String formattedValue = formatter.getBarLabel(entry);
// Modify the x, y position here to control where the
// text is. The "buffer" array gives the positions of
// the current bar (in pixels)
drawValue(c,
formattedValue,
buffer.buffer[j] + valueOffsetPlus,
buffer.buffer[j+1] - valueOffsetPlus,
dataSet.getValueTextColor(j / 2));
}
}
}
}
Example Use
To use the custom renderer, set it on the chart and modify the bar widths to leave room for the text.
HorizontalBarChart chart = findViewById(R.id.chart);
List<Float> values = Arrays.asList(3f, 2f, 2.2f);
List<Integer> colors = Arrays.asList(Color.CYAN, Color.RED, Color.GREEN);
List<String> labels = Arrays.asList("Pickled Horses", "Horses", "Pickles");
List<BarEntry> entries = new ArrayList<>();
for(int i = 0; i < values.size(); ++i) {
entries.add(new BarEntry(i+1, values.get(i), labels.get(i)));
}
BarDataSet ds = new BarDataSet(entries, "Values");
ds.setColors(colors);
ds.setValueTextSize(14f);
ds.setValueFormatter(new ValueFormatter() {
#Override
public String getBarLabel(BarEntry barEntry) {
return (String)barEntry.getData();
}
});
ds.setAxisDependency(YAxis.AxisDependency.RIGHT);
BarData data = new BarData(ds);
// Reduce bar width to leave room for text
data.setBarWidth(0.75f);
// Use the custom renderer
chart.setRenderer(new MyRenderer(chart, chart.getAnimator(), chart.getViewPortHandler()));
// Misc formatting & setup
chart.setData(data);
chart.getAxisLeft().setEnabled(false);
chart.getAxisRight().setAxisMinimum(0f);
chart.getAxisRight().setAxisMaximum(4f);
chart.getAxisRight().setGranularity(1f);
chart.getAxisRight().setTextSize(14f);
chart.getAxisRight().setDrawGridLines(false);
chart.getXAxis().setDrawLabels(false);
chart.getXAxis().setDrawGridLines(false);
chart.getXAxis().setAxisMaximum(3.6f);
chart.getDescription().setEnabled(false);
chart.getLegend().setEnabled(false);
chart.setBorderColor(Color.BLACK);
chart.setBorderWidth(1f);
chart.setDrawBorders(true);
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I have two linecharts in my application. One is with linear axis and the other is with logarithmic axis.
When I want so view only one series in the charts I set the other series and their data not visible so i can see only that series and i use the same method for visualize again all series too.
I've tried with threads but my problem is still there: in the chart with linear axis I don't have any problem but the logarithmic one doesn't update well the data.
Some nodes remain or are not shown, for example, it seems like the chart freeze while adding or removing visibility of data. Everything goes well only if I do a resize of the window and I don't understand why it is correlated. Here is my method to show only series with a certain name:
new Thread(() -> {
for (Series<Number, Number> series : lineChart.getData()) {
Platform.runLater(() -> {
if (series.getName().equals(name)) {
series.getNode().setVisible(!series.getNode().isVisible());
series.getData().forEach(data -> data.getNode().setVisible(series.getNode().isVisible()));
}
});
}
}).start();
Here is the class i use for the logarithmic axis:
public class LogarithmicAxis extends ValueAxis<Number> {
private Object currentAnimationID;
private final ChartLayoutAnimator animator = new ChartLayoutAnimator(this);
private final DoubleProperty logUpperBound = new SimpleDoubleProperty();
private final DoubleProperty logLowerBound = new SimpleDoubleProperty();
public LogarithmicAxis() {
super(0.0001, 1000);
bindLogBoundsToDefaultBounds();
}
public LogarithmicAxis(double lowerBound, double upperBound) {
super(lowerBound, upperBound);
validateBounds(lowerBound, upperBound);
bindLogBoundsToDefaultBounds();
}
public void setLogarithmicUpperBound(double d) {
double nd = Math.pow(10, Math.ceil(Math.log10(d)));
setUpperBound(nd == d ? nd * 10 : nd);
}
/**
* Binds logarithmic bounds with the super class bounds, consider the
* base 10 logarithmic scale.
*/
private void bindLogBoundsToDefaultBounds() {
logLowerBound.bind(new DoubleBinding() {
{
super.bind(lowerBoundProperty());
}
#Override
protected double computeValue() {
return Math.log10(lowerBoundProperty().get());
}
});
logUpperBound.bind(new DoubleBinding() {
{
super.bind(upperBoundProperty());
}
#Override
protected double computeValue() {
return Math.log10(upperBoundProperty().get());
}
});
}
/**
* Validates the bounds by throwing an exception if the values are not
* conform to the mathematics log interval: [0,Double.MAX_VALUE]
*
*/
private void validateBounds(double lowerBound, double upperBound) throws IllegalLogarithmicRangeException {
if (lowerBound < 0 || upperBound < 0 || lowerBound > upperBound) {
throw new IllegalLogarithmicRangeException(
"The logarithmic range should be in [0,Double.MAX_VALUE] and the lowerBound should be less than the upperBound");
}
}
/**
* It is used to get the list of minor tick marks position to display on the axis.
* It's based on the number of minor tick and the logarithmic formula.
*
*/
#Override
protected List<Number> calculateMinorTickMarks() {
List<Number> minorTickMarksPositions = new ArrayList<>();
return minorTickMarksPositions;
}
//Then, the calculateTickValues method
/**
* It is used to calculate a list of all the data values for each tick mark in range,
* represented by the second parameter. Displays one tick each power of 10.
*
*/
#Override
protected List<Number> calculateTickValues(double length, Object range) {
LinkedList<Number> tickPositions = new LinkedList<>();
if (range != null) {
double lowerBound = ((double[]) range)[0];
double upperBound = ((double[]) range)[1];
for (double i = Math.log10(lowerBound); i <= Math.log10(upperBound); i++) {
tickPositions.add(Math.pow(10, i));
}
if (!tickPositions.isEmpty()) {
if (tickPositions.getLast().doubleValue() != upperBound) {
tickPositions.add(upperBound);
}
}
}
return tickPositions;
}
/**
* The getRange provides the current range of the axis. A basic
* implementation is to return an array of the lowerBound and upperBound
* properties defined into the ValueAxis class.
*
*/
#Override
protected double[] getRange() {
return new double[]{
getLowerBound(),
getUpperBound()
};
}
/**
* The getTickMarkLabel is only used to convert the number value to a string
* that will be displayed under the tickMark.
*
*/
#Override
protected String getTickMarkLabel(Number value) {
NumberFormat formatter = NumberFormat.getInstance();
formatter.setMaximumIntegerDigits(10);
formatter.setMinimumIntegerDigits(1);
return formatter.format(value);
}
/**
* Updates the range when data are added into the chart.
* There is two possibilities, the axis is animated or not. The
* simplest case is to set the lower and upper bound properties directly
* with the new values.
*
*/
#Override
protected void setRange(Object range, boolean animate) {
if (range != null) {
final double[] rangeProps = (double[]) range;
final double lowerBound = rangeProps[0];
final double upperBound = rangeProps[1];
final double oldLowerBound = getLowerBound();
setLowerBound(lowerBound);
setUpperBound(upperBound);
if (animate) {
animator.stop(currentAnimationID);
currentAnimationID = animator.animate(
new KeyFrame(Duration.ZERO,
new KeyValue(currentLowerBound, oldLowerBound)
),
new KeyFrame(Duration.millis(700),
new KeyValue(currentLowerBound, lowerBound)
)
);
} else {
currentLowerBound.set(lowerBound);
}
}
}
#Override
public Number getValueForDisplay(double displayPosition) {
double delta = logUpperBound.get() - logLowerBound.get();
if (getSide().isVertical()) {
return Math.pow(10, (((displayPosition - getHeight()) / -getHeight()) * delta) + logLowerBound.get());
} else {
return Math.pow(10, (((displayPosition / getWidth()) * delta) + logLowerBound.get()));
}
}
#Override
public double getDisplayPosition(Number value) {
double delta = logUpperBound.get() - logLowerBound.get();
double deltaV = Math.log10(value.doubleValue()) - logLowerBound.get();
if (getSide().isVertical()) {
return (1. - ((deltaV) / delta)) * getHeight();
} else {
return ((deltaV) / delta) * getWidth();
}
}
/**
* Exception to be thrown when a bound value isn't supported by the
* logarithmic axis<br>
*
*/
public static class IllegalLogarithmicRangeException extends RuntimeException {
public IllegalLogarithmicRangeException(String message) {
super(message);
}
}
}
view only one series in the charts
One way to do this is to place one series in the chart and remove all others.
To do that, don't try to hide the nodes in other series, but, instead, remove other series from the data set, the charts will update automatically.
Turn off animation on the chart if you want an instant update instead of an animated one.
lineChart.setAnimated(false);
I've tried with threads
I don't advise using other threads unless you really need to.
Never access data associated with the active scene graph off of the JavaFX thread. This would include the line graph and its data.
To allow that to happen, run the logic which gets the lineChart data within the Platform.runlater() call, rather than accessing the lineChart.getData() call in your own thread as you have in your question.
new Thread(() -> {
Platform.runLater(() -> {
for (Series<Number, Number> series : lineChart.getData()) {
if (series.getName().equals(name)) {
series.getNode().setVisible(!series.getNode().isVisible());
series.getData().forEach(data -> data.getNode().setVisible(series.getNode().isVisible()));
}
}
});
}).start();
But then, if you do that, the thread and runLater calls seem pointless because you could just do everything inline:
for (Series<Number, Number> series : lineChart.getData()) {
if (series.getName().equals(name)) {
series.getNode().setVisible(!series.getNode().isVisible());
series.getData().forEach(data -> data.getNode().setVisible(series.getNode().isVisible()));
}
}
Example Code
I tried implementing a kind of "only include displayed series" strategy.
After implementing, I think that perhaps your original idea of hiding series by changing their visibility might be better.
It was a little bit trickier than I expected because when you only display a single series, it is colored by the default coloring scheme, where the colors are assigned by the sequential position of the series in the data. So, when you only display one series instead of many, the color of the series displayed actually changes, unless you override the default color scheme.
You can override that in a CSS style sheet, but then you need to change the style sheets for each selected series so that the series color stays constant, which is a pain.
There may be a nicer way of handling this. I thought you might just be able to set the DEFAULT_COLOR_ looked up color via setStyle in code, but I couldn't get that to work, so I just went with the pretty ugly style sheet code.
I also didn't integrate the logarithmic axis, because (as far as I can tell) the axis type should really have no bearing on this.
Another trick to this was that animated must be turned off for it to work, otherwise, the observer for the animated data will be fired while resetting the series data. It will think there is duplicated data series being added (which there isn't really, so that is a strange implementation quirk of the chart animation logic).
Anyway, for what it's worth, I provided the code I came up with.
If no toggles are set, it shows all series. If a toggle is set, it only shows the series corresponding to that toggle.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.Random;
public class SeriesSelectionApp extends Application {
private static final int NUM_SERIES = 3;
private static final int NUM_DATA_PER_SERIES = 10;
private static final int DATA_MIN_VALUE = 5;
private static final int DATA_MAX_VALUE = 10;
private static final String[] seriesColors = new String[] {
"red", "green", "blue"
};
private static final Random random = new Random(42);
#Override
public void start(Stage stage) {
ObservableList<XYChart.Series<Number, Number>> data = generateData();
LineChart<Number, Number> lineChart = new LineChart<>(
new NumberAxis(0, NUM_DATA_PER_SERIES, 1),
new NumberAxis(0, DATA_MAX_VALUE, 1)
);
lineChart.getData().setAll(data);
lineChart.setAnimated(false);
setDefaultChartSeriesColors(lineChart);
HBox controls = new HBox(10);
ToggleGroup seriesSelectionToggleGroup = new ToggleGroup();
for (int i = 0; i < NUM_SERIES; i++) {
ToggleButton showSeriesToggleButton = new ToggleButton("Series " + (i+1));
showSeriesToggleButton.setToggleGroup(seriesSelectionToggleGroup);
showSeriesToggleButton.setUserData(i+1);
controls.getChildren().add(showSeriesToggleButton);
}
seriesSelectionToggleGroup.selectedToggleProperty().addListener((observable, oldValue, selectedToggle) -> {
lineChart.getData().clear();
if (selectedToggle == null) {
lineChart.getData().addAll(data);
setDefaultChartSeriesColors(lineChart);
} else {
int selectedSeriesNum = (int) selectedToggle.getUserData();
lineChart.getData().add(data.get(selectedSeriesNum - 1));
setSpecificChartSeriesColor(lineChart, selectedSeriesNum);
}
});
VBox layout = new VBox(10, controls, lineChart);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private void setDefaultChartSeriesColors(LineChart<Number, Number> lineChart) {
lineChart.getStylesheets().setAll(
"""
data:text/css,
.default-color0.chart-line-symbol { -fx-background-color: red, white; }
.default-color1.chart-line-symbol { -fx-background-color: green, white; }
.default-color2.chart-line-symbol { -fx-background-color: blue, white; }
.default-color0.chart-series-line { -fx-stroke: red; }
.default-color1.chart-series-line { -fx-stroke: green; }
.default-color2.chart-series-line { -fx-stroke: blue; }
"""
);
}
private void setSpecificChartSeriesColor(LineChart<Number, Number> lineChart, int seriesNum) {
lineChart.getStylesheets().setAll(
"""
data:text/css,
.default-color0.chart-line-symbol { -fx-background-color: MY_COLOR, white; }
.default-color0.chart-series-line { -fx-stroke: MY_COLOR; }
""".replaceAll("MY_COLOR", getSeriesColor(seriesNum))
);
}
private String getSeriesColor(int seriesNum) {
return seriesColors[(seriesNum - 1) % seriesColors.length];
}
private ObservableList<XYChart.Series<Number, Number>> generateData() {
ObservableList<XYChart.Series<Number, Number>> allData = FXCollections.observableArrayList();
for (int seriesNum = 0; seriesNum < NUM_SERIES; seriesNum++) {
ObservableList<XYChart.Data<Number, Number>> seriesData = FXCollections.observableArrayList();
for (int x = 0; x < NUM_DATA_PER_SERIES; x++) {
int y = random.nextInt(DATA_MAX_VALUE - DATA_MIN_VALUE) + DATA_MIN_VALUE;
XYChart.Data<Number, Number> dataItem = new XYChart.Data<>(x, y);
seriesData.add(dataItem);
}
XYChart.Series<Number, Number> series = new XYChart.Series<>("Series " + (seriesNum + 1), seriesData);
allData.add(series);
}
return allData;
}
public static void main(String[] args) {
launch(args);
}
}
I'm developing an application that parses a text file and looks for times between certain sent actions, a simple visual aid in short.
My issue is that the sorting on the StackedBarCharts x-axis has gone awry, as shown by the image linked below;
Image of sorting issue
The relevant code for generating these charts;
public boolean updateBarChart(Tab t, DataHolder dock) {
Node n = t.getContent();
Node graph = n.lookup("#Graph");
StackedBarChart bc = (StackedBarChart) graph;
//Barchart
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
bc.setTitle("Summary");
bc.getData().clear();
bc.setLegendVisible(true);
bc.setCategoryGap(1);
xAxis.setTickLabelRotation(90);
ArrayList<String> tempArr = dock.getUniqueActionNumbers();
for(String s : tempArr)
{
bc.getData().add(dock.calculateIntervalsBetweenActions(s));
}
bc.getXAxis().setAutoRanging(true);
bc.getYAxis().setAutoRanging(true);
return true;
}
The code generating the series, where:
ConstantStrings are an ENUM of the constantly reocurring strings,
PairValue is a simple home brewed Pair made for a simple local caching system so I don't have search the entire data structure every time I want each instance of a specific value.
public XYChart.Series<String, Number> calculateIntervalsBetweenActions(String actionNumber)
{
XYChart.Series returnValue = new XYChart.Series();
returnValue.setName(actionNumber);
LocalTime lastTime = null;
TreeMap<Integer, Integer> listOfNumbers = new TreeMap<Integer, Integer>();
int maxVal = 0;
ArrayList<PairValue> temp = metaMap.get(ConstantStrings.RECIEVED_ACTION_NUMBER);
if (temp != null)
{
for( PairValue p : temp)
{
String s = dp.get(p.getNodePlace()).getTokens().get(p.getPointPlace()).getValue();
if (!s.equals(actionNumber))
continue;
if(lastTime != null)
{
LocalTime tempTime = LocalTime.parse(dp.get(p.getNodePlace()).getTimestamp());
int seconds = (int) lastTime.until(tempTime, SECONDS);
if(seconds > maxVal) maxVal = seconds;
Integer count = listOfNumbers.get(seconds);
listOfNumbers.put(seconds, (count == null) ? 1 : count + 1);
lastTime = tempTime;
}
else lastTime = LocalTime.parse(dp.get(p.getNodePlace()).getTimestamp());
}
//todo add filter so the user can choose what to ignore and not.
for(int i = 2; i <= maxVal; i++) {
Integer find = listOfNumbers.get(i);
if(find != null) {
XYChart.Data toAdd = new XYChart.Data(Integer.valueOf(i).toString(), find);
returnValue.getData().add(toAdd);
}
}
}
else Logger.getGlobal().warning("Could not find meta map for Recieved action numer, aborting");
return returnValue;
}
My suspicions lie in the order the Series are added, but that should not matter in my opinion, so my question stands; Is there any simple way to sort these values properly?
Found the solution after an extensive amount of banging my head against the wall of not understanding:
First of all, don't programatically skip adding any values to the original series. Add all 0-values ranging between the values you want to keep. This is for sorting purposes.
Remove all 0-values when you've finalized the adding of new data.
This is the snippet of code I've used to remove the 0-values, modify for your own purposes as you wish.
ObservableList<XYChart.Series> xys = bc.getData();
for(XYChart.Series<String,Number> series : xys) {
ArrayList<XYChart.Data> removelist = new ArrayList<>();
for(XYChart.Data<String,Number> data: series.getData()) {
if(data.getYValue().equals(0)) removelist.add(data);
}
series.getData().removeAll(removelist);
}
I write a plugin in ImageJ, and I need some idea.
I have a plugin which generate plots for every image in a stack. So if I have 4 image in a stack, it will generate 4 plot from a vector.
But I need to be one plot with 4 curve. Please help me. `
This is the code.
public void run(String arg){
openImage();
if (cancel==false){
options();
}
if (cancel==false){
for (int k=0;k<imp.getStackSize();k++){
imp.setSlice(k+1);
generateESFArray("ESF Plot",imp,roi);
generateLSFArray("LSF Plot",ESFArray);
calculateMax();
ESFArrayF=alignArray(ESFArray);
if (cancel==false){
LSFArrayF=alignArray(LSFArray);
}
if (cancel==false){
ESFVector=averageVector(ESFArrayF);
}
if (cancel==false){
LSFVector=averageVector(LSFArrayF);
int aura = (LSFVector.length * 2);
LSFDVector = new double [aura];
int j = 0;
int aura2 = (LSFVector.length);
for(i=0;i<(LSFDVector.length-3); i++){
if(i % 2 == 0) {
LSFDVector[i]= LSFVector[j];
j=j+1;
}else {
LSFDVector[i]= ((0.375*LSFVector[(j-1)]) + (0.75*LSFVector[(j)]) - (0.125*LSFVector[(j+1)]));
}
}
LSFDVector[i] = ((LSFVector[j-1] + LSFVector[j])*0.5);
LSFDVector[i+1] = LSFVector[j];
LSFDVector[i+2] = LSFVector[j];
int indexMax = 0;
double valorMax = LSFDVector[0];
for(int i=0;i<LSFDVector.length;i++){
if(valorMax < LSFDVector[i]){
indexMax = i;
valorMax = LSFDVector[i];
}
}
i=indexMax;
LSFDVector[i-1]=((LSFDVector[i-2] + LSFDVector[i])*0.5);
MTFVector=fftConversion(LSFDVector, "MTF");
Max=obtenerMax();
SPPVector=fftConversion(Max,"SPP");
LSFArrayF=alignArray(LSFArray);
if (MTFButton.isSelected()){
generatePlot (MTFVector,"MTF");
...
}
void generatePlot(double[] Vector, String plot){
double[]xValues;
String ejeX="pixel";
String ejeY="";
String allTitle="";
ImageProcessor imgProc;
xValues=calculateXValues(Vector,plot);
//plot titles
if (plot=="ESF"){
ejeY="Grey Value";
...
allTitle=plot + "_" + title;
plotResult = new Plot(allTitle, ejeX, ejeY, xValues, Vector);
//plot limits
if (plot=="ESF"){
plotResult.setLimits(1,Vector.length,0,yMax);
}
plotResult.draw();
plotResult.show();
}
`
The ij.gui.Plot class has an addPoints method allowing you to add multiple data series to a plot. The Groovy script below illustrates its usage. Just paste the code into ImageJ's script editor, choose Language > Groovy and press Run to try it.
import ij.gui.Plot
plot = new Plot("Multiple Line Plot", "x values", "y values", (double[])[0,1,2,3,4], (double[])[0.1,0.3,0.5,0.6,0.7])
plot.addPoints((double[])[0,1,2,3,4], (double[])[0.2,0.15,0.1,0.05,0.05], Plot.LINE)
plot.setLimits(0, 4, 0, 1)
plot.draw()
plot.show()
For any further questions regarding the usage of the ImageJ API, you might get better help on the ImageJ forum.
I was wondering if there is a straightforward way to display line numbers with StyledText text field - even if lines are wrapped. I'm using it in my application and if content gets to big, some line numbers would be nice.
Thank you.
The key is org.eclipse.swt.custom.Bullet. It's basically a symbol (or in our case a number) you can add to the beginning of a line.
//text is your StyledText
text.addLineStyleListener(new LineStyleListener()
{
public void lineGetStyle(LineStyleEvent e)
{
//Set the line number
e.bulletIndex = text.getLineAtOffset(e.lineOffset);
//Set the style, 12 pixles wide for each digit
StyleRange style = new StyleRange();
style.metrics = new GlyphMetrics(0, 0, Integer.toString(text.getLineCount()+1).length()*12);
//Create and set the bullet
e.bullet = new Bullet(ST.BULLET_NUMBER,style);
}
});
This is my working implementation.
styledText.addLineStyleListener(new LineStyleListener() {
#Override
public void lineGetStyle(LineStyleEvent event) {
// Using ST.BULLET_NUMBER sometimes results in weird alignment.
//event.bulletIndex = styledText.getLineAtOffset(event.lineOffset);
StyleRange styleRange = new StyleRange();
styleRange.foreground = Display.getCurrent().getSystemColor(SWT.COLOR_GRAY);
int maxLine = styledText.getLineCount();
int bulletLength = Integer.toString(maxLine).length();
// Width of number character is half the height in monospaced font, add 1 character width for right padding.
int bulletWidth = (bulletLength + 1) * styledText.getLineHeight() / 2;
styleRange.metrics = new GlyphMetrics(0, 0, bulletWidth);
event.bullet = new Bullet(ST.BULLET_TEXT, styleRange);
// getLineAtOffset() returns a zero-based line index.
int bulletLine = styledText.getLineAtOffset(event.lineOffset) + 1;
event.bullet.text = String.format("%" + bulletLength + "s", bulletLine);
}
});
styledText.addModifyListener(new ModifyListener() {
#Override
public void modifyText(ModifyEvent e) {
// For line number redrawing.
styledText.redraw();
}
});
Note that the possible overhead of syntax highlighting recalculation when calling redraw() is likely to be acceptable, because lineGetStyle() are only called with lines currently on screen.
I believe that using a LineStyleListener should work. Something along the lines of:
styledText.addLineStyleListener(
new LineStyleListener() {
#Override
public void lineGetStyle(LineStyleEvent event) {
String line = event.lineText;
int lineNumber = event.lineOffset;
// Do stuff to add line numbers
}
}
);
This is a way to use bullets that updates the numbers when the content changes:
text.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent event) {
int maxLine = text.getLineCount();
int lineCountWidth = Math.max(String.valueOf(maxLine).length(), 3);
StyleRange style = new StyleRange();
style.metrics = new GlyphMetrics(0, 0, lineCountWidth * 8 + 5);
Bullet bullet = new Bullet(ST.BULLET_NUMBER, style);
text.setLineBullet(0, text.getLineCount(), null);
text.setLineBullet(0, text.getLineCount(), bullet);
}
});
As a side-note for colouring the line numbers:
Device device = Display.getCurrent();
style.background = new Color(device, LINE_NUMBER_BG);
style.foreground = new Color(device, LINE_NUMBER_FG);
where LINE_NUMBER_BG and LINE_NUMBER_FG might be a RGB object such as:
final RGB LINE_NUMBER_BG = new RBG(160, 80, 0); // brown
final RGB LINE_NUMBER_FG = new RGB(255, 255, 255); // white
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.