Take434 / Appollon

Tracker for your Spotify
https://appollon.vercel.app
0 stars 0 forks source link

Frontend Pages #8

Open Take434 opened 1 year ago

Take434 commented 1 year ago

Create the necessary pages according to the figma prototype. Pages:

Take434 commented 1 year ago

Current Progress


I have created the following frontend Pages:

There is a differentiation between the layout of pages for users who are logged in and those who are not. Users that are logged in, have a header and navbar. This is achieved by adding those pages in a sub-directory (loggedin). But there is currently no logic implemented to check whether a user is logged in or not, this will be the topic of another issue.

I also added the following components:

While I added all the currently needed pages, I only finished designing the login page and the personal statistics. The personal stats page is currently only mocked.

Problem with boxplots

The problem with the boxplot is that there are different scales for different boxplots. The Boxplot component has to support atleast three different scales:

I am rendering the boxplots as SVGs, but due to the different scales, that have to be supported, it is necessary to normalize the data, but I am not yet sure how to correctly do this.

Take434 commented 1 year ago

The Boxplot component

This comment aims to describe the boxplot component and how the boxplots are generated from the pure data. It was quite difficult to achive this, therefore it feels necessary to document.

Normalizing the data

In order to visuallize the data correctly, it was necessary to transform it depending on the scale provided.

data.sort((a, b) => a - b);
  scale.sort((a, b) => a - b);
  data = data.map(
    (x) => ((x - scale[0]) / (scale[scale.length - 1] - scale[0])) * 92 + offset
  );

This is done with this mapping function. The first thing, that has to be done, is to sort the data and scale. This lets us find the maximum minimum and quartiles, as well as the median. It also allows us to guarantee, that the scale is in correct order.

After that, the data is normalized in relation to the scale, this is achieved via this formula $$\frac{x - minScala}{maxScala - minScala} * 92 + offset$$ The offset and multiplication with 92 will be explained further down.

important metrics to create the plot

After the data was normalized, I can now get the important points needed to create the boxplot.

const min = data[0];
const max = data[data.length - 1];
const q1 = data[Math.floor(data.length / 4)];
const q3 = data[Math.floor((3 * data.length) / 4)];
const iqr = q3 - q1;
const lowerWhisker = Math.max(min, q1 - 1.5 * iqr);
const upperWhisker = Math.min(max, q3 + 1.5 * iqr);

fake padding

Because the boxplot will be cut if it starts at 0% and ends at 100% width of the svg I had to fake padding. In order to do this, I added the offset variable, which is added onto all placement calculations, this lets me move the whole plot and scale to the right. Another thing I did, was to multiply the data by 92 instead of 100, so that it doesnt take the full width of the svg. But for this to stay consistent with the scale, the scale-steps, which are used to draw the scale must also be generated as follows:

scaleStep: 92 / (scale.length - 1)

putting it all together

With all of the work above done, I can now simply use the values from above to generate an svg.

return (
    <div className="w-full">
      <svg width={props.svgWidth} height={props.svgHeight}>
        <line
          x1={`${props.lowerWhisker}%`}
          y1="0%"
          x2={`${props.lowerWhisker}%`}
          y2="60%"
          stroke={props.textColor}
          strokeWidth="2"
        />
        <line
          x1={`${props.upperWhisker}%`}
          y1="0%"
          x2={`${props.upperWhisker}%`}
          y2="60%"
          stroke={props.textColor}
          strokeWidth="2"
        />
...

Here I use all the variables from above to ensure that the positions are correct.

text centering

The only problem remaining with this, was centering the text of the scale. But this could be solved by nesting svgs and centering the text within those:

{
  //generate the steps of the scale
  props.scale.map((step, index) => (
    <g key={index}>
      <line
        x1={`${index * props.scaleStep + props.offset}%`}
        y1="79%"
        x2={`${index * props.scaleStep + props.offset}%`}
        y2="85%"
        stroke={props.textColor}
        strokeWidth="1"
      />
      <svg
        x={`${
          index * props.scaleStep + props.offset - props.scaleStep / 2
        }%`}
        y="85%"
        width={`${props.scaleStep}%`}
        height="30%"
        fill={props.textColor}
      >
        <text
          x="50%"
          y="50%"
          fontSize="1rem"
          textAnchor="middle"
          fill={props.textColor}
          textRendering={"geometricPrecision"}
        >
          {step}
        </text>
      </svg>
    </g>
  ))
}

The result

The component can now receive data, a scale and color values. The data is than normalized according to the scale and an svg is rendered using the colors provided.

<BoxPlotWithoutPopover
  data={[
    2.323, 2, 4, 2, 1.43, 2.12, 5.323, 1.123, 2.234, 3.092, 2.32, 3.23,
    2.32,
  ]}
  scale={[1, 2, 3, 4, 5, 6, 7]}
  textColor={theme.extend.colors.textDark}
  boxColor={theme.extend.colors.primary}
  medianColor={theme.extend.colors.textLight}
  svgHeight={100}
  svgWidth={384}
/>
<BoxPlotWithoutPopover
  data={[
    80, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 90, 70, 80, 90,
    100, 100, 100, 180, 180, 180, 170, 150,
  ]}
  scale={[60, 80, 100, 120, 140, 160, 180]}
  textColor={theme.extend.colors.textDark}
  boxColor={theme.extend.colors.primary}
  medianColor={theme.extend.colors.textLight}
  svgHeight={100}
  svgWidth={384}
/>
<BoxPlotWithoutPopover
  data={[
    90, 100, 110, 120, 130, 140, 150, 160, 90, 90, 140, 160, 150, 160,
    100, 100, 100, 180, 180, 180, 170, 150,
  ]}
  scale={[60, 80, 100, 120, 140, 160, 180]}
  textColor={theme.extend.colors.textDark}
  boxColor={theme.extend.colors.primary}
  medianColor={theme.extend.colors.textLight}
  svgHeight={100}
  svgWidth={384}
/>

Take434 commented 1 year ago

Logo and Loading Animation

I have added the Icon for the app.

Aswell as a component which should be displayed when the application is loading. This component contains the full Logo of the app.

Which is then animated with react-spring to have the vinyl roatating and the arm moving.

More on the Logo and Icon Design will be documented in the wiki.