Принудительное указание определенного числа и набора меток оси домена в Java JFreeChart

Я пытаюсь написать метод для создания простого графика нормального распределения в JFreeChart и сохранения его в виде файла. Вот пример выходного изображения, которое в значительной степени соответствует тому, что мне нужно введите здесь описание изображения

Обратите внимание, что на оси x ровно 9 делений. Центральный — это среднее значение распределения, а остальные галочки указывают на стандартные отклонения. Каждому стандартному отклонению от среднего соответствует один тик.

Вот пример другой диаграммы, показывающей нормальное распределение со средним значением 7 и стандартным отклонением 5 без каких-либо других изменений кода.введите здесь описание изображения

Это не то, чего я хочу. Внезапно осталось всего 8 галочек, а в центре нет галочки, чтобы отметить среднее значение. Похоже, что JFreeChart хочет использовать только красивые круглые числа вместо нечетных 7 в качестве центральной галочки.

Я пробовал читать другие вопросы StackOverflow о принудительной маркировке осей, но, похоже, все остальные хотят сделать это с той или иной формой дат. Было бы полезно, если бы я мог просто указать 9 точных значений для размещения на оси вместо того, чтобы они генерировались автоматически, но я не знаю, как это сделать.

Есть и еще одна проблема. Если вы посмотрите на кривую по бокам графика, она обрезается ниже рамки графика и упирается в деления. Я хочу добавить отступ между кривой и делениями. Я пытался использовать что-то вроде plot.getRangeAxis().setRange(-0.01, 0.09);, но столкнулся со странной проблемой, когда оказалось, что высота нормального распределения зависит от его ширины. Большие средние значения и стандартные отклонения приводят к тому, что это с треском ломается. (Это не имеет никакого смысла с точки зрения статистики, и я начинаю сомневаться в этом нормальном методе распределения.)

В любом случае мне в основном нужен способ заставить диаграмму (а) добавить отступы вокруг кривой и (б) использовать ровно девять отметок, соответствующих среднему и четырем стандартным отклонениям.

Вот мой текущий код, который в основном был украден в Интернете и обрезан до того, что казалось действительно необходимым:

static double mean = 7.0, sd = 5.0;
static Color line = new Color(0x6AA2A3);
static Color grey = new Color(0x555555);

public static void main(String[] args) throws IOException {
  // Create the normal distribution
  double minX = mean - (4 * sd), maxX = mean + (4 * sd);
  Function2D normal = new NormalDistributionFunction2D(mean, sd);
  XYDataset dataset = DatasetUtils.sampleFunction2D(normal, minX, maxX, 100, "Normal");

  JFreeChart chart = ChartFactory.createXYLineChart(null, null, null, dataset, PlotOrientation.VERTICAL, false, false, false);
  chart.setBorderVisible(true);

  // Create and format the Plot
  XYPlot plot = chart.getXYPlot();
  plot.setBackgroundPaint(Color.WHITE);
  plot.getRangeAxis().setVisible(false);
  plot.setOutlineVisible(false);

  // Format the X axis to look pretty
  NumberAxis domain = (NumberAxis) plot.getDomainAxis();
  domain.setRange(minX, maxX);
  domain.setAxisLineVisible(false);
  domain.setAutoRangeStickyZero(false);
  domain.setTickUnit(new NumberTickUnit(sd));
  domain.setTickLabelFont(new Font("Roboto", Font.PLAIN, 20));
  domain.setTickLabelPaint(grey);
  domain.setTickMarkStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
  domain.setTickMarkInsideLength(8);
  domain.setTickMarkPaint(grey);

  // Create a renderer to turn the chart into an image
  XYLineAndShapeRenderer render = (XYLineAndShapeRenderer) plot.getRenderer(0);
  render.setSeriesStroke(0, new BasicStroke(4));
  render.setSeriesPaint(0, line);

  // Output the final image
  chart.setPadding(new RectangleInsets(5, 20, 20, 20));
  BufferedImage image = chart.createBufferedImage(600,400);

  File outFile = new File("graph.png");
  outFile.createNewFile();
  ImageIO.write(image, "png", outFile);
}

person kviLL    schedule 05.01.2021    source источник


Ответы (1)


для запроса а), plot.setAxisOffset(new RectangleInsets(5,5,5,5)); должно помочь. Для запроса б) общая рекомендация состоит в том, чтобы переопределить refreshTicks(Graphics2D g2, AxisState state,Rectangle2D dataArea,RectangleEdge edge) класса ValueAxis и вернуть подходящий список тиков. Хотя это может показаться немного пугающим, это не так, если ваша логика проста. Вы можете попробовать просто добавить NumberTick для среднего значения в автоматически сгенерированный список тиков.

person Soltub    schedule 05.01.2021
comment
Спасибо, это сработало отлично. Мне потребовалось некоторое время, чтобы понять, что я не могу просто привести ValueAxis к моему новому классу CustomAxis с помощью метода переопределения, потому что я получу ClassCastExceptions. Мне пришлось создать новый экземпляр моего класса CustomAxis, а затем использовать plot.setDomainAxis() для его применения. Во всяком случае, это работает сейчас. Что касается решения для запроса a, оно технически обеспечивает интервал, который я искал, но мне все еще не нравится, как линия обрывается, когда она опускается до нуля. Я думаю, я могу жить с этим, хотя - person kviLL; 06.01.2021
comment
Отсечение строки происходит из-за того, что Graphics2D обрезается до dataArea. Это отсечение сделано в XYPlot.draw. Чтобы избежать этого, вы можете расширить XYLineAndShapeRenderer и переопределить метод drawItem. В вашем переопределенном методе сохраните текущий клип Graphics2D, затем установите для клипа значение null, вызовите super.drawItem и, наконец, восстановите сохраненный клип. Не забудьте установить новый рендерер как рендерер в файле XYPlot. - person Soltub; 06.01.2021