Categories
Visx

Add Box Plots into Our React App with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add box plots into our React app.

Install Required Packages

We have to install a few modules.

To get started, we run:

npm i @visx/gradient @visx/group @visx/mock-data @visx/pattern @visx/responsive @visx/scale @visx/stats @visx/tooltip

to install the packages.

Add Box Plots

Now we can add the box plots with:

import React from "react";
import { Group } from "@visx/group";
import { ViolinPlot, BoxPlot } from "@visx/stats";
import { LinearGradient } from "@visx/gradient";
import { scaleBand, scaleLinear } from "@visx/scale";
import genStats from "@visx/mock-data/lib/generators/genStats";
import {
  Tooltip,
  defaultStyles as defaultTooltipStyles,
  useTooltip
} from "@visx/tooltip";
import { PatternLines } from "@visx/pattern";

const data = genStats(5);
const x = (d) => d.boxPlot.x;
const min = (d) => d.boxPlot.min;
const max = (d) => d.boxPlot.max;
const median = (d) => d.boxPlot.median;
const firstQuartile = (d) => d.boxPlot.firstQuartile;
const thirdQuartile = (d) => d.boxPlot.thirdQuartile;
const outliers = (d) => d.boxPlot.outliers;

const Example = ({ width, height }) => {
  const {
    tooltipOpen,
    tooltipLeft,
    tooltipTop,
    tooltipData,
    hideTooltip,
    showTooltip
  } = useTooltip();

  const xMax = width;
  const yMax = height - 120;
  const xScale = scaleBand({
    range: [0, xMax],
    round: true,
    domain: data.map(x),
    padding: 0.4
  });

  const values = data.reduce((allValues, { boxPlot }) => {
    allValues.push(boxPlot.min, boxPlot.max);
    return allValues;
  }, []);
  const minYValue = Math.min(...values);
  const maxYValue = Math.max(...values);

  const yScale = scaleLinear({
    range: [yMax, 0],
    round: true,
    domain: [minYValue, maxYValue]
  });

  const boxWidth = xScale.bandwidth();
  const constrainedWidth = Math.min(40, boxWidth);

  return width < 10 ? null : (
    <div style={{ position: "relative" }}>
      <svg width={width} height={height}>
        <LinearGradient id="statsplot" to="#8b6ce7" from="#87f2d4" />
        <rect
          x={0}
          y={0}
          width={width}
          height={height}
          fill="url(#statsplot)"
          rx={14}
        />
        <PatternLines
          id="hViolinLines"
          height={3}
          width={3}
          stroke="#ced4da"
          strokeWidth={1}
          orientation={["horizontal"]}
        />
        <Group top={40}>
          {data.map((d, i) => (
            <g key={i}>
              <ViolinPlot
                data={d.binData}
                stroke="#dee2e6"
                left={xScale(x(d))}
                width={constrainedWidth}
                valueScale={yScale}
                fill="url(#hViolinLines)"
              />
              <BoxPlot
                min={min(d)}
                max={max(d)}
                left={xScale(x(d)) + 0.3 * constrainedWidth}
                firstQuartile={firstQuartile(d)}
                thirdQuartile={thirdQuartile(d)}
                median={median(d)}
                boxWidth={constrainedWidth * 0.4}
                fill="#FFFFFF"
                fillOpacity={0.3}
                stroke="#FFFFFF"
                strokeWidth={2}
                valueScale={yScale}
                outliers={outliers(d)}
                minProps={{
                  onMouseOver: () => {
                    showTooltip({
                      tooltipTop: yScale(min(d)) ?? 0 + 40,
                      tooltipLeft: xScale(x(d)) + constrainedWidth + 5,
                      tooltipData: {
                        min: min(d),
                        name: x(d)
                      }
                    });
                  },
                  onMouseLeave: () => {
                    hideTooltip();
                  }
                }}
                maxProps={{
                  onMouseOver: () => {
                    showTooltip({
                      tooltipTop: yScale(max(d)) ?? 0 + 40,
                      tooltipLeft: xScale(x(d)) + constrainedWidth + 5,
                      tooltipData: {
                        max: max(d),
                        name: x(d)
                      }
                    });
                  },
                  onMouseLeave: () => {
                    hideTooltip();
                  }
                }}
                boxProps={{
                  onMouseOver: () => {
                    showTooltip({
                      tooltipTop: yScale(median(d)) ?? 0 + 40,
                      tooltipLeft: xScale(x(d)) + constrainedWidth + 5,
                      tooltipData: {
                        ...d.boxPlot,
                        name: x(d)
                      }
                    });
                  },
                  onMouseLeave: () => {
                    hideTooltip();
                  }
                }}
                medianProps={{
                  style: {
                    stroke: "white"
                  },
                  onMouseOver: () => {
                    showTooltip({
                      tooltipTop: yScale(median(d)) ?? 0 + 40,
                      tooltipLeft: xScale(x(d)) + constrainedWidth + 5,
                      tooltipData: {
                        median: median(d),
                        name: x(d)
                      }
                    });
                  },
                  onMouseLeave: () => {
                    hideTooltip();
                  }
                }}
              />
            </g>
          ))}
        </Group>
      </svg>

      {tooltipOpen && tooltipData && (
        <Tooltip
          top={tooltipTop}
          left={tooltipLeft}
          style={{
            ...defaultTooltipStyles,
            backgroundColor: "#283238",
            color: "white"
          }}
        >
          <div>
            <strong>{tooltipData.name}</strong>
          </div>
          <div style={{ marginTop: "5px", fontSize: "12px" }}>
            {tooltipData.max && <div>max: {tooltipData.max}</div>}
            {tooltipData.thirdQuartile && (
              <div>third quartile: {tooltipData.thirdQuartile}</div>
            )}
            {tooltipData.median && <div>median: {tooltipData.median}</div>}
            {tooltipData.firstQuartile && (
              <div>first quartile: {tooltipData.firstQuartile}</div>
            )}
            {tooltipData.min && <div>min: {tooltipData.min}</div>}
          </div>
        </Tooltip>
      )}
    </div>
  );
};

export default function App() {
  return (
    <div className="App">
      <Example width={500} height={300} />
    </div>
  );
}

The data variable has the data for the boxplot.

Then we add the getter methods below it.

In the Example component, we call the useTooltip hook to show tooltips when we hover over the boxplots.

showTooltip shows the tooltip. hideTooltip hides the tooltip.

We add the values variable to gather the values needed for the boxplots.

yScale has the scale for the y-axis.

To add the boxplots, we call data.map to map the data values box plot components.

We use the ViolinPlot component to render the violin plot surrounding the boxplot.

Then we use the BoxPlot component to render the boxplot.

We pass in all the getter methods as props into ViolinPlot and BoxPlot .

We call showTooltip in the onMouseOver method to show a tooltip when we hover over the boxplot.

And in the onMouseLeave handler, we call hideTooltip to hide the tooltips when we move away from the boxplot.

minProps takes handlers that runs when we hover over the bottom of the boxplot.

maxProps takes handlers that runs when we hover over the top of the boxplot.

boxProps takes handlers that runs when we hover over the boxes of the boxplot.

medianProps takes handlers that runs when we hover over the median point of the boxplot.

Finally, we add the tooltip content with the Tooltip component.

Now we should see a box and violin plot. And when we hover over the whiskers or the boxes, we see a tooltip with the values.

Conclusion

We can add box and violin plots into our React app with the Visx library.

Categories
Visx

Add Polygons into Our React App with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add polygons into our React app.

Install Required Packages

We have to install a few modules.

To get started, we run:

npm i @visx/gradient @visx/group @visx/responsive @visx/scale @visx/shape

to install the packages.

Add Polygons

To add the polygons, we write:

import React from "react";
import { Polygon } from "@visx/shape";
import { Group } from "@visx/group";
import { scaleBand } from "@visx/scale";
import { GradientPinkRed } from "@visx/gradient";

export const background = "#7f82e3";
const polygonSize = 25;
const defaultMargin = { top: 10, right: 10, bottom: 10, left: 10 };

const polygons = [
  {
    sides: 3,
    fill: "rgb(174, 238, 248)",
    rotate: 90
  },
  {
    sides: 4,
    fill: "lightgreen",
    rotate: 45
  },
  {
    sides: 6,
    fill: "rgb(229, 130, 255)",
    rotate: 0
  },
  {
    sides: 8,
    fill: "url(#polygon-pink)",
    rotate: 0
  }
];

const yScale = scaleBand({
  domain: polygons.map((p, i) => i),
  padding: 0.8
});

const Example = ({ width, height, margin = defaultMargin }: PolygonProps) => {
  yScale.rangeRound([0, height - margin.top - margin.bottom]);
  const centerX = (width - margin.left - margin.right) / 2;
  return (
    <svg width={width} height={height}>
      <rect width={width} height={height} fill={background} rx={14} />
      <GradientPinkRed id="polygon-pink" />
      {polygons.map((polygon, i) => (
        <Group
          key={`polygon-${i}`}
          top={(yScale(i) || 0) + polygonSize / 2}
          left={margin.left + centerX}
        >
          <Polygon
            sides={polygon.sides}
            size={polygonSize}
            fill={polygon.fill}
            rotate={polygon.rotate}
          />
        </Group>
      ))}
    </svg>
  );
};

export default function App() {
  return (
    <div className="App">
      <Example width={500} height={300} />
    </div>
  );
}

The background variable has the background’s color.

polygonSize is the size of each polygon in pixels.

We create the polygons array with the properties of the polygons.

We set the number of sides with the sides .

fill is the background of the polygon.

rotate is the number of degrees to rotate it.

In the Example component, we add the polygons onto the screen with the polygons.map callback.

We add the Polygon component with the props to set the sides, fill, and rotation angle.

Conclusion

We can add polygons into our React app with the Visx library.

Categories
JavaScript Basics

Check JavaScript Variable Data Types with the Typeof Operator

The typeof operator in JavaScript is useful for checking primitive types.

JavaScript is a loosely typed or a dynamically typed language. This means that a variable that’s declare with one type can be converted to another type without explicitly converting the data to another type. Variables can also contain any type at any time depending on what’s assigned. JavaScript has multiple data types. There are 7 primitive data types and an object type. The 7 primitive types are boolean, null, undefined, number, BigInt, string and symbol. Since there are different data types and they a variable or an object’s properties’ types can change anything, we need a way to check for the type of data of an variable or an object’s property.

To check for the type of an object’s property or a variable, we use the typeof operator of an object. It lets us get the type name of an object or its properties with as a string. We can use it as allows:

typeof x

Where x is a variable’s name. This is the only operand of the typeof operator. The check the type of an object’s property, we can write:

typeof x.prop

With the code above we can conveniently get the type of the prop property of x . The typeof operator returns a string containing the name of the type of the operand. It can return the following types names as a string:

  • Objects with the undefined type will return 'undefined'
  • Objects with the null type will return 'object'
  • Objects with the boolean type will return 'boolean'
  • Objects with the number type will return 'number'
  • Objects with the BigInt type will return 'bigint'
  • Objects with the String type will return 'string'
  • Objects with the Symbol type will return 'symbol'
  • Objects with the Function type will return 'function'
  • Any other types of object will return 'object'

We can use the typeof operator like in the following examples:

Numbers

The following operands will return 'number' if the typeof operator is applied to it. Therefore, all of the expressions when logged will output true .

typeof 1 === 'number';
typeof 3.1 === 'number';
typeof(2) === 'number';
typeof Math.LN10 === 'number';
typeof Infinity === 'number';
typeof NaN === 'number';
typeof Number('2') === 'number';
typeof Number('shoe') === 'number';

The number type is a double precision 64 bit number that can have values between negative 2 to the 53rd power minus one to 2 to the 53rd power minus one. There’s no specific type for integers. All numbers are floating point numbers. There’re also 3 symbolic values: Infinity , -Infinity and NaN . These are all within the range or are Infinity , -Infinity or NaN so they all have the type of 'number' . Number(‘shoe’) returns NaN and since NaN is of type number, typeof Number(‘shoe’) is number.

Strings

The following operands will return 'string' if the typeof operator is applied to it. Therefore, all of the expressions when logged will output true .

typeof '' === 'string';
typeof 'abc' === 'string';
typeof `template string` === 'string';
typeof '2' === 'string';
typeof (typeof 2) === 'string';
typeof String(1) === 'string';

Strings are used to represent textual data. Each element of the string has its own position in the string. It’s zero indexed, so the position of the first character of a string is 0. The length property of the string has the total number of characters of the string.

JavaScript strings are immutable. We cannot modify a string that has been created, but we can still create a new string that contains the originally defined string. We can extract substrings from a string with the substr() function and use the concat() function to concatenate 2 strings.

In JavaScript, anything surrounded by single quotes, double quotes, or backticks are strings. Single and double quote surround normal strings, and backticks surround template strings. The String function converts anything to a string, so String(1) returns '1' , so it’s of type string. The typeof operator returns a string, so if we are applying the typeof operator to the result returned by typeof , then that should be a string.

Booleans

The following operands will return 'boolean' if the typeof operator is applied to it. Therefore, all of the expressions when logged will output true .

typeof true === 'boolean';
typeof false === 'boolean';
typeof Boolean(2) === 'boolean';
typeof !!(2) === 'boolean';

The Boolean function and the double not operator (!! ) will both convert truthy values to true and falsy values to false . Falsy values in JavaScript includes 0, empty string, null , undefined , and false . So if they are passed in as as arguments to the Boolean function then they are converted to false . Everything else is truthy, so they’re converted to true with the Boolean function or the double not operator.

Symbols

The following operands will return 'symbol' if the typeof operator is applied to it. Therefore, all of the expressions when logged will output true .

typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
typeof Symbol.iterator === 'symbol'

Symbols are new to ES2015. It is unique and immutable identifier. Once you created it, it cannot be copied. Every time you create a new Symbol it is a unique one. It’s mainly used for unique identifiers in an object. It’s a Symbol’s only purpose.

The Symbol function creates a new symbol, so anything returned by it is of type symbol. Symbol.iterator is a special symbol, so that’s also of type symbol.

Undefined

The following operands will return 'undefined' if the typeof operator is applied to it. Therefore, all of the expressions when logged will output true .

typeof undefined === 'undefined';
typeof declaredVariableWithoutValueAssigned === 'undefined';
typeof undeclaredVariable === 'undefined';

A variable that has not been assigned a value has type undefined. If you do not assign a value to a declared variable or try to reference an undeclared variable, then you would get type undefined if you apply the typeof operator to them. Likewise, if you have never declared the property of an object or have declared the property haven’t assigned a value to it, those properties will also have the undefined type.

Objects

The following operands will return 'object' if the typeof operator is applied to it. Therefore, all of the expressions when logged will output true .

typeof {a: 1} === 'object';
typeof [1, 2, 4] === 'object';
typeof null === 'object';
typeof new Date() === 'object';
typeof /regex/ === 'object';

Object is a reference data type, which means it can be referenced by an identifier which points to location of the location of the object in memory. In memory, the object’s value is stored, and with the identifier, we can access the value. Object has properties, which are key-value pairs with the values being able to contain the data with primitive types or other objects, which means we can use object to build complex data structures.

In addition to data with key-value pairs having the type object, the null type also returns object when the typeof operator is operated on it. Arrays and other iterable objects also return object as the type when we apply the operator to it to get the type, since these kinds of data are also objects in JavaScript. To check if an object is an array, we can use the Array.isArray function to determine if an object is an array.

Functions

The following operands will return 'function' if the typeof operator is applied to it. Therefore, all of the expressions when logged will output true .

typeof function() {} === 'function';
typeof class C {} === 'function';
typeof Math.sin === 'function';

Functions are objects in JavaScript. Any kinds of functions, like arrow functions, functions declares with the function keyword, and classes are of type function. Note that classes in JavaScript is just syntactic sugar for functions that we can create new objects with. Therefore, classes are still of type function.

BigInts

The following operands will return 'bigint' if the typeof operator is applied to it. Therefore, all of the expressions when logged will output true .

typeof 42n === 'bigint';

In JavaScript, there is the BigInt type to store numbers that are beyond safe integer range. A BigInt number can be created by adding an n character to the end of a number. With BigInt, we can make calculations that have results beyond the safe range of normal numbers. So any number that ends with an n will be of type bigint .

New Operator

The new Operator will always return an object to whatever it’s applied to, so the following will always have type object. The following operands will return 'object' if the typeof operator is applied to it. Therefore, all of the expressions when logged will output true .

typeof new Boolean(true) === 'object';
typeof new Number(1) === 'object';
typeof new String('abc') === 'object';
`typeof new String(null)` === 'object';`typeof new Number(undefined)` === 'object';

Notice that no matter what we have, we get type object if the new keyword is used to create the object. It doesn’t matter whether it’s boolean, number, string, or whatever you pass in, Therefore, when we don’t need to use the new keyword to create the object, then don’t do it. To convert things to boolean, we use the Boolean function, to convert things to numbers we use the Number function, and to convert things to a string, we use the String function.

Null Type

null objects are of type object because they were represented by type tag 0. The null pointer for JavaScript is 0x00 like in most platforms. Therefore, null has type tag 0, so object has object as the type returned by typeof .

Determining Types of Expressions

We can use the typeof operator to determine the type of entities returned by expressions. We parentheses to determine the type of the result combined together in expressions. For example, if we want to get the type of 1 + ‘1’ , then we need to write:

typeof (1 + '1');

If we write the code above, we get string as the type, which is the correct type of the result of the expression. If we don’t put parentheses around the expression, then we only get the type of the first part of the expression, which is number concatenated with '1' .

Errors

Using the typeof operator before a variable declared with let or const will result in a ReferenceError being thrown, since it’s referencing a variable that’s not declared yet. Previous using the typeof operator on undeclared variables will return the type 'undefined’. The typeof operator never generated an error before ES2015.

For example, if we have:

typeof letVariable;
typeof constant;
typeof c;

let letVariable;
const constant = 'constant';
class c{};

The first 3 lines will throw a ReferenceError when they’re run.

The typeof operator is useful for checking types of primitive types and objects. For primitive types, it will return the type of the object, except for null type, which will return object as the type. Objects, including arrays and other iterable objects, will always return object as the type. With variables declared with let or const keywords and classes, we have to declare them before using the typeof operator on them.

Categories
Visx

Add Tree Views into Our React App with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add tree views into our React app.

Install Required Packages

We have to install a few modules.

To get started, we run:

npm i @visx/gradient @visx/group @visx/hierarchy @visx/responsive @visx/shape

to install the packages.

Create the Tree View

We can create tree views with different link types, animations, and layouts by writing:

import React, { useState } from "react";
import { Group } from "@visx/group";
import { hierarchy, Tree } from "@visx/hierarchy";
import { LinearGradient } from "@visx/gradient";
import { pointRadial } from "d3-shape";
import {
  LinkHorizontal,
  LinkVertical,
  LinkRadial,
  LinkHorizontalStep,
  LinkVerticalStep,
  LinkRadialStep,
  LinkHorizontalCurve,
  LinkVerticalCurve,
  LinkRadialCurve,
  LinkHorizontalLine,
  LinkVerticalLine,
  LinkRadialLine
} from "@visx/shape";

