Preface

G2Plot: a configurable, experience-elegant, data-analyzing-oriented statistics chart library ****, ** helps developers create high-quality statistics charts at minimum cost, born out of the business appeal of real scenarios of Ali Economy BI products.

Portal provides an excellent solution for rendering child nodes to DOM nodes that exist outside the parent component.

If you’re familiar with either React or G2Plot, check out this article on how to combine the two and use G2Plot’s graphs for rich manipulation.

Annotations

G2Plot provides annotations as an auxiliary element of the chart, mainly used to identify additional markup annotations on the chart, currently including line, text, image, HTML and other 10 types. Although there are many types, each type of configuration item has certain restrictions, which makes it useless in complex business scenarios. After all, Canvas is not HTML.

The other types are simpler and don’t have much room to manipulate. Take type: ‘HTML’ as an example. A simple auxiliary tag is as follows:

Annotations: [{type: "HTML", position: [" 1995 ", 4.9], HTML: '< p > auxiliary tag < / p >'}].Copy the code

As you can see from the example, HTML types support HTML strings. In the React Vue era, I’m sure no one wants to concatenate HTML strings anymore unless they have to.

ReactDOM

There are many ways to handle HTML strings that I won’t go over here.

Render

Since the type: ‘HTML’ mode supports HTML strings, I wonder if you have thought of ReactDOM. This is almost perfect with ReactDOM. After a simple modification, it looks like this.

Annotations: [{type: "HTML ", Position: ["1995", 4.9], HTML: () => { const ele = document.createElement("div"); ReactDom.render(<Annotation />, ele); return ele; } } ],Copy the code

This looks perfect, but the business is much more complicated. In a very simple case, if the diagram container has overflow: ‘hidden’ configured, it looks like this.

The Annotation is truncated, so increasing the height of the container or adding a scroll bar to the Annotation component is often not the best solution.

CreatePortal

To solve the above problem and keep the Annotation out of the parent container, we can use CreatePortal to render the Annotation into any DOM tree we want, using the body example.

  const getAnnotationHtml = () => {
    const ele = document.createElement("div");
    ele.id = "annotation-box";
    ReactDOM.render(
      <>
        {ReactDOM.createPortal(
          <Annotation />,
          document.getElementsByTagName("body")[0]
        )}
      </>,
      ele
    );
    return ele;
  };
Copy the code

The Annotation is rendered inside the body correctly, but not as expected because Annotaion is not rendered inside the HTMLElement ID [‘annotation-box’], so it is out of position.

In fact, under normal circumstances, if the Annotation has too much content, it should not be directly displayed, because the Annotation is too occlude the content, we simply add an interaction (onmousemove).

Annotations: [{type: "HTML ", position: ["1995", 4.9], HTML: getAnnotationHtml() } ] const getPosition = (targetElement) => { const { top, left, right } = targetElement.getBoundingClientRect(); / / need to be considered with the situation of the scrollbar const boxTop = top - document. DocumentElement. ClientTop + document. The documentElement. ScrollTop; const boxLeft = left - document.documentElement.clientLeft + document.documentElement.scrollLeft; const body = document.getElementsByTagName("body")[0]; const { width } = body.getBoundingClientRect(); const boxWdith = 230; // Container width const offsetX = width-right < boxWdith? boxWdith - (width - right) : 0; Return {left: boxleft-offsetx, top: boxTop}; }; const showAnnotationComponent = (e) => { const exist = document.querySelector("#annotaion-component"); if (! exist) { const targetElement = e.currentTarget.parentNode.getElementsByClassName( "annotation-box" )[0]; const { top, left } = getPosition(targetElement); ReactDOM.render( <> {ReactDOM.createPortal( <div id="annotaion-component" style={{ position: "absolute", left, top }} > <Annotation /> </div>, document.getElementsByTagName("body")[0] )} </>, targetElement ); } else { // todo set display } }; const getAnnotationHtml = () => { const ele = document.createElement("div"); Ele. InnerHTML = '<span> view details </span><div class="annotation-box"></div>'; ele.onmousemove = (e) => { showAnnotationComponent(e); }; return ele; };Copy the code

When the mouse moves over to view details, it does not matter if overFlow: ‘hidden’ is set on the parent container:

Events such as onMouseleave are handled according to business requirements.

What’s more?

Sometimes these problems can be bypassed, sometimes they can’t. Consider CreatePortal, for example, a typical tooltip, which can be solved with customContent.

The sample code address: codesandbox. IO/s/annotatio…