亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

Table of Contents
What we’re making
The basics of the chart
Determining the highest value
Calculating the dimension of the rectangles
Creating and inserting the DOM elements
Cutout rectangles
Masking 101
The white fill
The black fill
The gray fill
The last bit
Generating the masks
The final result
Home Web Front-end CSS Tutorial How to Create an Animated Chart of Nested Squares Using Masks

How to Create an Animated Chart of Nested Squares Using Masks

Mar 18, 2025 am 11:03 AM

How to Create an Animated Chart of Nested Squares Using Masks

We have many well-known chart types: bar, donut, line, pie, you name it. All popular chart libraries support these. Then there are the chart types that do not even have a name. Check out this dreamt-up chart with stacked (nested) squares that can help visualize relative sizes, or how different values compare to one another:

What we’re making

Without any interactivity, creating this design is fairly straightforward. One way to do it is is to stack elements (e.g. SVG elements, or even HTML divs) in decreasing sizes, where all of their bottom-left corners touch the same point.

But things get trickier once we introduce some interactivity. Here’s how it should be: When we move our mouse over one of the shapes, we want the others to fade out and move away.

We’ll create these irregular shapes using rectangles and masks — literal with and elements. If you are entirely new to masks, you are in the right place. This is an introductory-level article. If you are more seasoned, then perhaps this cut-out effect is a trick that you can take with you.

Now, before we begin, you may wonder if a better alternative to SVG to using custom shapes. That’s definitely a possibility! But drawing shapes with a can be intimidating, or even get messy. So, we’re working with “easier” elements to get the same shapes and effects.

For example, here’s how we would have to represent the largest blue shape using a .

<svg viewbox="0 0 320 320" width="320" height="320">
  <path d="M320 0H0V56H264V320H320V0Z" fill="#264653"></path>
</svg>

If the 0H0V56… does not make any sense to you, check out “The SVG path Syntax: An Illustrated Guide” for a thorough explanation of the syntax.

The basics of the chart

Given a data set like this:

type DataSetEntry = {
  label: string;
  value: number;
};

type DataSet = DataSetEntry[];

const rawDataSet: DataSet = [
  { label: 'Bad', value: 1231 },
  { label: 'Beginning', value: 6321 },
  { label: 'Developing', value: 10028 },
  { label: 'Accomplished', value: 12123 },
  { label: 'Exemplary', value: 2120 }
];

…we want to end up with an SVG like this:

<svg viewbox="0 0 320 320" width="320" height="320">
  <rect width="320" height="320" y="0" fill="..."></rect>
  <rect width="264" height="264" y="56" fill="..."></rect>
  <rect width="167" height="167" y="153" fill="..."></rect>
  <rect width="56" height="56" y="264" fill="..."></rect>
  <rect width="32" height="32" y="288" fill="..."></rect>
</svg>

Determining the highest value

It will become apparent in a moment why we need the highest value. We can use the Math.max() to get it. It accepts any number of arguments and returns the highest value in a set.

const dataSetHighestValue: number = Math.max(
  ...rawDataSet.map((entry: DataSetEntry) => entry.value)
);

Since we have a small dataset, we can just tell that we will get 12123.

Calculating the dimension of the rectangles

If we look at the design, the rectangle representing the highest value (12123) covers the entire area of the chart.

We arbitrarily picked 320 for the SVG dimensions. Since our rectangles are squares, the width and height are equal. How can we make 12123 equal to 320? How about the less “special” values? How big is the 6321 rectangle?

Asked another way, how do we map a number from one range ([0, 12123]) to another one ([0, 320])? Or, in more math-y terms, how do we scale a variable to an interval of [a, b]?

For our purposes, we are going to implement the function like this:

const remapValue = (
  value: number,
  fromMin: number,
  fromMax: number,
  toMin: number,
  toMax: number
): number => {
  return ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin)   toMin;
};

remapValue(1231, 0, 12123, 0, 320); // 32
remapValue(6321, 0, 12123, 0, 320); // 167
remapValue(12123, 0, 12123, 0, 320); // 320

Since we map values to the same range in our code, instead of passing the minimums and maximums over and over, we can create a wrapper function:

const valueRemapper = (
  fromMin: number,
  fromMax: number,
  toMin: number,
  toMax: number
) => {
  return (value: number): number => {
    return remapValue(value, fromMin, fromMax, toMin, toMax);
  };
};