const data = {
  name: "T",
  children: [
    {
      name: "A",
      children: [
        { name: "A1" },
        { name: "A2" },
        {
          name: "C",
          children: [
            {
              name: "C1"
            },
            {
              name: "D",
              children: [
                {
                  name: "D1"
                },
                {
                  name: "D2"
                },
                {
                  name: "D3"
                }
              ]
            }
          ]
        }
      ]
    },
    { name: "Z" },
    {
      name: "B",
      children: [{ name: "B1" }, { name: "B2" }]
    }
  ]
};

function useForceUpdate() {
  const [, setValue] = useState(0);
  return () => setValue((value) => value + 1);
}

const defaultMargin = { top: 30, left: 30, right: 30, bottom: 70 };

function LinkControls({
  layout,
  orientation,
  linkType,
  stepPercent,
  setLayout,
  setOrientation,
  setLinkType,
  setStepPercent
}) {
  return (
    <div>
      <label>layout:</label>&nbsp;
      <select
        onClick={(e) => e.stopPropagation()}
        onChange={(e) => setLayout(e.target.value)}
        value={layout}
      >
        <option value="cartesian">cartesian</option>
        <option value="polar">polar</option>
      </select>
      &nbsp;&nbsp;
      <label>orientation:</label>&nbsp;
      <select
        onClick={(e) => e.stopPropagation()}
        onChange={(e) => setOrientation(e.target.value)}
        value={orientation}
        disabled={layout === "polar"}
      >
        <option value="vertical">vertical</option>
        <option value="horizontal">horizontal</option>
      </select>
      &nbsp;&nbsp;
      <label>link:</label>&nbsp;
      <select
        onClick={(e) => e.stopPropagation()}
        onChange={(e) => setLinkType(e.target.value)}
        value={linkType}
      >
        <option value="diagonal">diagonal</option>
        <option value="step">step</option>
        <option value="curve">curve</option>
        <option value="line">line</option>
      </select>
      {linkType === "step" && layout !== "polar" && (
        <>
          &nbsp;&nbsp;
          <label>step:</label>&nbsp;
          <input
            onClick={(e) => e.stopPropagation()}
            type="range"
            min={0}
            max={1}
            step={0.1}
            onChange={(e) => setStepPercent(Number(e.target.value))}
            value={stepPercent}
            disabled={linkType !== "step" || layout === "polar"}
          />
        </>
      )}
    </div>
  );
}

function getLinkComponent({ layout, linkType, orientation }) {
  let LinkComponent;

  if (layout === "polar") {
    if (linkType === "step") {
      LinkComponent = LinkRadialStep;
    } else if (linkType === "curve") {
      LinkComponent = LinkRadialCurve;
    } else if (linkType === "line") {
      LinkComponent = LinkRadialLine;
    } else {
      LinkComponent = LinkRadial;
    }
  } else if (orientation === "vertical") {
    if (linkType === "step") {
      LinkComponent = LinkVerticalStep;
    } else if (linkType === "curve") {
      LinkComponent = LinkVerticalCurve;
    } else if (linkType === "line") {
      LinkComponent = LinkVerticalLine;
    } else {
      LinkComponent = LinkVertical;
    }
  } else if (linkType === "step") {
    LinkComponent = LinkHorizontalStep;
  } else if (linkType === "curve") {
    LinkComponent = LinkHorizontalCurve;
  } else if (linkType === "line") {
    LinkComponent = LinkHorizontalLine;
  } else {
    LinkComponent = LinkHorizontal;
  }
  return LinkComponent;
}

