7
\$\begingroup\$

I have not done Javascript programming in almost a decade. I see that it has changed a lot. Below is code that displays colourful block digits. Any constructive feedback is welcome.

function getRandomColor() {
  const colours = ["#FF6663", "#94FF63", "#63FFEA", "#A763FF", "#F2FF63"];
  return colours[Math.floor(Math.random() * colours.length)];
}

class Square extends React.Component {
  render() {
    var squareStyle = {
      width: 25,
      height: 25,
      backgroundColor: this.props.colour || getRandomColor(),
      display: "inline-block"
    };

    return (
      <div style={squareStyle}>
      </div>
    );
  }
}

class Digit extends React.Component {
  render() {
    const divStyle = {
      display: "inline-block",
      lineHeight: 0,
      paddingLeft: "10px",
      paddingRight: "10px"
    };

    const filled = [
      [
        [1, 1, 1],
        [1, 0, 1],
        [1, 0, 1],
        [1, 0, 1],
        [1, 1, 1]
      ],
      [
        [0, 0, 1],
        [0, 0, 1],
        [0, 0, 1],
        [0, 0, 1],
        [0, 0, 1]
      ],
      [
        [1, 1, 1],
        [0, 0, 1],
        [1, 1, 1],
        [1, 0, 0],
        [1, 1, 1]
      ],
      [
        [1, 1, 1],
        [0, 0, 1],
        [1, 1, 1],
        [0, 0, 1],
        [1, 1, 1]
      ],
      [
        [1, 0, 1],
        [1, 0, 1],
        [1, 1, 1],
        [0, 0, 1],
        [0, 0, 1]
      ],
      [
        [1, 1, 1],
        [1, 0, 0],
        [1, 1, 1],
        [0, 0, 1],
        [1, 1, 1]
      ],
      [
        [1, 1, 1],
        [1, 0, 0],
        [1, 1, 1],
        [1, 0, 1],
        [1, 1, 1]
      ],
      [
        [1, 1, 1],
        [0, 0, 1],
        [0, 0, 1],
        [0, 0, 1],
        [0, 0, 1]
      ],
      [
        [1, 1, 1],
        [1, 0, 1],
        [1, 1, 1],
        [1, 0, 1],
        [1, 1, 1]
      ],
      [
        [1, 1, 1],
        [1, 0, 1],
        [1, 1, 1],
        [0, 0, 1],
        [1, 1, 1]
      ]
    ];

    let rows = [];

    for (let y = 0; y < 5; y++) {
      for (let x = 0; x < 3; x++) {
        let objKey = y * 5 + x;

        if (filled[this.props.digit][y][x]) {
          rows.push(
            <Square key={objKey} />
          );
        }
        else {
          rows.push(
            <Square colour="#FFFFFF" key={objKey} />
          );
        }
      }

      rows.push(
        <br key={y + "-br"} />
      );
    }

    return (
      <div style={divStyle}>
        {rows}
      </div>
    );
  }
}

