Visualizing the 71 Times Apple Has Been Declared Dead with React and Nivo
Tags: react, javascript, dataviualization, nivo
Published: 2020-03-13 20:05:52


*This was originally published on [codinhood.com](https://codinhood.com/post/visualizing-apple-deaths)*


Doomsayers have been declaring the death of Apple for decades. Apple is not unique in this regard, of course. Every company has had its share of dedicated detractors announcing their demise. Blockbuster's end was predicated for years before it formally announced bankruptcy, while Bitcoin has been declared dead over [380 times](https://99bitcoins.com/bitcoin-obituaries/) since 2010. Some harbingers were right, some were wrong. This article is not here to pass judgment on those predictions or prophesize the future of Apple but simply to visualize some fun data points using Javascript, React, and Nivo Charts.

If you're only interested in the charts themselves and not how they were made then checkout the [Apple Doomsayers Demo Page](https://codinhood.com/demos/apple-doomsayers).

## Apple Death Knell Data

The data is sourced from The Mac Observer's excellent [Apple Death Knell Counter](https://www.macobserver.com/features/death-knell/), which is a collection of predictions from public figures, mostly journalists, about the end of Apple. The Mac Observer staff curates the list to include only instances where the speaker is specifically stating the demise and doom of Apple and not include a simply negative sentiment.

> Articles where Apple is referred to as "beleaguered" are not necessarily eligible, and in fact, the vast majority of such references are simply colorfully negative descriptions that do not qualify as an Apple Death Knell. - The Mac Observer

Unfortunately, some of the earliest death knells no longer link to live web pages. A few of these can be found on the [Wayback Machine](https://archive.org/web/web.php), but others cannot. For visualization purposes, we're just going to trust that all accounts are accurate.

I scraped the data from MacObserver using [Beautiful Soup](https://pypi.org/project/beautifulsoup4/) to scrape the `Title`, `Author`, `Date`, `Source`, and `Publisher` for each entry.

![json data deaths](https://codinhood.com/static/aac07374dad8e471a006119b4cc34c1b/32b35/json-data.png)

This gives us an array of 71 objects where each object represents one predication. From this data alone we can create our first graph. Rather than build these charts from scratch, we're going to use [Nivo](https://nivo.rocks/), which provides a set of highly customizable React components built on [D3](https://d3js.org/) for visualizing data. We're also going to use [Day.js](https://github.com/iamkun/dayjs) for dealing with dates and times.

## Deaths Per Year - Data Transform

What we want to display is the number of deaths per year, which means we need to transform our array of deaths into an array of objects with two keys, one for the year of deaths and one for the number of deaths. Since Nivo requires data along the `x-axis` to be named `x` and data along the `y-axis` to be named `y`, we'll add the year to `x` and number of deaths to `y`.

```js
function calculateYearsBetween(startYear) {
  let currentYear = new Date().getFullYear();
  let years = [];
  startYear = startYear;
  while (startYear <= currentYear) {
    years.push({ x: startYear, y: 0 });
    startYear++;
  }
  return years;
}
```

Next, create a function that loops through the death array and increments the correct year in the `yearArray`. Nivo again requires a special object format that includes a unique `id` to name the chart and `data` property to contain the actual data we want to visualize, which is the `yearArray`.

```js
function deathsPerYear() {
  const yearArray = calculateYearsBetween(1994);

  appleDeaths.forEach((death, index) => {
    const dayjsDate = dayjs(death.Date);
    const yearOfDeath = dayjsDate.get("year");
    const inYearArray = yearArray.findIndex(year => {
      return year.year == yearOfDeath;
    });
    yearArray[inYearArray].y++;
  });

  const formattedData = [{ id: "deaths", data: yearArray }];
  return formattedData;
}
```

The result of the two functions above is an array that looks like this:

```js
[
    id: 'deaths',
    data: [
        {x: 1994: y: 0},
        {x: 1995: y: 1},
        {x: 1996: y: 2},
        {x: 1997: y: 7},
        ...
    ]
]
```

## Deaths Per Year - Charts with Nivo

Using Nivo's [Line](https://nivo.rocks/line/) chart we can graph the above data for each year. Although Nivo charts have props for practically every part of the graph, all we need to get started is a defined height and width. We'll also define a container with `overflow: auto` so we can see the full chart on mobile by scrolling.

```jsx
import { Line } from "@nivo/line";
import React from "react";
import { deathsPerYear } from "./data";

const DeathsPerYearGraph = () => {
  const newData = deathsPerYear();

  return (
    <div style={styles.container}>
      <Line data={newData} margin width={780} height={500} />
    </div>
  );
};

const styles = {
  container: {
    height: 500,
    maxWidth: 780,
    overflow: "auto",
  },
};

export { DeathsPerYearGraph };
```

![Deaths Per Year Graph](https://dev-to-uploads.s3.amazonaws.com/i/ddgrrgwdx70mm0qvhsmy.png)

### Nivo Chart Margins

The first problem is that there is no margin between the graph itself and the edge of the container, which means the row and column labels are hidden. The `margin` prop allows us to define this margin and reveal the labels.

```jsx
...
<Line data={newData} width={780} height={500} margin={styles.chartMargin} />
...
chartMargin: {
    top: 50,
    right: 50,
    bottom: 50,
    left: 60,
},
...
```

![Deaths Per Year margins](https://dev-to-uploads.s3.amazonaws.com/i/97zolu5ua5ov0luki20t.png)

### Nivo Axis Legends and Grid Lines

By default, Nivo charts do not have axis legends, but we can add a legend to any side of the chart using `axisBottom`, `axisLeft`, `axisRight`, and `axisTop` props. These props take in an object with various properties that allow us to, among other things, add legends, position legends, define axis tick size, padding, and rotation.

We can also remove the grid lines, which I think are distracting, by passing `false` to both the `enableGridX` and `enableGridY` prop.

```jsx
...
<Line
    data={newData}
    width={780}
    height={500}
    margin={styles.chartMargin}
    enableGridX={false}
    enableGridY={false}
    axisBottom={styles.axisBottom}
    axisLeft={styles.axisLeft}
/>
...
axisLeft: {
    orient: "left",
    tickSize: 5,
    tickPadding: 5,
    tickRotation: 0,
    legend: "Apple Deaths",
    legendOffset: -40,
    legendPosition: "middle",
},
axisBottom: {
    orient: "bottom",
    tickSize: 5,
    tickPadding: 5,
    tickRotation: 0,
    legend: "Year",
    legendOffset: 36,
    legendPosition: "middle",
},
...
```

![Deaths Per Year Axis](https://dev-to-uploads.s3.amazonaws.com/i/et1pzp3izt511vrnzh94.png)

### Nivo Theme and Points

Out-of-the-box Nivo charts can utilize color schemes from the [d3-scale-chromotic](https://github.com/d3/d3-scale-chromatic) module, but we can define a completely custom theme by passing in an array of colors to the `colors` prop. Passing in one color will define the color for the line, however, it will not define the color of the actual data points, that is, the circles on the line. To change the point size and color we can use the aptly named, `pointSize` and `pointColor` props.

Nivo charts also accept a `theme` prop that will allow us to style the text color and text size to make it readable on dark backgrounds.

```jsx
...
<Line
    data={newData}
    width={780}
    height={500}
    margin={styles.chartMargin}
    enableGridX={false}
    enableGridY={false}
    axisBottom={styles.axisBottom}
    axisLeft={styles.axisLeft}
    colors={["#03e1e5"]}
    theme={styles.theme}
    pointSize={10}
    pointColor={"#03e1e5"}
/>
...
theme: {
    fontSize: "14px",
    textColor: "#7b7b99",
},
...
```

![Deaths Per Year Colors](https://dev-to-uploads.s3.amazonaws.com/i/0m9id2s66luu8zaio4gn.png)

### Nivo X-Axis Scale

Now that the labels are much easier to read, you'll notice that the x-axis column names are overlapping. The chart is trying to show a label for every single data point along the x-axis, which is the default behavior for scale type `point`. We can change the x-axis scale type with the property `xScale`. In this case, we want to change the scale type to `linear`, which will display labels evenly across a specific range. If do not provide that range, then the chart will start from 0 and show equal increments to our end date 2020. But we don't care about dates before 1994 (where our data starts), so we need to set a minimum date for the x-axis to start at, which we can do using the `min` property.

```jsx
<Line
  data={newData}
  width={780}
  height={500}
  margin={styles.chartMargin}
  enableGridX={false}
  enableGridY={false}
  axisBottom={styles.axisBottom}
  axisLeft={styles.axisLeft}
  colors={["#03e1e5"]}
  theme={styles.theme}
  pointSize={10}
  pointColor={"#03e1e5"}
  xScale={{ type: "linear", min: "1994" }}
/>
```

![Deaths Per Year Axis Fix](https://dev-to-uploads.s3.amazonaws.com/i/asj0x9g6cbpwzlmherwm.png)

### Nivo Hover Labels

Adding the `useMesh` prop will display a label next to each data point when you hover over it with the values for that data pont. To customize this hover label, however, we need to provide our own label component. First, create a custom label component, Tooltip, which takes `slice` as a prop. Nivo will pass each data point (slice) to this component with other useful information about the chart to create a custom label.

```jsx
const Tooltip = function({ slice }) {
  return (
    <div
      style={{
        background: "#09001b",
        padding: "9px 12px",
      }}
    >
      {slice.points.map(point => (
        <div
          key={point.id}
          style={{
            color: point.serieColor,
            padding: "3px 0",
          }}
        >
          <strong>{point.serieId}</strong> {point.data.yFormatted}
        </div>
      ))}
    </div>
  );
};
```

Now we can pass this custom Tooltip into the `sliceTooltip` prop with the `slice` prop. Also, enable custom tooltips (hover labels) by providing the `enableSlices` prop with the string, `'x'`

```jsx
<Line
  data={newData}
  width={780}
  height={500}
  margin={styles.chartMargin}
  enableGridX={false}
  enableGridY={false}
  axisBottom={styles.axisBottom}
  axisLeft={styles.axisLeft}
  colors={["#03e1e5"]}
  theme={styles.theme}
  pointSize={10}
  pointColor={"#03e1e5"}
  xScale={{ type: "linear", min: "1994" }}
  enableSlices="x"
  sliceTooltip={({ slice }) => {
    return <Tooltip slice={slice} />;
  }}
/>
```

Now when you hover over the chart, a tooltip will display the number of deaths.

![Deaths Per Year Tooltips](https://dev-to-uploads.s3.amazonaws.com/i/yuoqoqhcazkf6x66jyxg.png)

### Nivo Area Chart

We can easily convert this line chart into an Area chart by adding the `enableArea` prop.

```jsx
<Line
  data={newData}
  width={780}
  height={500}
  margin={styles.chartMargin}
  xScale={{ type: "linear", min: "1994" }}
  enableGridX={false}
  enableGridY={false}
  axisBottom={styles.axisBottom}
  axisLeft={styles.axisLeft}
  colors={["#03e1e5"]}
  pointSize={10}
  pointColor={"#03e1e5"}
  theme={styles.theme}
  enableSlices="x"
  sliceTooltip={({ slice }) => {
    return <Tooltip slice={slice} />;
  }}
  enableArea={true}
/>
```

![Deaths Per Year Area](https://dev-to-uploads.s3.amazonaws.com/i/0odbc3yvm740njcpjmrt.png)

### Nivo Highlight Markers

The last things we're going to add to this line chart are markers to highlight specific events in Apple's history on the chart to give more context. Nivo allows us to create vertical or horizontal lines with labels at any point on the chart by passing an array of objects to the `markers` prop. Each object in that array is a separate marker with properties that define which axis it should display along, the point or value to display, the style of the marker, and the text, if any, to show. Let's create three markers, one for the introduction of the iPod, the introduction of the iPhone, and Steven Job's death.

```jsx
const contextLines = [
  {
    axis: "x",
    value: 2011,
    lineStyle: { stroke: "#09646b", strokeWidth: 2 },
    legend: "Steven Jobs' Death",
    textStyle: {
      fill: "7b7b99",
    },
  },
  {
    axis: "x",
    value: 2007,
    lineStyle: { stroke: "#09646b", strokeWidth: 2 },
    legend: "iPhone",
    textStyle: {
      fill: "7b7b99",
    },
  },
  {
    axis: "x",
    value: 2001,
    lineStyle: { stroke: "#09646b", strokeWidth: 2 },
    legend: "iPod",
    textStyle: {
      fill: "7b7b99",
    },
    orient: "bottom",
    legendPosition: "top-left",
  },
];
```

```jsx
<Line
  data={newData}
  width={780}
  height={500}
  margin={styles.chartMargin}
  xScale={{ type: "linear", min: "1994" }}
  enableGridX={false}
  enableGridY={false}
  axisBottom={styles.axisBottom}
  axisLeft={styles.axisLeft}
  colors={["#03e1e5"]}
  pointSize={10}
  pointColor={"#03e1e5"}
  theme={styles.theme}
  enableSlices="x"
  sliceTooltip={({ slice }) => {
    return <Tooltip slice={slice} />;
  }}
  enableArea={true}
  markers={contextLines}
/>
```

![Deaths Per Year Markers](https://dev-to-uploads.s3.amazonaws.com/i/y6bsn0zbw6zl1u80j6vs.png)

## Deaths Per Author - Data Transform

The next chart will be a pie graph displaying the number of death predictions per author. First, similarly to the _deaths per year_ data, we need to transform the death array into an array that shows _deaths per author_. The Nivo pie chart expects the data to be an array of objects with each object containing an `id` and `value`. Creating this array will reveal that the vast majority of predictions were made by different people. To avoid graphing 50+ data points with only 1 value, we will filter the results for authors that have more than one prediction on our list using the [filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) method. Finally, we only want named authors so we will also filter out all "Unknow" authors.

```js
export function deathsPerAuthor() {
  const perAuthorArray = [];

  appleDeaths.forEach((death, index) => {
    if (index == 0) {
      perAuthorArray.push({ id: death.Author, value: 1 });
    }
    const inPerAuthorArray = perAuthorArray.findIndex(author => {
      return author.id == death.Author;
    });
    if (inPerAuthorArray > -1) {
      perAuthorArray[inPerAuthorArray].value++;
    } else {
      perAuthorArray.push({ id: death.Author, value: 1 });
    }
  });
  const filtered = perAuthorArray.filter(author => author.value > 1);
  return filtered;
}
```

The data will end up looking like this:

```js
[
  {
    id: "Michael Dell",
    value: 2,
  },
  ...
];
```

## Deaths Per Author - Nivo Pie Chart

We can create a simple `Pie` chart using the data above in a similar way to the line chart above. Remember, we need to set margins within the chart so nothing will get cut off. Let's also set the scheme to `set2`.

```jsx
import { Pie } from "@nivo/pie";
import React from "react";
import { deathsPerAuthor } from "./data";

const DeathsPerAuthor = ({ version }) => {
  const newData = deathsPerAuthor();

  return (
    <div style={styles.container}>
      <Pie
        data={newData}
        width={780}
        height={500}
        margin={styles.chartMargin}
        colors={{ scheme: "set2" }}
      />
    </div>
  );
};

const styles = {
  container: {
    height: 500,
    maxWidth: 780,
    background: "#09001b",
    overflow: "auto",
  },
  chartMargin: {
    top: 40,
    right: 80,
    bottom: 40,
    left: 80,
  },
};

export { DeathsPerAuthor };
```

![Deaths Per Author Pie](https://dev-to-uploads.s3.amazonaws.com/i/b8daa58z4yf4vnqsun9g.png)

### Nivo Donut Chart and Section Styles

Nivo allows us to create a donut chart by defining the size of the inner radius using the `innerRadius` prop, try playing around with this prop on Nivo's [interactive documentation for pie charts](https://nivo.rocks/pie/). We can add some padding in between each data section using the `padAngle` prop, which will make it easier to distinguish each section. The `cornerRadius` prop defines the radius of each section of the pie.

```jsx
<Pie
  data={newData}
  width={780}
  height={500}
  margin={styles.chartMargin}
  colors={{ scheme: "set2" }}
  animate={true}
  innerRadius={0.5}
  padAngle={0.7}
  cornerRadius={3}
  borderWidth={1}
  borderColor={{ from: "color", modifiers: [["darker", 0.2]] }}
/>
```

![Deaths Per Author Donut](https://dev-to-uploads.s3.amazonaws.com/i/ypo5t1b2dqkd1jzv0het.png)

### Nivo Radial Labels

The chart labels are hard to read on dark backgrounds, luckily Nivo gives ample customization of these labels. We can change the label color with the `radialLabelsTextColor` prop. The `radialLabelsLinkDiagonalLength` and `radialLabelsLinkHorizontalLength` props allow us to customize the exact length of each part of the line to the label, while `radialLabelsLinkStrokeWidth` defines the line's width. The `radialLabelsLinkColor` defines the color of the line, setting this to `from: "color"` will make the line match the color of the section it's coming from. Finally, we can also customize the spacing between the line, label, and data section, but I think the defaults are fine here.

```jsx
<Pie
  data={newData}
  width={780}
  height={500}
  margin={styles.chartMargin}
  colors={{ scheme: "set2" }}
  animate={true}
  innerRadius={0.5}
  padAngle={0.7}
  cornerRadius={3}
  radialLabelsTextColor="#7b7b99"
  radialLabelsLinkDiagonalLength={16}
  radialLabelsLinkHorizontalLength={24}
  radialLabelsLinkStrokeWidth={1.3}
  radialLabelsLinkColor={{ from: "color" }}
/>
```

![Deaths Per Author Labels](https://dev-to-uploads.s3.amazonaws.com/i/g2dyuevq6dkdxfdfp4ey.png)

### Nivo Legends

Nivo legends are available for each chart type and are defined as an array of objects on the `legends` prop. The legend position, in relation to the chart itself, is defined by the `anchor` property, for this chart let's define it at the bottom. The `direction` prop can either be a `row` or a `column`. Each legend item can further be customized with the specific props `itemWidth`, `itemWidth`, and `itemTextColor`. The symbol that appears next to the text can either be a circle, triangle, square, or diamond. Finally, we need to change the bottom chart margins to give room for this legend.

```jsx
<Pie
  data={newData}
  width={780}
  height={500}
  margin={{ top: 40, right: 80, bottom: 80, left: 80 }}
  colors={{ scheme: "set2" }}
  animate={true}
  innerRadius={0.5}
  padAngle={0.7}
  cornerRadius={3}
  radialLabelsTextColor="#7b7b99"
  radialLabelsLinkDiagonalLength={16}
  radialLabelsLinkHorizontalLength={24}
  radialLabelsLinkStrokeWidth={1}
  radialLabelsLinkColor={{ from: "color" }}
  legends={[
    {
      anchor: "bottom",
      direction: "row",
      translateY: 56,
      itemWidth: 120,
      itemHeight: 18,
      itemTextColor: "#999",
      symbolSize: 18,
      symbolShape: "circle",
    },
  ]}
/>
```

![Deaths Per Author Legends](https://dev-to-uploads.s3.amazonaws.com/i/ge23m1fxru7wbjz47qmf.png)

## Deaths Per Publication - Data Transform

This function is almost identical to the per author function except we're looking for publications that have multiple death knells, instead of authors. We could create a shared function that accepts a property to filter for, but we can just as easily copy and paste for now.

```js
export function deathsPerPublication() {
  const perPubArray = [];
  appleDeaths.forEach((death, index) => {
    if (index == 0) {
      perPubArray.push({ id: death.Publisher, value: 1 });
    }
    const inPerAuthorArray = perPubArray.findIndex(author => {
      return author.id == death.Publisher;
    });
    if (inPerAuthorArray > -1) {
      perPubArray[inPerAuthorArray].value++;
    } else {
      perPubArray.push({ id: death.Publisher, value: 1 });
    }
  });
  const filtered = perPubArray.filter(author => {
    const isAboveOne = author.value > 1;
    const isNotUnknown = author.id !== "Unknown";
    return isAboveOne && isNotUnknown;
  });
  return filtered;
}
```

The data will end up looking like this:

```js
[
  {
    id: "Mac Observer",
    value: 2,
  },
  ...
];
```

## Deaths Per Publication - Pie Chart

The data here is so similar to the per author data that we're going to resuse the pie chart we created above and simply provide this publication data.

```jsx
import { Pie } from "@nivo/pie";
import React from "react";
import { deathsPerPublication } from "./data";

const DeathsPerPublication = ({ version }) => {
  const newData = deathsPerPublication();

  let chart = (
    <Pie
      data={newData}
      width={780}
      height={500}
      margin={styles.chartMargin}
      colors={{ scheme: "set2" }}
      animate={true}
      innerRadius={0.5}
      padAngle={0.7}
      cornerRadius={3}
      radialLabelsTextColor="#7b7b99"
      radialLabelsLinkDiagonalLength={16}
      radialLabelsLinkHorizontalLength={24}
      radialLabelsLinkStrokeWidth={1}
      radialLabelsLinkColor={{ from: "color" }}
    />
  );

  return <div style={styles.container}>{chart}</div>;
};

const styles = {
  container: {
    height: 500,
    maxWidth: 780,
    background: "#09001b",
  },
  chartMargin: {
    top: 40,
    right: 80,
    bottom: 40,
    left: 80,
  },
};

export { DeathsPerPublication };
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/eaeqggq7ilz5m073xniz.png)

## Word cloud

For fun, I generated a word cloud using [wordclouds.com](https://www.wordclouds.com/) with all the relevant death knell quotes.

![Apple Death Word Cloud](https://codinhood.com/static/6810f3319f77ccfe73ce75d37f1223bc/26df7/wordcloud3.png)

## Conclusion

Nivo charts contain a lot of functionality out-of-the-box while allowing developers to customize almost all aspects of a chart. However, there are numerous other charting libraries for React and Javascript, check out [How to Build a Bitcoin DCA Chart with React and Recharts](https://codinhood.com/how-to-build-bitcoin-dca-chart-react-recharts) to see how Recharts differs from Nivo.