← Back toDrag and drop in React

Create a linear gauge with discrete values

Written byPhuoc Nguyen
Created
22 Nov, 2023
Tags
React linear gauge
A linear gauge is a tool used to visually represent measurements on a scale. It has a horizontal or vertical axis with values and a pointer or bar that moves along the axis to show the current value being measured. Linear gauges are often used in dashboards and reports to give a quick overview of data, like progress towards a goal, performance metrics, or levels of a particular variable. They're especially helpful when you need to show changes in data over time or compare multiple variables.

The benefits of using a discrete linear gauge over other types of gauges

While continuous gauges offer smooth and precise measurements, discrete gauges have their own unique advantages. A discrete linear gauge can provide a clear and straightforward representation of data by breaking it down into easily identifiable categories or levels. This can be especially helpful when measuring qualitative data, such as customer satisfaction levels on a scale of 1 to 5. A discrete linear gauge with five ticks would be more appropriate than a continuous gauge with infinite gradations.
Another benefit of using a discrete linear gauge is that it allows for easier identification of outliers or anomalies in the data being measured. If there is an unexpected spike or dip in the value being measured, it will be more apparent on a discrete gauge where each tick represents a specific value.
Overall, using a discrete linear gauge can provide greater clarity and simplicity in visualizing data, making it easier for viewers to understand and interpret the information presented.

Situations where discrete linear gauges are useful

Discrete linear gauges are handy tools for representing data on a scale with distinct categories or levels. Here are some examples of situations where they can be particularly useful:
  • Rating scales: Discrete linear gauges are commonly used in surveys and questionnaires to measure attitudes, opinions, or satisfaction levels. They can help to provide a clear and concise representation of responses on a scale of "strongly disagree" to "strongly agree".
  • Health indicators: In healthcare settings, discrete gauges can be used to represent vital signs such as blood pressure, heart rate, or temperature. Each tick on the gauge could correspond to a specific range of values that indicate normal, low, or high readings.
  • Budgeting and finance: Discrete linear gauges can be useful for tracking expenses or budgeting goals. For example, you might use a gauge with five ticks to represent spending levels from "very low" to "very high", and track your progress towards staying within your budget.
  • Performance metrics: Discrete gauges can also be used to track performance metrics such as sales targets, customer retention rates, or website traffic. Each tick on the gauge could represent a specific threshold that indicates whether the metric is below expectations, on target, or exceeding expectations.
These are just a few examples of how discrete linear gauges can provide clear and concise representations of data in different contexts.
In this post, we'll explore how to use React to create a discrete linear gauge.

Creating the layout

Let's dive into creating the layout for a linear gauge. For a detailed guide on how to do this with CSS and HTML, check out this link.
Here's a sneak peek of what a vertical linear gauge looks like. It has ticks and labels that represent discrete values like 0, 20, 40, 60, and so on. The `Gauge` component currently has a fixed progress bar set at 60%. However, we plan to make it more dynamic by adjusting its value based on the user's input.

Adding a marker

The `Gauge` component we created above doesn't allow the user to adjust the value. To address this issue, we'll add a marker that shows the current value. Users can then simply drag the marker to their desired position.
tsx
<div className="gauge">
<div className="gauge__marker" />
...
</div>
The marker in our linear gauge will be positioned absolutely with respect to its parent container, allowing it to move freely along the gauge axis. To control the marker's position, we use the `top` property for vertical placement and the `left` property for horizontal placement, both relative to the top edge of its parent container. Additionally, the size of the marker can be adjusted using the `width` and `height` properties. With these adjustments, we can easily customize the appearance and behavior of our linear gauge to meet our specific needs.
css
.gauge__marker {
position: absolute;
top: -0.5rem;
left: 0;
width: 0.5rem;
height: 1rem;
}
Now, let's take a look at how to create a triangle shape for our marker using the `::before` pseudo-element. To start, we'll position the `::before` element at the left edge of the marker using `left: 0`. We'll then set its `width` and `height` to 0 and add border widths only on two sides, which will create our desired triangle shape.
To ensure that the base of the triangle is aligned with the left edge of the marker, we'll use the `transform: translateX(-100%)` property to move it to the left by 100% of its own width. Finally, we'll set the border color to transparent for all sides except for one, which will give us a single-colored triangle shape.
With these adjustments, we can easily customize the appearance of our marker to fit our specific needs.
css
.gauge__marker::before {
content: '';
position: absolute;
top: 0;
left: 0;
transform: translateX(-100%);

border-style: solid;
height: 0;
width: 0;

border-color: transparent transparent transparent rgb(99 102 241);
border-width: 0.5rem 0 0.5rem 0.5rem;
}
Here's what the gauge looks like with the new marker element:

Adjusting the gauge value

To change the gauge value, simply drag the marker. We'll use the same approach to snap the draggable element to a grid we created earlier.
Here's the code snippet that tracks the mouse movement, where `gridSize` represents the size of the grid:
ts
const handleMouseMove = (e: React.MouseEvent) => {
const dx = e.clientX - startPos.x;
const dy = e.clientY - startPos.y;
const snappedX = Math.round(dx / gridSize) * gridSize;
const snappedY = Math.round(dy / gridSize) * gridSize;
setOffset({ dx: snappedX, dy: snappedY });
};
To snap the draggable element to specific values, we'll use the `step` prop instead of the `gridSize` prop. We'll calculate the element's position as a percentage of its parent container's `width` and `height`, and then round that value to the nearest multiple of a predefined step size.
For instance, if we set the step size to 20%, each drag movement will snap the element's position to the closest multiple of 20%. This guarantees that the element is always positioned precisely on one of these predetermined points, rather than allowing for arbitrary placement.
ts
const parentRect = node.parentElement.getBoundingClientRect();
const percentX = Math.round(100 * dx / (parentRect.width * step)) * step;
const percentY = Math.round(100 * dy / (parentRect.height * step)) * step;
In this example, we calculate the `percentX` and `dx` values based on the difference between the current and starting mouse positions during a drag event. `dx` represents the distance traveled by the draggable element along the x-axis, while `percentX` represents this distance as a percentage of the parent container's width.
To keep our values discrete, we round `percentX` to the nearest multiple of our predefined step size. We then use this rounded percentage value to calculate the new absolute position of the draggable element along the x-axis by multiplying it with the parent container's width and dividing it by 100.
ts
dx = percentX * parentRect.width / 100;
dy = percentY * parentRect.height / 100;
Next, we clamp both `percentX` and `dx` to make sure they stay within their minimum and maximum allowed values. This makes sure our draggable element stays within its boundaries and prevents any unexpected behavior.
ts
setPercent({
percentX: clamp(percentX, 0, 100),
percentY: clamp(percentY, 0, 100),
});
setOffset({
dx: clamp(dx, 0, parentRect.width),
dy: clamp(dy, 0, parentRect.height),
});
The `setPercent` function updates an internal state that indicates how far the draggable element has been moved in terms of percentages.
ts
const [{ percentX, percentY }, setPercent] = React.useState({
percentX: 0,
percentY: 0,
});
Lastly, we will add both `percentX` and `percentY` to the values returned by the hook.
ts
const useDraggable = ({ step }) => {
return [ref, dx, dy, percentX, percentY];
};
Using the hook is easy. We've been demonstrating how simple it is throughout this series. To use the hook, all we need to do is call the hook function with an additional `step` prop.
ts
const [draggableRef, dx, dy, percentX, percentY] = useDraggable({
step: 20,
});
To enable users to drag the marker to discrete values, we pass the `step: 20` parameter to the hook. The hook returns an array of five items, and we use the first item to set the reference of the marker via the `ref` attribute, which makes it draggable. We then use the `dy` value to set the position of the marker.
tsx
<div
className="gauge__marker"
ref={draggableRef}
style={{
transform: `translateY(${dy}px)`,
}}
/>
To synchronize the gauge progress with the marker position, we use the `height` property of the progress element. We calculate this value based on the `percentY` value obtained from our draggable hook.
tsx
<div
className="gauge__progress"
style={{
height: `${percentY}%`,
}}
/>
By setting the `height` to a percentage value, we can create a progress bar that adjusts based on user input. As users drag the marker up or down along the gauge's vertical axis, the `percentY` value updates accordingly and changes the height of our progress bar.
This approach lets us create an interactive and visually appealing linear gauge that presents data in a clear and concise way.
Take a look at the final demo below:

Conclusion

In this post, we'll explore how to create a dynamic linear gauge using React. First, we'll create a basic gauge component with a fixed progress bar. Then, we'll add a draggable marker that allows users to adjust the gauge's value.
We'll also implement snapping functionality for the marker, which ensures that it always lands on discrete values based on a predefined step size. This approach creates a more structured user experience and prevents any unwanted behavior when dragging the marker.
By syncing the position of the marker with the height of our progress bar, we can create an interactive and visually appealing linear gauge. This makes it easy for users to understand the data being presented in a concise and intuitive way.
With these techniques, you can customize your own linear gauges to meet specific design requirements. This will provide your users with an intuitive interface for interacting with data.

See also

If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn