Prabowo Murti
Prabowo Murti

Reputation: 1341

Create a tree diagram (multiple parents) with pure HTML/CSS

I want to create a tree diagram with multiple parents. The idea is to create a diagram for Dota 2 Items, which several items can be made into one single item (plus a recipe), AND/OR one item can be made into several possibilities (more than one parent item).

enter image description here

One of my reference is from thecodeplayer's code.

Here is my work on jsfiddle so far.

<div class="items">
  <ul>
    <li>
        <a href="#" class="">Dagon</a>
        <a href="#" class="">Veil of Discord</a>
        <a href="#" class="">Other Parent Item</a>
      <ul>
        <li>
          <a href="#">Null Talisman</a>
            <ul>
            <li><a href="#">Circlet</a></li>
            <li><a href="#">Mantle of Intelligence</a></li>
            <li><a href="#">Recipe: Null Talisman</a></li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</div>

CSS

<style type="text/css">
* {
  margin: 0 auto;
  padding: 0;
  text-align: center;
  color: black;
  font-family: tahoma;
}

.items ul {
  padding-top: 20px;
  position: relative;
}

/* Make all children "inline" */
.items li {
  float: left;
  text-align: center;
  list-style-type: none;
  position: relative;
  padding: 20px 5px 0 5px;
}

/* Add horizontal connector. Note: they are 2 pseudo-elements */
.items li::before, .items li::after {
  content: '';
  position: absolute;
  top: 0;
  right: 50%;
  width: 50%;
  height: 45px;
  z-index: -1;
  border-top: 1px solid red;
}

.items li::after {
  border-left: 1px solid green;
  left: 50%;
  right: auto;
}

/* Remove left and right connector from a single child */
.items li:only-child::after, .items li:only-child::before {
  display: none;
}

.items li:only-child {
  padding-top: 0;
}

/* Remove "outer" connector */
.items li:first-child::before, .items li:last-child::after {
  border: 0 none;
}
/* Add back the down connector for last node */
.items li:last-child::before {
  border-right: 1px solid blue;
  border-radius: 0 5px 0 0;
}

/* Add curve line to the first child's connector */
.items li:first-child::after {
  border-radius: 5px 0 0 0;
}


/* Add down connector from parent */
.items ul ul::before {
  content: '';
  border-left: 1px solid magenta;
  z-index: -1;
  height: 20px;
  position: absolute;
  top: 0px;
  left: 50%;
  width: 0;
}

/* Add cosmetic for each item */
.items li a {
  font-size: 12px;
  background-color: white;
  border: 1px solid #ccc;
  padding: 5px 10px;
  text-decoration: none;
  display: inline-block;
  border-radius: 4px;
}

.items li a:hover {
  background-color: #EEE;
}

/* Experimental for multiple parents */
/* Add margin for the parents */
.items li a+a {
  position: relative;
  margin-bottom: 12px;
}
/* Implement also for the first parent */
.items li a:first-child {
  position: relative;
  margin-bottom: 12px;
}

/* 
- Add "down" connector (vertical line) from each parent 
- Currently it will also select the single parent
*/
.items li > a:not(:only-child)::after{
  content: '';
  position: absolute;
  border-right: 1px solid pink;
  top: 20px;
  height: 20px;
  width: 0;
  left: 50%;
  z-index: -1;
}

/* Starting to fvcked up from here */

/* Making the horizontal connector line after each multiple parent */
.items li > a:not(:only-of-type):not(:last-of-type)::before {
  content: '';
  position: absolute;
  top: 40px;
  left: auto;
  width: 100%;
  border-top: 1px solid indigo;
}

.items li > a:last-of-type:not(:first-child)::before {
  content: '';
  position: absolute;
  top: 40px;
  right: auto;
  width: 100%;
  border-top: 1px solid indigo;
}
</style>

My attempts (which failed, or is not nice enough) is to create another pseudo-element (::before or ::after) the <a> (multiple parent) element.

Upvotes: 4

Views: 12505

Answers (1)

