A host of questions, two easy and one very hard

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
apaz
Posts: 3
Joined: Fri May 31, 2019 6:52 am
antibot: No, of course not.

A host of questions, two easy and one very hard

Post by apaz » Mon Jun 03, 2019 4:08 am

I'm trying to write a wrapper library that will let me use JFreeChart easier with my Discord bot. It's almost completely done, I've just run into a couple of snags.

Here's a google drive folder with some images of the charts that I'm working on.
https://drive.google.com/drive/folders/ ... sp=sharing

My questions are, in order of increasing difficulty:

If you look at normalchartexample.png, you can see that the vertical dashed line in the center is a different color than everything else. It seems trivial, but how do I change that? I styled the graph with the following, if it helps.

Code: Select all

if (plot instanceof XYPlot) {
	XYPlot xyp = (XYPlot) plot;
	xyp.setDomainZeroBaselineVisible(true);
	xyp.setRangeZeroBaselineVisible(true);
	xyp.setDomainPannable(true);
	xyp.setRangePannable(true);

	// Style axes
	NumberAxis rangeAxis = (NumberAxis) xyp.getRangeAxis();
	rangeAxis.setLabelPaint(Color.WHITE);
	rangeAxis.setAxisLinePaint(Color.LIGHT_GRAY);
	rangeAxis.setTickLabelPaint(Color.LIGHT_GRAY);
	rangeAxis.setTickLabelsVisible(true);

	ValueAxis domainAxis = xyp.getDomainAxis();
	domainAxis.setLabelPaint(Color.WHITE);
	domainAxis.setAxisLinePaint(Color.LIGHT_GRAY);
	domainAxis.setTickLabelPaint(Color.LIGHT_GRAY);
	domainAxis.setTickLabelsVisible(true);
	domainAxis.setLowerMargin(0.0);
	domainAxis.setUpperMargin(0.0);
			
	// TODO style integral (This will be done after I obtain the answer to a more difficult question)

	XYLineAndShapeRenderer r = (XYLineAndShapeRenderer) xyp.getRenderer();
	r.setDrawSeriesLineAsPath(true);

	// Style annotations
	@SuppressWarnings("unchecked")
	List<XYTextAnnotation> annotations = xyp.getAnnotations();
	for (int i = 0; i < annotations.size(); i++) {
		XYTextAnnotation a = (XYTextAnnotation) annotations.get(i);
		a.setTextAnchor(TextAnchor.HALF_ASCENT_CENTER);
		a.setBackgroundPaint(paintProgression[i % paintProgression.length]);
		a.setOutlinePaint(Color.LIGHT_GRAY);
	}
}
How do I style the stroke of a series on a line chart? I want to be able to show points for individual data points that cannot necessarily be connected by a line. I tried the following, to no avail.

Code: Select all