const remapDataSetValueToSvgDimension = valueRemapper(
  0,
  dataSetHighestValue,
  0,
  svgDimension
);

We can use it like this:

remapDataSetValueToSvgDimension(1231); // 32
remapDataSetValueToSvgDimension(6321); // 167
remapDataSetValueToSvgDimension(12123); // 320

Creating and inserting the DOM elements

What remains has to do with DOM manipulation. We have to create the and the five elements, set their attributes, and append them to the DOM. We can do all this with the basic createElementNS, setAttribute, and the appendChild functions.

Notice that we are using the createElementNS instead of the more common createElement. This is because we are working with an SVG. HTML and SVG elements have different specs, so they fall under a different namespace URI. It just happens that the createElement conveniently uses the HTML namespace! So, to create an SVG, we have to be this verbose:

document.createElementNS('http://www.w3.org/2000/svg', 'svg') as SVGSVGElement;

Surely, we can create another helper function:

const createSvgNSElement = (element: string): SVGElement => {
  return document.createElementNS('http://www.w3.org/2000/svg', element);
};

When we are appending the rectangles to the DOM, we have to pay attention to their order. Otherwise, we would have to specify the z-index explicitly. The first rectangle has to be the largest, and the last rectangle has to be the smallest. Best to sort the data before the loop.

const data = rawDataSet.sort(
  (a: DataSetEntry, b: DataSetEntry) => b.value - a.value
);

data.forEach((d: DataSetEntry, index: number) => {
  const rect: SVGRectElement = createSvgNSElement('rect') as SVGRectElement;
  const rectDimension: number = remapDataSetValueToSvgDimension(d.value);

  rect.setAttribute('width', `${rectDimension}`);
  rect.setAttribute('height', `${rectDimension}`);
  rect.setAttribute('y', `${svgDimension - rectDimension}`);

  svg.appendChild(rect);
});

The coordinate system starts from the top-left; that’s where the [0, 0] is. We are always going to draw the rectangles from the left side. The x attribute, which controls the horizontal position, defaults to 0, so we don’t have to set it. The y attribute controls the vertical position.

To give the visual impression that all of the rectangles originate from the same point that touches their bottom-left corners, we have to push the rectangles down so to speak. By how much? The exact amount that the rectangle does not fill. And that value is the difference between the dimension of the chart and the particular rectangle. If we put all the bits together, we end up with this:

We already added the code for the animation to this demo using CSS.

Cutout rectangles

We have to turn our rectangles into irregular shapes that sort of look like the number seven, or the letter L rotated 180 degrees.

If we focus on the “missing parts” then we can see they cutouts of the same rectangles we’re already working with.

We want to hide those cutouts. That’s how we are going to end up with the L-shapes we want.

Masking 101

A mask is something you define and later apply to an element. Typically, the mask is inlined in the element it belongs to. And, generally, it should have a unique id because we have to reference it in order to apply the mask to an element.

<svg>
  <mask>
    <!-- ... -->
  </mask>
</svg>

In the tag, we put the shapes that serve as the actual masks. We also apply the mask attribute to the elements.

<svg>
  <mask>
    <!-- ... -->
  </mask>
  <rect mask="url(#myCleverlyNamedMask)"></rect>
</svg>

That’s not the only way to define or apply a mask, but it’s the most straightforward way for this demo. Let’s do a bit of experimentation before writing any code to generate the masks.

We said that we want to cover the cutout areas that match the sizes of the existing rectangles. If we take the largest element and we apply the previous rectangle as a mask, we end up with this code:

<svg viewbox="0 0 320 320" width="320" height="320">
  <mask>
    <rect width="264" height="264" y="56" fill=""></rect>
  </mask>
  <rect width="320" height="320" y="0" fill="#264653" mask="url(#theMask)"></rect>
</svg>

The element inside the mask needs a fill value. What should that be? We’ll see entirely different results based on the fill value (color) we choose.

The white fill

If we use a white value for the fill, then we get this:

Now, our large rectangle is the same dimension as the masking rectangle. Not exactly what we wanted.

The black fill

If we use a black value instead, then it looks like this:

We don’t see anything. That’s because what is filled with black is what becomes invisible. We control the visibility of masks using white and black fills. The dashed lines are there as a visual aid to reference the dimensions of the invisible area.

