private void computeSpline(final ChartModel inputModel, DefaultChartModel splineModel) {
    splineModel.clearPoints();
    // Create some double arrays and populate them from the chart model
    // TODO: xs and ys are assumed to be sorted in the call to computeSplineSlopes, which is not
    // guaranteed
    final int pointCount = inputModel.getPointCount();
    double xs[] = new double[pointCount];
    double ys[] = new double[pointCount];
    for (int i = 0; i < pointCount; i++) {
      Chartable p = inputModel.getPoint(i);
      xs[i] = p.getX().position();
      ys[i] = p.getY().position();
    }

    double[] slopes = SplineEngine.computeSplineSlopes(pointCount - 1, xs, ys);

    // Number of divisions in each segment
    int nDivs = 20;
    double rx, ry;

    for (int seg = 0; seg < pointCount - 1; seg++) {
      double xstep = (xs[seg + 1] - xs[seg]) / nDivs;
      for (int i = 0; i <= nDivs; i++) {
        rx = xs[seg] + i * xstep;
        ry =
            SplineEngine.splineEval(
                rx, xs[seg], xs[seg + 1], ys[seg], ys[seg + 1], slopes[seg], slopes[seg + 1]);
        splineModel.addPoint(rx, ry);
      }
    }
  }
  private JPanel createDemo() {
    upper = new DefaultChartModel();
    lower = new DefaultChartModel();
    splineChartModel = new DefaultChartModel();
    chart = new Chart();
    demoPanel = new JPanel();
    demoPanel.setPreferredSize(new Dimension(500, 500));
    demoPanel.setLayout(new BorderLayout());

    assert xPoints.length == lowerPoints.length && lowerPoints.length == upperPoints.length;

    for (int i = 0; i < xPoints.length; i++) {
      lower.addPoint(xPoints[i], lowerPoints[i]);
      upper.addPoint(xPoints[i], upperPoints[i]);
    }

    Range<?> xRange = lower.getXRange();
    Range<Double> yRange = new CombinedNumericRange().add(lower.getYRange()).add(upper.getYRange());

    Axis xAxis = new Axis(xRange);
    Axis yAxis = new Axis(yRange);

    chart.setXAxis(xAxis);
    chart.setYAxis(yAxis);

    chart.setShadowVisible(false);

    PointStyle pointStyle = new PointStyle(Color.green.darker(), PointShape.SQUARE);
    ChartStyle chartStyle = new ChartStyle();
    chartStyle.setPointStyle(pointStyle);
    chartStyle.setLinesVisible(false);
    chartStyle.setPointsVisible(true);

    chart.addModel(lower, chartStyle);
    chart.addModel(upper, chartStyle);

    // ChartStyle averageChartStyle = new ChartStyle(Color.blue, false, true);
    ChartStyle splineChartStyle = new ChartStyle();
    splineChartStyle.setLinesVisible(true);
    LineStyle lineStyle = new LineStyle();
    lineStyle.setColor(Color.blue);
    lineStyle.setWidth(1);
    splineChartStyle.setLineStyle(lineStyle);

    final AnnotatedChartModel[] baseModels = new AnnotatedChartModel[] {lower, upper};

    averageModel = new AverageChartModel(baseModels);
    // chart.addModel(averageModel, averageChartStyle);

    chart.addModel(splineChartModel, splineChartStyle);
    computeSpline(averageModel, splineChartModel);

    demoPanel.add(chart, BorderLayout.CENTER);

    chart.addMouseListener(
        new MouseListener() {
          public void mouseClicked(MouseEvent e) {

            if (e.getClickCount() == 2 && nearestModel != null && _nearestPoint != null) {
              ((DefaultChartModel) nearestModel).removePoint(_nearestPoint);
              ((DefaultChartModel) nearestModel).update();
              computeSpline(averageModel, splineChartModel);
            }
          }

          public void mouseEntered(MouseEvent e) {}

          public void mouseExited(MouseEvent e) {}

          public void mousePressed(MouseEvent e) {
            Point p = e.getPoint();
            Double minDistance = null;
            for (ChartModel model : baseModels) {
              PointSelection nearest = chart.nearestPoint(p, model);
              Chartable chartable = nearest.getSelected();
              double distance = nearest.getDistance();
              if (minDistance == null || distance < minDistance) {
                _nearestPoint = chartable;
                nearestModel = model;
                minDistance = distance;
              }
            }
          }

          public void mouseReleased(MouseEvent e) {}
        });
    chart.addMouseMotionListener(
        new MouseMotionListener() {
          public void mouseDragged(MouseEvent e) {
            if (nearestModel != null) {
              Point p = e.getPoint();
              Point2D realPoint = chart.calculateUserPoint(p);
              if (realPoint != null) {
                ((ChartPoint) _nearestPoint).setY(new RealPosition(realPoint.getY()));
              }
              // TODO: Should not have to call an update() method!
              ((DefaultChartModel) nearestModel).update();
              computeSpline(averageModel, splineChartModel);
            }
          }

          public void mouseMoved(MouseEvent e) {}
        });
    return demoPanel;
  }
  public JPanel createDemo() {
    model = new DefaultChartModel();
    demoPanel = new JPanel();
    Axis xAxis = new CategoryAxis<Country>(countries);
    xAxis.setLabel("Countries");
    Axis yAxis = new Axis(new NumericRange(0, 25));
    yAxis.setLabel("Numbers");
    chart = new Chart();
    chart.setXAxis(xAxis);
    chart.setYAxis(yAxis);
    Font titleFont = UIManager.getFont("Label.font").deriveFont(Font.BOLD, 14f);
    chart.setTitle(
        new AutoPositionedLabel("Chart with Categorical X Values", Color.blue, titleFont));
    chart.setVerticalGridLinesVisible(false);
    chart.setShadowVisible(true);
    model
        .addPoint(usa, 22)
        .addPoint(uk, 18)
        .addPoint(france, 13.5)
        .addPoint(germany, 12)
        .addPoint(russia, 8)
        .addPoint(china, 7);
    Color green = new Color(0, 170, 0);
    ChartStyle style = new ChartStyle(green, true, true);
    style.setLineWidth(6);
    style.setPointSize(20);
    chart.addModel(model, style).setPointRenderer(new SphericalPointRenderer());
    ChartStyle redHighlightStyle =
        new ChartStyle(new Color(200, 0, 0), PointShape.DISC, 20).withPointsAndLines();
    redHighlightStyle.setLineWidth(6);
    redHighlightStyle.setLineColor(green);
    chart.setHighlightStyle(redHighlight, redHighlightStyle);
    demoPanel.setLayout(new BorderLayout());
    demoPanel.add(chart, BorderLayout.CENTER);
    chart.addMouseMotionListener(
        new MouseMotionListener() {
          // Allow the user to drag _points in the vertical direction
          public void mouseDragged(MouseEvent e) {
            rollover(e);
            if (highlighted != null) {
              Point p = e.getPoint();
              Point2D userPoint = chart.calculateUserPoint(p);
              if (userPoint != null) {
                highlighted.setY(new RealPosition(userPoint.getY()));
              }
              chart.repaint();
            }
          }

          // Add a rollover effect
          public void mouseMoved(MouseEvent e) {
            rollover(e);
          }

          private void rollover(MouseEvent e) {
            Point p = e.getPoint();
            PointSelection selection = chart.nearestPoint(p, model);
            if (highlighted != null) {
              highlighted.setHighlight(null);
            }
            Chartable selected = selection.getSelected();
            Point2D selectedCoords =
                new Point2D.Double(selected.getX().position(), selected.getY().position());
            Point dp = chart.calculatePixelPoint(selectedCoords);
            // Only activate the rollover effect when within 50 pixels of a point
            if (p.distance(dp) < 50) {
              highlighted = (ChartPoint) selection.getSelected();
              highlighted.setHighlight(redHighlight);
              ChartCategory<?> x = (ChartCategory<?>) selected.getX();
              chart.setToolTipText(
                  String.format("%s : %.1f", x.getName(), selected.getY().position()));
            } else {
              chart.setToolTipText(null);
            }
            chart.repaint();
          }
        });
    chart.addMouseListener(
        new MouseAdapter() {
          @Override
          public void mouseExited(MouseEvent e) {
            if (highlighted != null) {
              highlighted.setHighlight(null);
              chart.repaint();
            }
          }
        });
    demoPanel.setPreferredSize(new Dimension(500, 500));
    return demoPanel;
  }