Obsidian Age
Obsidian Age

Reputation: 42304

What I would recommend doing is setting the left property to 50% on the left-most and right-most ::before pseudo-classes, and then adjusting the width of the left-most and middle pseudo-classes to cover the offset.

I'd also recommend increasing the top of the downwards connectors by 2px just to avoid spiky 'overhangs'.

A bit sloppy, but here's my attempt:

* {
  margin: 0 auto;
  padding: 0;
  text-align: center;
  color: black;
  font-family: tahoma;
}

.items ul {
  padding-top: 20px;
  position: relative;
}


/* Make all children "inline" */

.items li {
  float: left;
  text-align: center;
  list-style-type: none;
  position: relative;
  padding: 20px 5px 0 5px;
}


/* Add horizontal connector. Note: they are 2 pseudo-elements */

.items li::before,
.items li::after {
  content: '';
  position: absolute;
  top: 0;
  right: 50%;
  width: 50%;
  height: 45px;
  z-index: -1;
  border-top: 1px solid red;
}

.items li::after {
  border-left: 1px solid green;
  left: 50%;
  right: auto;
}


/* Remove left and right connector from a single child */

.items li:only-child::after,
.items li:only-child::before {
  display: none;
}

.items li:only-child {
  padding-top: 0;
}


/* Remove "outer" connector */

.items li:first-child::before,
.items li:last-child::after {
  border: 0 none;
}


/* Add back the down connector for last node */

.items li:last-child::before {
  border-right: 1px solid blue;
  border-radius: 0 5px 0 0;
}


/* Add curve line to the first child's connector */

.items li:first-child::after {
  border-radius: 5px 0 0 0;
}


/* Add down connector from parent */

.items ul ul::before {
  content: '';
  border-left: 1px solid magenta;
  z-index: -1;
  height: 20px;
  position: absolute;
  top: 2px; /* Changed */
  left: 50%;
  width: 0;
}


/* Add cosmetic for each item */

.items li a {
  font-size: 12px;
  background-color: white;
  border: 1px solid #ccc;
  padding: 5px 10px;
  text-decoration: none;
  display: inline-block;
  border-radius: 4px;
}

.items li a:hover {
  background-color: #EEE;
}


/* Experimental for multiple parents */


/* Add margin for the parents */

.items li a+a {
  position: relative;
  margin-bottom: 12px;
}


/* Implement also for the first parent */

.items li a:first-child {
  position: relative;
  margin-bottom: 12px;
}


/* 
- Add "down" connector (vertical line) from each parent 
- Currently it will also select the single parent
*/

.items li>a:not(:only-child)::after {
  content: '';
  position: absolute;
  border-right: 1px solid pink;
  top: 20px;
  height: 20px;
  width: 0;
  left: 50%;
  z-index: -1;
}


/* Starting to fvcked up from here */


/* Making the horizontal connector line after each multiple parent */

.items li>a:not(:only-of-type):not(:last-of-type)::before {
  content: '';
  position: absolute;
  top: 40px;
  left: auto;
  width: 110%; /* Changed */
  border-top: 1px solid indigo;
}

.items li>a:last-of-type:not(:first-child)::before {
  content: '';
  position: absolute;
  top: 40px;
  right: auto;
  width: 42%; /* Changed */
  border-top: 1px solid indigo;
}


/* ADDED STYLES */
.items li>a:first-of-type:first-child::before {
  left: 50%;
}

.items li>a:last-of-type:last-child::before {
  left: 50%;
}
<div class="items">
  <ul>
    <li>
      <a href="#" class="">Dagon</a>
      <a href="#" class="">Veil of Discord</a>
      <a href="#" class="">Other Parent Item</a>
      <ul>
        <li>
          <a href="#">Null Talisman</a>
          <ul>
            <li><a href="#">Circlet</a></li>
            <li><a href="#">Mantle of Intelligence</a></li>
            <li><a href="#">Recipe: Null Talisman</a></li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</div>

Upvotes: 4

Related Questions