The gray fill

Now let’s use something in-between white and black, say gray:

It’s neither fully opaque or solid; it’s transparent. So, now we know we can control the “degree of visibility” here by using something different than white and black values which is a good trick to keep in our back pockets.

The last bit

Here’s what we’ve covered and learned about masks so far:

  • The element inside the controls the dimension of the masked area.
  • We can make the contents of the masked area visible, invisible, or transparent.

We have only used one shape for the mask, but as with any general purpose HTML tag, we can nest as many child elements in there as we want. In fact, the trick to achieve what we want is using two SVG elements. We have to stack them one on top of the other:

<svg viewbox="0 0 320 320" width="320" height="320">
  <mask>
    <rect width="320" height="320" y="0" fill="???"></rect>
    <rect width="264" height="264" y="56" fill="???"></rect>
  </mask>
  <rect width="320" height="320" y="0" fill="#264653" mask="url(#maskW320)"></rect>
</svg>

One of our masking rectangles is filled with white; the other is filled with black. Even if we know the rules, let’s try out the possibilities.

<mask>
  <rect width="320" height="320" y="0" fill="black"></rect>
  <rect width="264" height="264" y="56" fill="white"></rect>
</mask>

The is the dimension of the largest element and the largest element is filled with black. That means everything under that area is invisible. And everything under the smaller rectangle is visible.

Now let’s do flip things where the black rectangle is on top:

<mask>
  <rect width="320" height="320" y="0" fill="white"></rect>
  <rect width="264" height="264" y="56" fill="black"></rect>
</mask>

This is what we want!

Everything under the largest white-filled rectangle is visible, but the smaller black rectangle is on top of it (closer to us on the z-axis), masking that part.

Generating the masks

Now that we know what we have to do, we can create the masks with relative ease. It’s similar to how we generated the colored rectangles in the first place — we create a secondary loop where we create the mask and the two rects.

This time, instead of appending the rects directly to the SVG, we append it to the mask:

data.forEach((d: DataSetEntry, index: number) => {
  const mask: SVGMaskElement = createSvgNSElement('mask') as SVGMaskElement;

  const rectDimension: number = remapDataSetValueToSvgDimension(d.value);
  const rect: SVGRectElement = createSvgNSElement('rect') as SVGRectElement;

  rect.setAttribute('width', `${rectDimension}`);
  // ...setting the rest of the attributes...

  mask.setAttribute('id', `maskW${rectDimension.toFixed()}`);

  mask.appendChild(rect);

  // ...creating and setting the attributes for the smaller rectangle...

  svg.appendChild(mask);
});

data.forEach((d: DataSetEntry, index: number) => {
    // ...our code to generate the colored rectangles...
});

We could use the index as the mask’s ID, but this seems a more readable option, at least to me:

mask.setAttribute('id', `maskW${rectDimension.toFixed()}`); // maskW320, masW240, ...

As for adding the smaller rectangle in the mask, we have easy access the value we need because we previously ordered the rectangle values from highest to lowest. That means the next element in the loop is the smaller rectangle, the one we should reference. And we can do that by its index.

// ...previous part where we created the mask and the rectangle...

const smallerRectIndex = index   1;

// there's no next one when we are on the smallest
if (data[smallerRectIndex] !== undefined) {
  const smallerRectDimension: number = remapDataSetValueToSvgDimension(
    data[smallerRectIndex].value
  );
  const smallerRect: SVGRectElement = createSvgNSElement(
    'rect'
  ) as SVGRectElement;

  // ...setting the rectangle attributes...

  mask.appendChild(smallerRect);
}

svg.appendChild(mask);

What is left is to add the mask attribute to the colored rectangle in our original loop. It should match the format we chose:

rect.setAttribute('mask', `url(#maskW${rectDimension.toFixed()})`); // maskW320, maskW240, ...

The final result

And we are done! We’ve successfully made a chart that’s made out of nested squares. It even comes apart on mouse hover. And all it took was some SVG using the element to draw the cutout area of each square.

The above is the detailed content of How to Create an Animated Chart of Nested Squares Using Masks. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undress AI Tool

Undress AI Tool

Undress images for free

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

CSS tutorial for creating loading spinners and animations CSS tutorial for creating loading spinners and animations Jul 07, 2025 am 12:07 AM

