1   package org.djutils.swing.multislider;
2   
3   import org.djutils.exceptions.Throw;
4   
5   /**
6    * LinearMultiSlider implements a slider with multiple thumbs and liner values based on the class Number. The slider returns
7    * instances of a given type. The MultiSlider is implemented by drawing a number of sliders on top of each other using an Swing
8    * {@code OverlayManager}, and passing the mouse events from a glass pane on top to the correct slider(s). The class is a
9    * {@code ChangeListener} to listen to the changes of individual sliders underneath.
10   * <p>
11   * Several models exist to indicate whether thumbs can pass each other or not, or be on top of each other or not.
12   * </p>
13   * <p>
14   * Copyright (c) 2024-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
15   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
16   * distributed under a three-clause BSD-style license, which can be found at
17   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
18   * </p>
19   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
20   * @param <T> The type of the labels for the multislider
21   */
22  public abstract class LinearMultiSlider<T extends Number & Comparable<T>> extends AbstractMultiSlider<T>
23  {
24      /** */
25      private static final long serialVersionUID = 20241124L;
26  
27      /** the lowest value of the linear scale. */
28      private T min;
29  
30      /** the highest value of the linear scale. */
31      private T max;
32  
33      /** the number of ticks of the linear scale. The number of intervals in {@code unitTicks - 1}. */
34      private int unitTicks;
35  
36      /**
37       * Creates a horizontal slider using the specified interval, number of steps, and initial values.
38       * @param min the lowest value of the linear scale
39       * @param max the highest value of the linear scale
40       * @param unitTicks the number of ticks on the linear scale; note that when you need 100 intervals (0-100), ticks should be
41       *            101. When you need 99 intervals (1-100), ticks should be 100.
42       * @param initialValues the initial values of the slider
43       * @throws IllegalArgumentException if the initial values are not part of the scale, or if the number of thumbs is 0, or
44       *             when the values are not in increasing scale order (which is important for restricting passing and overlap)
45       */
46      @SafeVarargs
47      public LinearMultiSlider(final T min, final T max, final int unitTicks, final T... initialValues)
48      {
49          this(min, max, unitTicks, true, initialValues);
50      }
51  
52      /**
53       * Creates a horizontal or vertical slider using the specified min, max and initial values.
54       * @param horizontal the orientation of the slider; true for horizontal, false for vertical
55       * @param min the lowest value of the linear scale
56       * @param max the highest value of the linear scale
57       * @param unitTicks the number of ticks on the linear scale; note that when you need 100 intervals (0-100), ticks should be
58       *            101. When you need 99 intervals (1-100), ticks should be 100.
59       * @param initialValues the initial values of the slider.
60       * @throws IllegalArgumentException if the initial values are not part of the scale, or if the number of thumbs is 0, or
61       *             when the values are not in increasing scale order (which is important for restricting passing and overlap)
62       */
63      @SafeVarargs
64      public LinearMultiSlider(final T min, final T max, final int unitTicks, final boolean horizontal, final T... initialValues)
65      {
66          super(0, unitTicks - 1, horizontal, intArray(min, max, unitTicks, initialValues));
67          this.min = min;
68          this.max = max;
69          this.unitTicks = unitTicks;
70          setLabelTable(createStandardLabels(1));
71      }
72  
73      /**
74       * Make an int array from the initial values given the linear scale.
75       * @param min the lowest value of the linear scale
76       * @param max the highest value of the linear scale
77       * @param unitTicks the number of steps of the linear scale
78       * @param initialValues the initial values of the slider.
79       * @param <T> the type of objects for the categorial scale
80       * @return an int array with the index vales of the initial values on the scale
81       * @throws IllegalArgumentException if the initial values are not part of the scale, or when the scale has duplicate values
82       */
83      @SafeVarargs
84      private static <T extends Number & Comparable<T>> int[] intArray(final T min, final T max, final int unitTicks,
85              final T... initialValues)
86      {
87          int[] ret = new int[initialValues.length];
88          Throw.when(initialValues.length == 0, IllegalArgumentException.class, "the number of thumbs cannot be zero");
89  
90          // create the indices; map to closest step on the linear scale
91          for (int i = 0; i < initialValues.length; i++)
92          {
93              T iv = initialValues[i];
94              Throw.when(iv.compareTo(min) < 0, IllegalArgumentException.class,
95                      "initial value %s less than minimum scale value %s", iv.toString(), min.toString());
96              Throw.when(iv.compareTo(max) > 0, IllegalArgumentException.class,
97                      "initial value %s more than maximum scale value %s", iv.toString(), max.toString());
98              double div = iv.doubleValue();
99              double dmin = min.doubleValue();
100             double dmax = max.doubleValue();
101             double dut1 = Double.valueOf(unitTicks - 1);
102             int index = (int) Math.round(dut1 * (div - dmin) / (dmax - dmin));
103             ret[i] = index;
104         }
105         return ret;
106     }
107 
108     @Override
109     protected int mapValueToIndex(final T value)
110     {
111         Throw.when(value.compareTo(this.min) < 0, IllegalArgumentException.class,
112                 "initial value %s less than minimum scale value %s", value.toString(), this.min.toString());
113         Throw.when(value.compareTo(this.max) > 0, IllegalArgumentException.class,
114                 "initial value %s more than maximum scale value %s", value.toString(), this.max.toString());
115         double div = value.doubleValue();
116         double dmin = this.min.doubleValue();
117         double dmax = this.max.doubleValue();
118         double dut1 = Double.valueOf(this.unitTicks - 1);
119         int index = (int) Math.round(dut1 * (div - dmin) / (dmax - dmin));
120         return index;
121     }
122 
123     /**
124      * Return the number of unit ticks on the scale.
125      * @return the number of ticks on the linear scale; note that when you need 100 intervals (0-100), ticks is 101.
126      */
127     public int getUnitTicks()
128     {
129         return this.unitTicks;
130     }
131 
132 }