ReactDOM.render(
  <div>
    <Digit digit={0} />
    <Digit digit={5} />
    <Digit digit={9} />
  </div>,
  document.getElementById("root")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

To use, add <Digit digit={n}/> (where 0 <= n <= 9). In the future, I plan to change class Digit to class Character (which supports 0-9, A-Z and some punctuation characters). I will change the implementation to something like:

filled = {
    "A": [
        [0, 1, 0],
        [1, 0, 1],
        [1, 1, 1],
        [1, 0, 1],
        [1, 0, 1]
    ]
}

For those curious, this is what the digits 0 to 9 look like:

digits

(the 1 is right-aligned like the 1 on a digital clock face)

\$\endgroup\$

3 Answers 3

4
+50
\$\begingroup\$

Consistency

Sometimes the word color or colour is used to describe the look of a Square. Because I'm not a native English speaker I looked it up on wikipedia:

color (American English), or colour (Commonwealth English)

The Algorithm

Currently Digit calculates the form of a concrete digit based on the prop with the name digit. The calculation is inside a nested for-loop with a time complexity of \$O(n)\$. But actually the calculation is redundant since the form of a digit is known on run-time so we can reduce the time complexity (see final result, where we do not need to loop).

The form of a digit is already defined inside the array filled:

const filled = [
     [
       [1, 1, 1],
       [1, 0, 1],
       [1, 0, 1],
       [1, 0, 1],
       [1, 1, 1]
     ],
     /* .... */
]

But instead of define the form based on boolean values it is possible to insert the concrete React-Components to avoid the nested for-loops:

const filled = [
  [
    [<Square />, <Square />, <Square />, <br />],
    [<Square />, <Square colour="#FFFFFF"/>, <Square />, <br />],
    [<Square />, <Square colour="#FFFFFF"/>, <Square />, <br />],
    [<Square />, <Square colour="#FFFFFF"/>, <Square />, <br />],
    [<Square />, <Square />, <Square />]
  ]    
];

Expand to see the working example below

function getRandomColor() {
  const colours = ["#FF6663", "#94FF63", "#63FFEA", "#A763FF", "#F2FF63"];
  return colours[Math.floor(Math.random() * colours.length)];
}

class Square extends React.Component {
  render() {
    var squareStyle = {
      width: 25,
      height: 25,
      backgroundColor: this.props.colour || getRandomColor(),
      display: "inline-block"
    };

    return (
      <div style={squareStyle}>
      </div>
    );
  }
}

class Digit extends React.Component {
  render() {
    const divStyle = {
      display: "inline-block",
      lineHeight: 0,
      paddingLeft: "10px",
      paddingRight: "10px"
    };

    const filled = [
      [
        [<Square />, <Square />, <Square />, <br />],
        [<Square />, <Square colour="#FFFFFF"/>, <Square />, <br />],
        [<Square />, <Square colour="#FFFFFF"/>, <Square />, <br />],
        [<Square />, <Square colour="#FFFFFF"/>, <Square />, <br />],
        [<Square />, <Square />, <Square />]
      ]    
    ];

    return (
      <div style={divStyle}>
        {filled[this.props.digit]}
      </div>
    );
  }
}

ReactDOM.render(
  <div>
    <Digit digit={0} />
  </div>,
  document.getElementById("root")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Composition

Inside Reacts Documentation on composition is described how it can be used effectively.

Square

Currently the Square-Component can have random colors or a concrete color based on the prop. (In my opinion this is against the Single-Responsibility Principle ).

By extract the color behavior into separate components we can make the code more declarative:

const filled = [
      [
        [<RandomColoredSquare />, <RandomColoredSquare />, <RandomColoredSquare />, <br />],
        [<RandomColoredSquare />, <EmptySquare />, <RandomColoredSquare />, <br />],
        [<RandomColoredSquare />, <EmptySquare />, <RandomColoredSquare />, <br />],
        [<RandomColoredSquare />, <EmptySquare />, <RandomColoredSquare />, <br />],
        [<RandomColoredSquare />, <RandomColoredSquare />, <RandomColoredSquare />]
      ],
      /* ... */    
];

RandomColoredSquare and EmptySquare are specializations of Spuare:

const Square = ({color}) => 
  <div style={{
      width: 25,
      height: 25,
      backgroundColor: color,
      display: "inline-block"
    }}>
  </div>

const RandomColoredSquare = ({colors = ["#FF6663", "#94FF63", "#63FFEA", "#A763FF", "#F2FF63"]}) => 
  <Square color={colors[Math.floor(Math.random() * colors.length)]} />

const EmptySquare = _ => 
  <Square color="#FFF" />

I used the the destructuring assignment to make the code more readable. Instead of const Square = ({color}) I could have written const Square = (props) but then if have to query the color with props.color and now I can use directly the color.

Additionally I make use of the default parameters together with the destructuring assignment for ({colors = ["#FF6663", "#94FF63", "#63FFEA", "#A763FF", "#F2FF63"]}).

Expand to see the working example below

const Square = ({color}) => 
  <div style={{
      width: 25,
      height: 25,
      backgroundColor: color,
      display: "inline-block"
    }}>
  </div>

const RandomColoredSquare = ({colors = ["#FF6663", "#94FF63", "#63FFEA", "#A763FF", "#F2FF63"]}) => 
  <Square color={colors[Math.floor(Math.random() * colors.length)]} />

const EmptySquare = _ => 
  <Square color="#FFF" />

class Digit extends React.Component {
  render() {
    const divStyle = {
      display: "inline-block",
      lineHeight: 0,
      paddingLeft: "10px",
      paddingRight: "10px"
    };

    const filled = [
      [
        [<RandomColoredSquare />, <RandomColoredSquare />, <RandomColoredSquare />, <br />],
        [<RandomColoredSquare />, <EmptySquare />, <RandomColoredSquare />, <br />],
        [<RandomColoredSquare />, <EmptySquare />, <RandomColoredSquare />, <br />],
        [<RandomColoredSquare />, <EmptySquare />, <RandomColoredSquare />, <br />],
        [<RandomColoredSquare />, <RandomColoredSquare />, <RandomColoredSquare />]
      ]    
    ];

    return (
      <div style={divStyle}>
        {filled[this.props.digit]}
      </div>
    );
  }
}

ReactDOM.render(
  <div>
    <Digit digit={0} />
  </div>,
  document.getElementById("root")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Digit

I think <Digit digit={0} /> is a less intuitiv API then if we could simply write <Zero /> which would make our code more declarative at the first glance. Additionally Digit violates the Open-Close Principle.

We can first describe the more generic component Digit and composite it the "special" components Zero, One and so on:

const Zero = _ => 
  <Digit>
    <RandomColoredSquare />
    <RandomColoredSquare />
    <RandomColoredSquare />

    <br />

    /* ... */
  </Digit>

const Digit = ({children}) => 
  <div style={{
      display: "inline-block",
      lineHeight: 0,
      paddingLeft: "10px",
      paddingRight: "10px"
    }}>
    {children}
  </div>

For Digit I used the Children in JSX where it is possible to pass Components inside a component instead vie props.

Expand to see the working example below

const Square = ({color}) => 
  <div style={{
      width: 25,
      height: 25,
      backgroundColor: color,
      display: "inline-block"
    }}>
  </div>

const RandomColoredSquare = ({colors = ["#FF6663", "#94FF63", "#63FFEA", "#A763FF", "#F2FF63"]}) => 
  <Square color={colors[Math.floor(Math.random() * colors.length)]} />

const EmptySquare = _ => 
  <Square color="#FFF" />

const Zero = _ => 
  <Digit>
    <RandomColoredSquare />
    <RandomColoredSquare />
    <RandomColoredSquare />
  
    <br />
  
    <RandomColoredSquare />
    <EmptySquare />
    <RandomColoredSquare />
  
    <br />
  
    <RandomColoredSquare />
    <EmptySquare />
    <RandomColoredSquare />
  
    <br />
  
    <RandomColoredSquare />
    <EmptySquare />
    <RandomColoredSquare />
  
    <br />
  
    <RandomColoredSquare />
    <RandomColoredSquare />
    <RandomColoredSquare />
  </Digit>

const Digit = ({children}) => 
  <div style={{
      display: "inline-block",
      lineHeight: 0,
      paddingLeft: "10px",
      paddingRight: "10px"
    }}>
    {children}
  </div>

ReactDOM.render(
  <div>
    <Zero />
  </div>,
  document.getElementById("root")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

But now we have this explicite <br />. We could introduce a new Component Row to make it implicit:

const Row = ({children}) =>
  <div>
    {children}
    <br />
  </div>

const Zero = _ => 
  <Digit>
    <Row>
      <RandomColoredSquare />
      <RandomColoredSquare />
      <RandomColoredSquare />
    </Row>

    <Row>
      /* ... */
    </Row>

    /* ... */

  </Digit>

Expand to see the working example below

const Square = ({color}) => 
  <div style={{
      width: 25,
      height: 25,
      backgroundColor: color,
      display: "inline-block"
    }}>
  </div>

const RandomColoredSquare = ({colors = ["#FF6663", "#94FF63", "#63FFEA", "#A763FF", "#F2FF63"]}) => 
  <Square color={colors[Math.floor(Math.random() * colors.length)]} />

const EmptySquare = _ => 
  <Square color="#FFF" />

const Row = ({children}) =>
  <div>
    {children}
    <br />
  </div>

const Zero = _ => 
  <Digit>
    <Row>
      <RandomColoredSquare />
      <RandomColoredSquare />
      <RandomColoredSquare />
    </Row>
  
    <Row>
      <RandomColoredSquare />
      <EmptySquare />
      <RandomColoredSquare />
    </Row>
  
    <Row>
      <RandomColoredSquare />
      <EmptySquare />
      <RandomColoredSquare />
    </Row>
  
    <Row>
      <RandomColoredSquare />
      <EmptySquare />
      <RandomColoredSquare />
    </Row>
  
    <Row>
      <RandomColoredSquare />
      <RandomColoredSquare />
      <RandomColoredSquare />
    </Row>
  </Digit>

const Digit = ({children}) => 
  <div style={{
      display: "inline-block",
      lineHeight: 0,
      paddingLeft: "10px",
      paddingRight: "10px"
    }}>
    {children}
  </div>

ReactDOM.render(
  <div>
    <Zero />
  </div>,
  document.getElementById("root")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Final Result

The result is longer since we write the digits explicit instead of create them dynamic but with this way we have a much better time complexity and the code is easier to understand since we remove the computational logic and make the code more declarative.

const Square = ({color}) => 
  <div style={{
      width: 25,
      height: 25,
      backgroundColor: color,
      display: "inline-block"
    }}>
  </div>

const RandomColoredSquare = ({colors = ["#FF6663", "#94FF63", "#63FFEA", "#A763FF", "#F2FF63"]}) => 
  <Square color={colors[Math.floor(Math.random() * colors.length)]} />

const EmptySquare = _ => 
  <Square color="#FFF" />

const Row = ({children}) =>
  <div>
    {children}
    <br />
  </div>

const Zero = _ => 
  <Digit>
    <Row>
      <RandomColoredSquare />
      <RandomColoredSquare />
      <RandomColoredSquare />
    </Row>
  
    <Row>
      <RandomColoredSquare />
      <EmptySquare />
      <RandomColoredSquare />
    </Row>
  
    <Row>
      <RandomColoredSquare />
      <EmptySquare />
      <RandomColoredSquare />
    </Row>
  
    <Row>
      <RandomColoredSquare />
      <EmptySquare />
      <RandomColoredSquare />
    </Row>
  
    <Row>
      <RandomColoredSquare />
      <RandomColoredSquare />
      <RandomColoredSquare />
    </Row>
  </Digit>

const Three = _ =>
  <Digit>
    <Row>
        <RandomColoredSquare />
        <RandomColoredSquare />
        <RandomColoredSquare />
    </Row>
  
    <Row>
        <EmptySquare />
        <EmptySquare />
        <RandomColoredSquare />
    </Row>
  
    <Row>
        <EmptySquare />
        <RandomColoredSquare />
        <RandomColoredSquare />
    </Row>
  
    <Row>
        <EmptySquare />
        <EmptySquare />
        <RandomColoredSquare />
    </Row>
  
    <Row>
        <RandomColoredSquare />
        <RandomColoredSquare />
        <RandomColoredSquare />
    </Row>
  </Digit>

const Digit = ({children}) => 
  <div style={{
      display: "inline-block",
      lineHeight: 0,
      paddingLeft: "10px",
      paddingRight: "10px"
    }}>
    {children}
  </div>

ReactDOM.render(
  <div>
    <Zero />
    <Three />
    <Zero />
  </div>,
  document.getElementById("root")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

\$\endgroup\$
5
  • \$\begingroup\$ In your final result, what would you suggest that I export? Square, Row, and Digit? \$\endgroup\$ Commented Dec 28, 2019 at 4:06
  • 1
    \$\begingroup\$ If you consider the number of squares per digit as n then the OPs code per character is not O(n^2) as nested loops do not automatically square complexity. Neither is your example O(1) as each one of the n squares still need to be rendered which is unavoidable. Both the OP's and your example are O(n). There is no O(1) solution to this task. \$\endgroup\$ Commented Dec 28, 2019 at 5:53
  • \$\begingroup\$ @Blindman67, I thought it would be valid to ignore the concrete constants.. I will edit my posts. But the assumption about a better time complexity compared to the final result is correct? \$\endgroup\$ Commented Dec 28, 2019 at 16:54
  • \$\begingroup\$ @FromTheStackAndBack - The export depends on the goal. If you want to create a library where a customer can create his own customizable digit, I would export square, rowand digit besides your own implementations. The other option is to restrict the client to the digits and types of `squares, etc. you want to provide. \$\endgroup\$ Commented Dec 28, 2019 at 17:03
  • \$\begingroup\$ The scalar ( > 0 ) does not effect complexity, 1 times n, 100 times n, or 1billion times n all have the same time complexity O(n). The final result has not changed the complexity of the solution. Nor do i see that your approach is at all practical. The number of characters is likely to be large 0-1, A-Z, and punctuation. The better solution is to have the chars as a string `<ColText text = "012345,ABCDEFG" /> and using the char code to lookup each char. A single char can be encoded in a single number CharCode 48, is 0 with 15 pixels 0x7b6f, and 1 is 0x1249 \$\endgroup\$ Commented Dec 28, 2019 at 17:56
5
\$\begingroup\$
  • squareStyle variable should be initialized in constructor instead of render. Reason being, render is called on each state update and so, if your render is called multiple times, the variable inside render will be computed again which will cause the existing colours of the digits to change on each and will be unpleasant to look.
  • Usage of var is replaced by let and consts

Modified Code

class Square extends React.Component {
  constructor(props) {
    super(props)
    this.squareStyle = {
      width: 25,
      height: 25,
      backgroundColor: props.colour || getRandomColor(),
      display: "inline-block"
    };
  }
  render() {
    return (
      <div style={this.squareStyle}>
      </div>
    );
  }
}
\$\endgroup\$
3
\$\begingroup\$

Data entry

I am not much of a React fan so this is not a how to React, rather, fonts are best handled as sets of pixels in the simplest form possible. Fonts are subject to change and it should be as easy as possible to make changes.

There is nothing worse than having a complicated long winded data entry process to define visual content. The easier it is to create the more time you have to be creative, and the more likely you are willing to put in creative time.

Personally I would have defined the Text to be rendered in a low res canvas scaled to make the pixel size to what is desired and contain all the text. It would use much less memory and be rendered much quicker, but I assume your code is React practice, so will keep it as <Square>, <Character>, and <ColText>

The example

The char 0 is

111
1 1
1 1
1 1
111

Can be made a string "111101101101111" (or Number 0b111101101101111) and using a template to position line breaks "00b00b00b00b00" (0 is a pixel, b is a break) the conversion from string to displayed pixels is easy.

As the characters "M" "N" and "W" can not be displayed in a 3 pixel wide font the example uses the string length to get the correct template and then builds variable width characters.

I used a JavaScript Map that maps the pixel strings to each character. Characters not found are replaced with space.

Rather than define an element for each character the object ColText has the property text and size to define the string of characters to display and the height in CSS pixels to display the font.

const CHAR_HEIGHT = 5, charMap = new Map();
const PALLET = ["#F66","#DC3","#0A4","#AC4","#4AD","#4AA"];
const CHARS = [/* Variable width chars, Space, 0-9, A-Z */ "0000000000", "111101101101111", "0101010101", "111001111100111",  "111001011001111", "100100101111001", "111100111001111", "111100111101111", "111001001001001", "111101111101111", "111101111001111", "011101111101101", "110101111101110", "010101100101010", "110101101101110", "111100110100111", "111100110100100",   "111100100101111", "101101111101101", "111010010010111", "111001001101010",  "101101110101101", "100100100100111", "1000111011101011000110001", "10011101110110111001", "111101101101111", "110101110100100", "111101101111110", "110101110101101", "011100111001110", "111010010010010", "101101101101110", "101101101010010", "1000110001101010101001010", "101101010101101", "101101011001010", "111001010100111" ];
[..." 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"].forEach((c, i) => charMap.set(c, CHARS[i]));
const TEMPLATES = ["bbbb",  "0b0b0b0b0", "00b00b00b00b00",  "000b000b000b000b000", "0000b0000b0000b0000b0000", "00000b00000b00000b00000b00000",  "000000b000000b000000b000000b000000"];
const buildChar = (char, size) => {
    const pixels = charMap.get(charMap.has(char) ? char : " ");
    let i = 0;
    return [...TEMPLATES[pixels.length / CHAR_HEIGHT | 0]].map(t => (
        t === "0" ? 
          pixels[i++]==="1" ? <Square size={size}/> : <Square colour="#FFF" size={size}/> : 
          <br />
    ));
};
class Square extends React.Component {
  render() {
    return (<div style={{width: this.props.size, height: this.props.size, display: "inline-block", backgroundColor: this.props.colour || PALLET[Math.random() * PALLET.length | 0]}}></div> );
  }
}
class Character extends React.Component {
   render() {
     const pad = Math.max(3, this.props.size / CHAR_HEIGHT / 2.5) + "px";
     return (<div style={{display: "inline-block", lineHeight: 0, paddingLeft: pad, paddingRight: pad}}> 
        {buildChar(this.props.char, this.props.size / CHAR_HEIGHT)}  </div>);
  }
} 
class ColText extends React.Component {
  render() {
    return (<div> {[...this.props.text].map(char => <Character char={char} size={this.props.size} />)} </div>);
  }
}
ReactDOM.render(
  <div> <ColText size="50" text="HI THERE 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ" /><ColText size="25" text="GROOVY"/> </div>,
  document.getElementById("root")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.