There are three ways to create a CSS loading rotator: 1. Use the basic rotator of borders to achieve simple animation through HTML and CSS; 2. Use a custom rotator of multiple points to achieve the jump effect through different delay times; 3. Add a rotator in the button and switch classes through JavaScript to display the loading status. Each approach emphasizes the importance of design details such as color, size, accessibility and performance optimization to enhance the user experience.

Addressing CSS Browser Compatibility issues and prefixes Addressing CSS Browser Compatibility issues and prefixes Jul 07, 2025 am 01:44 AM

To deal with CSS browser compatibility and prefix issues, you need to understand the differences in browser support and use vendor prefixes reasonably. 1. Understand common problems such as Flexbox and Grid support, position:sticky invalid, and animation performance is different; 2. Check CanIuse confirmation feature support status; 3. Correctly use -webkit-, -moz-, -ms-, -o- and other manufacturer prefixes; 4. It is recommended to use Autoprefixer to automatically add prefixes; 5. Install PostCSS and configure browserslist to specify the target browser; 6. Automatically handle compatibility during construction; 7. Modernizr detection features can be used for old projects; 8. No need to pursue consistency of all browsers,

Styling visited links differently with CSS Styling visited links differently with CSS Jul 11, 2025 am 03:26 AM

Setting the style of links you have visited can improve the user experience, especially in content-intensive websites to help users navigate better. 1. Use CSS's: visited pseudo-class to define the style of the visited link, such as color changes; 2. Note that the browser only allows modification of some attributes due to privacy restrictions; 3. The color selection should be coordinated with the overall style to avoid abruptness; 4. The mobile terminal may not display this effect, and it is recommended to combine it with other visual prompts such as icon auxiliary logos.

Creating custom shapes with css clip-path Creating custom shapes with css clip-path Jul 09, 2025 am 01:29 AM

Use the clip-path attribute of CSS to crop elements into custom shapes, such as triangles, circular notches, polygons, etc., without relying on pictures or SVGs. Its advantages include: 1. Supports a variety of basic shapes such as circle, ellipse, polygon, etc.; 2. Responsive adjustment and adaptable to mobile terminals; 3. Easy to animation, and can be combined with hover or JavaScript to achieve dynamic effects; 4. It does not affect the layout flow, and only crops the display area. Common usages are such as circular clip-path:circle (50pxatcenter) and triangle clip-path:polygon (50%0%, 100 0%, 0 0%). Notice

What is the difference between display: inline, display: block, and display: inline-block? What is the difference between display: inline, display: block, and display: inline-block? Jul 11, 2025 am 03:25 AM

Themaindifferencesbetweendisplay:inline,block,andinline-blockinHTML/CSSarelayoutbehavior,spaceusage,andstylingcontrol.1.Inlineelementsflowwithtext,don’tstartonnewlines,ignorewidth/height,andonlyapplyhorizontalpadding/margins—idealforinlinetextstyling

How to create responsive images using CSS? How to create responsive images using CSS? Jul 15, 2025 am 01:10 AM

To create responsive images using CSS, it can be mainly achieved through the following methods: 1. Use max-width:100% and height:auto to allow the image to adapt to the container width while maintaining the proportion; 2. Use HTML's srcset and sizes attributes to intelligently load the image sources adapted to different screens; 3. Use object-fit and object-position to control image cropping and focus display. Together, these methods ensure that the images are presented clearly and beautifully on different devices.

What is CSS and what does it stand for? What is CSS and what does it stand for? Jul 03, 2025 am 01:48 AM

CSS,orCascadingStyleSheets,isthepartofwebdevelopmentthatcontrolsawebpage’svisualappearance,includingcolors,fonts,spacing,andlayout.Theterm“cascading”referstohowstylesareprioritized;forexample,inlinestylesoverrideexternalstyles,andspecificselectorslik

What is the CSS Painting API? What is the CSS Painting API? Jul 04, 2025 am 02:16 AM

TheCSSPaintingAPIenablesdynamicimagegenerationinCSSusingJavaScript.1.DeveloperscreateaPaintWorkletclasswithapaint()method.2.TheyregisteritviaregisterPaint().3.ThecustompaintfunctionisthenusedinCSSpropertieslikebackground-image.Thisallowsfordynamicvis

See all articles