0

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.

sosick
  • 624
  • 3
  • 17
  • 35

3 Answers3

2

Based on the jsbin link you gave, we can update the react version too.

Check the new sandbox I created.

https://codesandbox.io/s/chartist-m1yv9

The method is: Every react chartist component has it's chartist object attached to it. So add a ref to catch it and update the label from each chart's context just like the jsbin example.

import Chartist from "chartist";

//in componentDidMount()
      const chart = this.refTwo.current;
      chart.chartist.on("draw", context => {
        if (context.type === "label" && context.axis.units.pos === "x") {
          if (context.text && context.text.image) {
            const group = new Chartist.Svg("g");
            group.append(
              new Chartist.Svg("image", {
                "xlink:href": context.text.image,
                width: "30px",
                height: "30px",
                x: context.x + 55,
                y: context.y + 5
              })
            );

            context.element.replace(group);
          }
        }
      });


// in render()
<ChartistGraph
          ref={this.refTwo}
          data={data2}
          options={options}
          type="Bar"
 />

1

Hi you can do "on draw" on element like:

<ChartistGraph
          listener={{
            draw: e => this.onDrawHandler(e)
          }}
          data={data2}
          options={options}
          type="Bar"
        />

and do your class method:

onDrawHandler = context => {
//...code
}

here's example:

https://codesandbox.io/s/chartist-f7c7d

pegla
  • 1,866
  • 4
  • 17
  • 20
1

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;
Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51