function Example({
  width: totalWidth,
  height: totalHeight,
  margin = defaultMargin
}) {
  const [layout, setLayout] = useState("cartesian");
  const [orientation, setOrientation] = useState("horizontal");
  const [linkType, setLinkType] = useState("diagonal");
  const [stepPercent, setStepPercent] = useState(0.5);
  const forceUpdate = useForceUpdate();

  const innerWidth = totalWidth - margin.left - margin.right;
  const innerHeight = totalHeight - margin.top - margin.bottom;

  let origin;
  let sizeWidth;
  let sizeHeight;

  if (layout === "polar") {
    origin = {
      x: innerWidth / 2,
      y: innerHeight / 2
    };
    sizeWidth = 2 * Math.PI;
    sizeHeight = Math.min(innerWidth, innerHeight) / 2;
  } else {
    origin = { x: 0, y: 0 };
    if (orientation === "vertical") {
      sizeWidth = innerWidth;
      sizeHeight = innerHeight;
    } else {
      sizeWidth = innerHeight;
      sizeHeight = innerWidth;
    }
  }

  const LinkComponent = getLinkComponent({ layout, linkType, orientation });

  return totalWidth < 10 ? null : (
    <div>
      <LinkControls
        layout={layout}
        orientation={orientation}
        linkType={linkType}
        stepPercent={stepPercent}
        setLayout={setLayout}
        setOrientation={setOrientation}
        setLinkType={setLinkType}
        setStepPercent={setStepPercent}
      />
      <svg width={totalWidth} height={totalHeight}>
        <LinearGradient id="links-gradient" from="#fd9b93" to="#fe6e9e" />
        <rect width={totalWidth} height={totalHeight} rx={14} fill="#272b4d" />
        <Group top={margin.top} left={margin.left}>
          <Tree
            root={hierarchy(data, (d) => (d.isExpanded ? null : d.children))}
            size={[sizeWidth, sizeHeight]}
            separation={(a, b) => (a.parent === b.parent ? 1 : 0.5) / a.depth}
          >
            {(tree) => (
              <Group top={origin.y} left={origin.x}>
                {tree.links().map((link, i) => (
                  <LinkComponent
                    key={i}
                    data={link}
                    percent={stepPercent}
                    stroke="rgb(254,110,158,0.6)"
                    strokeWidth="1"
                    fill="none"
                  />
                ))}

                {tree.descendants().map((node, key) => {
                  const width = 40;
                  const height = 20;

                  let top;
                  let left;
                  if (layout === "polar") {
                    const [radialX, radialY] = pointRadial(node.x, node.y);
                    top = radialY;
                    left = radialX;
                  } else if (orientation === "vertical") {
                    top = node.y;
                    left = node.x;
                  } else {
                    top = node.x;
                    left = node.y;
                  }

                  return (
                    <Group top={top} left={left} key={key}>
                      {node.depth === 0 && (
                        <circle
                          r={12}
                          fill="url('#links-gradient')"
                          onClick={() => {
                            node.data.isExpanded = !node.data.isExpanded;
                            console.log(node);
                            forceUpdate();
                          }}
                        />
                      )}
                      {node.depth !== 0 && (
                        <rect
                          height={height}
                          width={width}
                          y={-height / 2}
                          x={-width / 2}
                          fill="#272b4d"
                          stroke={node.data.children ? "#03c0dc" : "#26deb0"}
                          strokeWidth={1}
                          strokeDasharray={node.data.children ? "0" : "2,2"}
                          strokeOpacity={node.data.children ? 1 : 0.6}
                          rx={node.data.children ? 0 : 10}
                          onClick={() => {
                            node.data.isExpanded = !node.data.isExpanded;
                            forceUpdate();
                          }}
                        />
                      )}
                      <text
                        dy=".33em"
                        fontSize={9}
                        fontFamily="Arial"
                        textAnchor="middle"
                        style={{ pointerEvents: "none" }}
                        fill={
                          node.depth === 0
                            ? "#71248e"
                            : node.children
                            ? "white"
                            : "#26deb0"
                        }
                      >
                        {node.data.name}
                      </text>
                    </Group>
                  );
                })}
              </Group>
            )}
          </Tree>
        </Group>
      </svg>
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <Example width={500} height={300} />
    </div>
  );
}