if (plot instanceof CategoryPlot) {
	CategoryPlot cp = (CategoryPlot) plot;
			
	if (cp.getRenderer() instanceof DefaultCategoryItemRenderer) {
		DefaultCategoryItemRenderer renderer = (DefaultCategoryItemRenderer) cp.getRenderer();
		int i = 0;
		boolean cont = true;
		while (cont) {
			if (renderer.getSeriesStroke(i) == null) {
				cont = false;
				continue;
			}
			renderer.setSeriesStroke(i, new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
			System.out.println("Styled #" + i);
			i++;
		}
	}
}
"Styled #" + i is never printed, and therefore I can never get a series stroke, never mind set it. Maybe the key I'm supposed to be using to get access to the series is something else? I thought that using get to figure out how many series that there are was a better way to do it than using exceptions as my control structure.




My last and most difficult question, which may get its own separate post later is: I would like to add a fill below my normal distribution chart. This question has been asked once in the past, but I've blown a few hours on trying to understand what the solution actually was, and I've come up short. I want to fill an area from an arbitrary beginning point to an arbitrary ending point on the domain, and I want to be able to do it any number of times with different values, preferably in a way such that you can't tell if they're overlapping, yet does not obscure any other series beneath it. I also want to be sure that everything is rendered in the correct order.

Another consideration is that this operation should preferably be fast, since I need to generate the graph on the fly, save, and send the file.

If it's helpful, I've already implemented a getHeightAt() function to help me place labels that finds the height of the bell curve at a specific x value. The equation can be found here: http://davidmlane.com/hyperstat/A25726.html

Code: Select all

public double getHeightAt(Double x) {
	Double coefficient = 1.0 / Math.sqrt(2.0 * Math.PI * getStandardDeviation() * getStandardDeviation());
	Double exponentnumerator = -1.0 * (x - getMean()) * (x - getMean());
	Double exponentdenominator = 2 * getStandardDeviation() * getStandardDeviation();
	return coefficient * (Math.pow(Math.E, exponentnumerator/exponentdenominator));
}
I'm currently using an XYLineAndShapeRenderer and one dataset for all the bell curves on the graphs. I'm not quite sure where to start. Shape is in the name, but I'm not quite sure if generating one polygon with per fill area is the most efficient way to go about this. If you know of a solution, I'm all ears, especially if it doesn't involve generating a polygon with 10000+ points.


david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Re: A host of questions, two easy and one very hard

Post by david.gilbert » Mon Jul 01, 2019 2:31 am

The first issue is cause by the drawing of the zero baseline for the domain axis - this is a vertical line drawn at the value 0 on the x-axis. You have this line in your code:

Code: Select all

xyp.setDomainZeroBaselineVisible(true);
Remove it and the baseline won't be drawn anymore and that should solve your problem.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Re: A host of questions, two easy and one very hard

Post by david.gilbert » Mon Jul 01, 2019 2:36 am

For your second question, could it be that the renderer is not an instance of DefaultCategoryItemRenderer?
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

apaz
Posts: 3
Joined: Fri May 31, 2019 6:52 am
antibot: No, of course not.

Re: A host of questions, two easy and one very hard

Post by apaz » Mon Jul 01, 2019 10:11 pm

I removed the offending line, and it's fixed now. All good.

The renderer was probably an instance of DefaultCategoryItemRenderer, but I replaced the code with this, and the resulting graph is still the same.

I have a main function testing it with a BarChart and a Linechart. Hello is printed twice as expected, and never does it print "Styled #" + i. Therefore I'm having trouble getting a series stroke. As you can tell from my google drive link from the initial post, there are indeed Series. I just can't access them.

Code: Select all

// Barchart and LineChart
if (plot instanceof CategoryPlot) {
	CategoryPlot cp = (CategoryPlot) plot;
	
	... style CategoryPlot ...
	
	CategoryItemRenderer renderer = cp.getRenderer();
			
	System.out.println("Hello");
			
	int i = 0;
	boolean cont = true;
	while (cont) {
		if (renderer.getSeriesStroke(i) == null) {
			cont = false;
			continue;
		}
		renderer.setSeriesStroke(i, new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
		System.out.println("Styled #" + i);
		i++;
	}
	
	... style Axes ...
	
}

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Re: A host of questions, two easy and one very hard

Post by david.gilbert » Tue Jul 02, 2019 5:53 am

The getSeriesStroke() method will not return null if the autoPopulateSeriesStroke flag is set to true, which I think is the default:

http://www.jfree.org/jfreechart/api/jav ... e-boolean-
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

apaz
Posts: 3
Joined: Fri May 31, 2019 6:52 am
antibot: No, of course not.

Re: A host of questions, two easy and one very hard

Post by apaz » Thu Jul 04, 2019 4:57 am

The documentation for getSeriesStroke says that it should never return null. I even manually set that flag on the renderer, and it's still returning null. Not sure what's up with that.

So eventually decided "Screw it, I'll just set the first fifty." I still don't know how to get how many series that there are, but I guess that I can reasonably expect there to be less than fifty. That didn't work either. It printed out "Styled #" + i as expected, but the stroke isn't being set. The graphs that I'm saving are coming out the same, with invisible points on them.

Is there something wrong with my renderer? I'm just going to paste everything relevant to how I created my LineChart down below. The method saveChart also styles the chart with a custom theme before it writes it to file. I don't think that I'm messing with the renderer anywhere else.

Create Linechart:

Code: Select all

public void createChart(File file, String chartTitle, String subtitle) throws IOException {
	if (file == null || chartTitle == null || subtitle == null) {
		throw new IllegalArgumentException("No arguments may be null.");
	}

	// x and y axis labels are class variables with default value "(x | y)-Axis Label"
	chart = ChartFactory.createLineChart(chartTitle, xAxisLabel, yAxisLabel, buildDataset());
	chart.addSubtitle(new TextTitle(subtitle));

	AbstractRenderer rend = (AbstractRenderer) chart.getCategoryPlot().getRenderer(); 
	rend.setAutoPopulateSeriesStroke(true);
		
	saveChart(file);
}
Theme:

Code: Select all

public class DiscordTheme implements ChartTheme {

	public DiscordTheme() {

	}

	static Paint[] paintProgression = DefaultDrawingSupplier.DEFAULT_PAINT_SEQUENCE;

	public void apply(JFreeChart chart) {
		// Set across all
		StandardChartTheme theme = new StandardChartTheme("theme");
		theme.apply(chart);
		// Discord chat color
		chart.setBackgroundPaint(new Color(54, 57, 62));

		// Style title
		TextTitle t = chart.getTitle();
		if (t != null) {
			t.setHorizontalAlignment(HorizontalAlignment.CENTER);
			t.setPaint(Color.WHITE);
			t.setFont(new Font("Arial", Font.BOLD, 26));
		}

		// Style subtitle(s)
		for (int i = 0; i < chart.getSubtitleCount(); i++) {
			Title subtitle = chart.getSubtitle(i);
			if (subtitle == null) {
				continue;
			}
			if (subtitle instanceof TextTitle) {
				TextTitle st = (TextTitle) subtitle;
				st.setTextAlignment(HorizontalAlignment.CENTER);
				st.setFont(new Font("TimesRoman", Font.BOLD | Font.ITALIC, 18));
			}
		}

		// Style legend
		LegendTitle legend = chart.getLegend();
		if (legend != null) {
			legend.setBackgroundPaint(null);
			legend.setItemPaint(Color.LIGHT_GRAY);
		}

		// Style plot
		Plot plot = chart.getPlot();
		plot.setBackgroundPaint(null);
		plot.setOutlineVisible(false);

		// PieChart
		if (plot instanceof PiePlot) {
			...
		}
		// Barchart and LineChart
		else if (plot instanceof CategoryPlot) {
			CategoryPlot cp = (CategoryPlot) plot;
			cp.setDomainGridlinePaint(Color.WHITE);
			cp.setRangeGridlinePaint(Color.WHITE);

			
			CategoryItemRenderer renderer = cp.getRenderer();
			
			System.out.println("Hello");
			
			int i = 0;
			boolean cont = true;
			while (cont) {
				if (renderer.getSeriesStroke(i) == null) {
					cont = false;
					continue;
				}
				renderer.setSeriesStroke(i, new BasicStroke(2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
				System.out.println("Styled #" + i);
				i++;
			}
			
			
			// Style axes
			NumberAxis rangeAxis = (NumberAxis) cp.getRangeAxis();
			rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
			rangeAxis.setLabelPaint(Color.WHITE);
			rangeAxis.setAxisLinePaint(Color.LIGHT_GRAY);
			rangeAxis.setTickLabelPaint(Color.LIGHT_GRAY);

			CategoryAxis domainAxis = cp.getDomainAxis();
			domainAxis.setLabelPaint(Color.WHITE);
			domainAxis.setAxisLinePaint(Color.LIGHT_GRAY);
			domainAxis.setTickLabelPaint(Color.LIGHT_GRAY);
		}
		// NormalDistributionChart
		else if (plot instanceof XYPlot) {
			...
		}
	}
}


Locked