javascriptreactjschartist.jsreact-chartist

How to set img in label using React?


I'm creating charts using react-chartist. On labels I want to add image next to label text. I tried all of my ideas, but none work. Everything return [object Object]. Whether there is a good solution of this issue? I can also add labels using just css, but if I can do it inside <Chartist /> component it would be much easier.

Simple code example with my attempts (codesandbox demo below):

class Chart extends React.Component {
  render() {
    const labels = ["label 1", "label 2", "label 3"];
    const images = [
      "http://placekitten.com/100/100",
      "http://placekitten.com/101/101",
      "http://placekitten.com/102/102"
    ];
    const series = [[40, 30, 20]];

    const labelsInsideReactFragment = labels.map((el, key) => (
      <React.Fragment>
        <img src={images[key]} /> {el}
      </React.Fragment>
    ));

    const labelsViaGhIssue = labels.map((el, key) => {
      return {
        label: el,
        image: images[key]
      };
    });

    const labelsInsideDiv = labels.map((el, key) => (
      <div>
        <img src={images[key]} /> {el}
      </div>
    ));

    const labelsOnlyImg = labels.map((el, key) => <img src={images[key]} />);

    const data1 = {
      labels,
      series
    };

    const data2 = {
      labels: labelsViaGhIssue,
      series
    };

    const data3 = {
      labels: labelsInsideDiv,
      series
    };

    const data4 = {
      labels: labelsInsideReactFragment,
      series
    };

    const data5 = {
      labels: labelsOnlyImg,
      series
    };

    const options = {
      height: "200px",
      width: "500px"
    };

    return (
      <div>
        <ChartistGraph data={data1} options={options} type="Bar" />
        <ChartistGraph data={data2} options={options} type="Bar" />
        <ChartistGraph data={data3} options={options} type="Bar" />
        <ChartistGraph data={data4} options={options} type="Bar" />
        <ChartistGraph data={data5} options={options} type="Bar" />
      </div>
    );
  }
}

Demo: https://codesandbox.io/s/chartist-8oeju

I also find this demo on jsbin: https://jsbin.com/gulokelide/edit?js,output but it didn't work in React I guess.


Solution

  • The JSbin example uses a custom on("draw") function which isn't supported by react-chartist (and it's also manually calculating the label placement, so it won't scale if the width or the amount of data in the series changes). That said, the simplest solution would be to make your own Chartist instance and alter it to suit your needs.

    Working example (labels and images will scale to the center regardless of the width and/or the number of items within the series):

    Demo

    Source


    components/Chart

    import React, { Component, cloneElement, Children } from "react";
    import Chartist from "chartist";
    import PropTypes from "prop-types";
    
    class Chart extends Component {
      componentDidUpdate(prevProps) {
        if (this.props !== prevProps) this.updateChart(this.props);
      }
    
      componentWillUnmount() {
        if (this.chart) {
          try {
            this.chart.detach();
          } catch (err) {
            throw new Error("Internal chartist error", err);
          }
        }
      }
    
      componentDidMount = () => this.updateChart(this.props);
    
      updateChart = ({ data, listener, options, responsiveOptions, type }) => {
        let event;
    
        if (this.chartist) {
          this.chart.update(data, options, responsiveOptions);
        } else {
          this.chart = new Chartist[type](
            this.chart,
            data,
            options || {},
            responsiveOptions || []
          ).on("draw", context => {
            if (type === "Pie") return;
            if (context.type === "label" && context.axis.units.pos === "x") {
              if (context && context.text) {
                const group = new Chartist.Svg("g");
                const isBar = type === "Bar";
                const hasImage = context.text.image;
                const hasLabel = context.text.label;
    
                if (hasImage) {
                  const x = isBar
                    ? context.x + context.width / 2 - 15
                    : context.x + 5;
                  const y = isBar
                    ? context.y + context.height / 2 - 10
                    : context.y + 0;
    
                  group.append(
                    new Chartist.Svg("image", {
                      "xlink:href": context.text.image,
                      width: "30px",
                      height: "30px",
                      x,
                      y
                    })
                  );
                }
    
                if (hasLabel) {
                  const x = isBar
                    ? context.x + context.width / 2 + 5
                    : context.x + 30;
                  const y = hasImage
                    ? context.y + context.height / 2 + 30
                    : context.y + context.height / 2;
    
                  group.append(
                    new Chartist.Svg("text", {
                      width: "100px",
                      height: "40px",
                      x,
                      y,
                      "text-anchor": "middle",
                      "alignment-baseline": "hanging"
                    }).text(context.text.label || "")
                  );
                }
    
                context.element.replace(group);
              }
            }
          });
    
          if (listener) {
            for (event in listener) {
              if (listener.hasOwnProperty(event)) {
                this.chart.on(event, listener[event]);
              }
            }
          }
        }
    
        return this.chart;
      };
    
      render = () => {
        const { className, style, children, data, type } = this.props;
        const childrenWithProps =
          children &&
          Children.map(children, child =>
            cloneElement(child, {
              type,
              data
            })
          );
        return (
          <div
            className={`ct-chart ${className || ""}`}
            ref={ref => (this.chart = ref)}
            style={style}
          >
            {childrenWithProps}
          </div>
        );
      };
    }
    
    Chart.propTypes = {
      type: PropTypes.oneOf(["Line", "Bar", "Pie"]).isRequired,
      data: PropTypes.object.isRequired,
      className: PropTypes.string,
      options: PropTypes.object,
      responsiveOptions: PropTypes.array,
      style: PropTypes.object
    };
    
    export default Chart;