We have the data object which we use to render the tree.

The useForceUpdate hook is used to return the function to force the tree to update when we change the layout, orientation, or link type.

Then we have the LinkControls component with the dropdowns to let us set the layout, orientation, or link type.

Next, we have the getLinkComponent function to return the component we render given the layoyt , linkType or orientation .

In the Example component, we get the layout, orientation, and linkType set in the LinkControls component.

The Group component contains all parts of the tree.

Then we add the tree view with the Tree component.

It has a render prop to with the tree parameter to get the links between parent and children with the tree.links() method, we render the links with the LinkComponent function component.

tree.descendants() returns the descendants.

We then render the nodes within the tree.descendants().map() callback.

We set the position of the nodes with the if statement.

Then we render circle s or rect s for the node depending on the depth level of the node.

In either case, we render the text inside the node, which is either inside the circle or rect .

Conclusion

We can render a tree view with cartesian or polar coordinates in our React app with the Visx library.

Categories
Visx

Add Gradients into Our React App with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add gradients into our React app.

Install Required Packages

We have to install a few modules.

To get started, we run:

npm i @visx/gradient @visx/responsive @visx/shape

to install the packages.

Create the Gradients

We can create gradients by writing:

import React from "react";
import { Bar } from "@visx/shape";
import {
  GradientDarkgreenGreen,
  GradientLightgreenGreen,
  GradientOrangeRed,
  GradientPinkBlue,
  GradientPinkRed,
  GradientPurpleOrange,
  GradientPurpleRed,
  GradientTealBlue,
  RadialGradient,
  LinearGradient
} from "@visx/gradient";

const defaultMargin = {
  top: 0,
  left: 0,
  right: 0,
  bottom: 0
};

const Gradients = [
  GradientPinkRed,
  ({ id }) => <RadialGradient id={id} from="#55bdd5" to="#4f3681" r="80%" />,
  GradientOrangeRed,
  GradientPinkBlue,
  ({ id }) => (
    <LinearGradient id={id} from="#351CAB" to="#621A61" rotate="-45" />
  ),
  GradientLightgreenGreen,
  GradientPurpleOrange,
  GradientTealBlue,
  GradientPurpleRed,
  GradientDarkgreenGreen
];

function Example({ width, height, margin = defaultMargin }) {
  const numColumns = width > 600 ? 5 : 2;
  const numRows = Gradients.length / numColumns;
  const columnWidth = Math.max(width / numColumns, 0);
  const rowHeight = Math.max((height - margin.bottom) / numRows, 0);

  return (
    <svg width={width} height={height}>
      {Gradients.map((Gradient, index) => {
        const columnIndex = index % numColumns;
        const rowIndex = Math.floor(index / numColumns);
        const id = `visx-gradient-demo-${index}-${rowIndex}${columnIndex}`;

        return (
          <React.Fragment key={id}>
            <Gradient id={id} />
            <Bar
              fill={`url(#${id})`}
              x={columnIndex * columnWidth}
              y={rowIndex * rowHeight}
              width={columnWidth}
              height={rowHeight}
              stroke="#ffffff"
              strokeWidth={8}
              rx={14}
            />
          </React.Fragment>
        );
      })}
    </svg>
  );
}

export default function App() {
  return (
    <div className="App">
      <Example width={500} height={300} />
    </div>
  );
}

We import the gradient components from the @visx/gradient module.

Then we put them in the Gradients array.

We can use the imported components directly.

Or we can create a function that returns the RadiantGradient to create the radiant gradient effect.

We can also use the LinearGradient component to create a linear gradient.

In the Example component, we call Gradients.map to render the components in the map callback.

We separate the gradients with a white bar by adding the Bar component.

Conclusion

We can add gradients into our React app with the Visx library.