CategorialMultiSlider.java
package org.djutils.swing.multislider;
import java.util.List;
import org.djutils.exceptions.Throw;
import org.djutils.immutablecollections.ImmutableArrayList;
import org.djutils.immutablecollections.ImmutableList;
/**
* CategorialMultiSlider implements a slider with multiple thumbs and categorial values. The slider returns instances of a given
* type. The MultiSlider is implemented by drawing a number of sliders on top of each other using an Swing
* {@code OverlayManager}, and passing the mouse events from a glass pane on top to the correct slider(s). The class is a
* {@code ChangeListener} to listen to the changes of individual sliders underneath.
* <p>
* Several models exist to indicate whether thumbs can pass each other or not, or be on top of each other or not.
* </p>
* <p>
* Copyright (c) 2024-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
* distributed under a three-clause BSD-style license, which can be found at
* <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
* </p>
* @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
* @param <T> The type of the labels for the multislider
*/
public class CategorialMultiSlider<T> extends AbstractMultiSlider<T>
{
/** */
private static final long serialVersionUID = 20241124L;
/** the categorial scale of this multislider. */
private final ImmutableList<T> scale;
/**
* Creates a horizontal slider using the specified scale and initial values.
* @param scale the list of categorial values for the scale
* @param initialValues the initial values of the slider.
* @throws IllegalArgumentException if the initial values are not part of the scale, or if the number of thumbs is 0, or
* when the values are not in increasing scale order (which is important for restricting passing and overlap),
* or when the scale has duplicate values
*/
@SafeVarargs
public CategorialMultiSlider(final List<T> scale, final T... initialValues)
{
this(true, scale, initialValues);
}
/**
* Creates a horizontal or vertical slider using the specified min, max and initial values.
* @param horizontal the orientation of the slider; true for horizontal, false for vertical
* @param scale the list of categorial values for the scale
* @param initialValues the initial values of the slider.
* @throws IllegalArgumentException if the initial values are not part of the scale, or if the number of thumbs is 0, or
* when the values are not in increasing scale order (which is important for restricting passing and overlap),
* or when the scale has duplicate values
*/
@SafeVarargs
public CategorialMultiSlider(final boolean horizontal, final List<T> scale, final T... initialValues)
{
super(0, scale.size() - 1, horizontal, intArray(scale, initialValues));
this.scale = new ImmutableArrayList<T>(scale);
setLabelTable(createStandardLabels(1));
}
/**
* Make an int array from the initial values given the scale.
* @param scale the list of categorial values for the scale
* @param initialValues the initial values of the slider.
* @param <T> the type of objects for the categorial scale
* @return an int array with the index vales of the initial values on the scale
* @throws IllegalArgumentException if the initial values are not part of the scale, or when the scale has duplicate values
*/
@SafeVarargs
private static <T> int[] intArray(final List<T> scale, final T... initialValues)
{
int[] ret = new int[initialValues.length];
Throw.when(initialValues.length == 0, IllegalArgumentException.class, "the number of thumbs cannot be zero");
// check for duplicate values in the scale
for (int i = 0; i < scale.size(); i++)
{
for (int j = 0; j < scale.size(); j++)
{
if (i != j && scale.get(i).equals(scale.get(j)))
{
throw new IllegalArgumentException(
"Two values on the CategorialMultiSlider scale are the same: " + scale.get(i));
}
}
}
// create the indices
for (int i = 0; i < initialValues.length; i++)
{
int index = scale.indexOf(initialValues[i]);
if (index == -1)
{
throw new IllegalArgumentException(
"Initial value " + initialValues[i] + " not found on the CategorialMultiSlider scale: " + scale);
}
ret[i] = index;
}
return ret;
}
/** {@inheritDoc} */
@Override
protected T mapIndexToValue(final int index)
{
Throw.when(index < 0 || index >= this.scale.size(), IllegalArgumentException.class,
"CategorialMultiSlider scale: index < 0 || index > scale size");
return this.scale.get(index);
}
/** {@inheritDoc} */
@Override
protected int mapValueToIndex(final T value)
{
int index = this.scale.indexOf(value);
if (index == -1)
{
throw new IllegalArgumentException(
"Value " + value + " not found on the CategorialMultiSlider scale: " + this.scale);
}
return index;
}
}