Gapminder/vizabi

View on GitHub
src/tools/axislabeler/axislabeler-component.js

Summary

Maintainability
D
2 days
Test Coverage
import * as utils from "base/utils";
import Component from "base/component";

import axisSmart from "helpers/d3.axisWithLabelPicker";


//BAR CHART COMPONENT
const AxisLabelerComponent = Component.extend({

  /**
   * Initializes the component (Bar Chart).
   * Executed once before any template is rendered.
   * @param {Object} config The options passed to the component
   * @param {Object} context The component's parent
   */
  init(config, context) {
    this.name = "axislabeler";
    this.template = require("./axislabeler.html");

    //define expected models for this component
    this.model_expects = [{ name: "scales" }, { name: "show" }];

    const _this = this;

    this.model_binds = {
      "change:scales": function(evt) {
        _this.initScales();
        _this.update();
      },
      "change:show": function(evt, path) {
        _this.initScales();
        _this.update();
      }
    };

    //contructor is the same as any component
    this._super(config, context);

    this.xScale = null;
    this.yScale = null;

    this.xAxis = axisSmart("bottom");
    this.yAxis = axisSmart("left");
  },

  /**
   * DOM is ready
   */
  readyOnce() {
    const _this = this;
    this.element = d3.select(this.element);

    // reference elements
    this.graph = this.element.select(".vzb-al-graph");
    this.xAxisEl = this.graph.select(".vzb-al-axis-x");
    this.yAxisEl = this.graph.select(".vzb-al-axis-y");

    //$(".vzb-bc-axis-x, .vzb-bc-axis-y").css('font-size',this.model.show.labelSize);
    this.xInvert = function(d) { return _this.xScale(_this.xScale.invert(_this.xScale(d))); };
    this.yInvert = function(d) { return _this.yScale(_this.yScale.invert(_this.yScale(d))); };

    this.line = d3.svg.line()
      .x(d => _this.xScale(d))
      .y(d => _this.yScale(d));

    this.lineInvert = d3.svg.line()
      .x(d => _this.xInvert(d))
      .y(d => _this.yInvert(d));

    //component events
    this.on("resize", () => {
      _this.update();
    });
  },

  /*
   * Both model and DOM are ready
   */
  ready() {
    console.log("Model ready");
    this.initScales();
    this.update();
  },

  initScales() {
    const _this = this;

    const domain = this.model.scales.domain;

    this.xScale = d3[`scale${utils.capitalize(this.model.scales.xScaleType)}`]();
    this.yScale = d3[`scale${utils.capitalize(this.model.scales.yScaleType)}`]();

    this.xScale.domain(domain);

    this.yScale.domain(domain);

    this.mockData = d3.range(domain[0], domain[domain.length - 1], (domain[domain.length - 1] - domain[0]) / 10000);
    this.mockData.push(domain[domain.length - 1]);
  },


  update() {
    const _this = this;

    const margin = this.model.show.toolMargin.getPlainObject();

    //stage
    const height = (parseInt(this.element.style("height"), 10) - margin.top - margin.bottom) || 0;
    const width = (parseInt(this.element.style("width"), 10) - margin.left - margin.right) || 0;

    if (height <= 0 || width <= 0) return utils.warn("Axis Labeler update() call interrupted for Vizabi container is too little or has display:none");

    //graph group is shifted according to margins (while svg element is at 100 by 100%)
    this.graph.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    //update scales to the new range
    const xLength = this.xScale.domain().length;
    const yLength = this.yScale.domain().length;
    this.xScale.range(d3.range(xLength).map(n => width / (xLength - 1) * n)); //.nice();
    this.yScale.range(d3.range(yLength).map(n => height / (yLength - 1) * n).reverse()); //.nice();

    this.xAxis.scale(this.xScale)
      .tickSize(6, 0)
      .tickSizeMinor(3, 0)
      .labelerOptions({
        scaleType: this.model.scales.xScaleType,
        toolMargin: margin
      });

    this.yAxis.scale(this.yScale)
      .tickSize(6, 0)
      .tickSizeMinor(3, 0)
      .labelerOptions({
        scaleType: this.model.scales.yScaleType,
        toolMargin: margin
      });


    this.xAxisEl.attr("transform", "translate(0," + height + ")");
    this.xAxisEl.call(this.xAxis);
    this.yAxisEl.call(this.yAxis);

    //TODO: remove. make font sizing through plain CSS
    //this.xAxisEl.selectAll("text").style('font-size',this.model.show.labelSize);
    //this.yAxisEl.selectAll("text").style('font-size',this.model.show.labelSize);


    const path = this.graph.selectAll(".vzb-al-line").data([0]);
    path.enter().append("path")
      .attr("class", "vzb-al-line");
    path.datum(this.mockData).attr("d", this.line);

    const pathInvert = this.graph.selectAll(".vzb-al-line-invert").data([0]);
    pathInvert.enter().append("path")
      .attr("class", "vzb-al-line-invert");
    pathInvert.datum(this.mockData).attr("d", this.lineInvert);

    const format = d3.format(".4r");

    const dots = this.graph.selectAll(".vzb-al-dots").data(this.mockData);
    dots.enter().append("circle")
      .attr("class", "vzb-al-dots")
      .attr("r", 5)
      .on("mouseenter", (d, i) => {
        console.log("Point #" + i + ": " + d + " x=" + format(_this.xScale(d)) + " y=" + format(_this.yScale(d)));
      });
    dots.exit().remove();
    dots.attr("cx", d => _this.xScale(d))
      .attr("cy", d => _this.yScale(d));

    const dotsInvert = this.graph.selectAll(".vzb-al-dots-invert").data(this.mockData);
    dotsInvert.enter().append("circle")
      .attr("class", "vzb-al-dots-invert")
      .attr("r", 5)
      .on("mouseenter", (d, i) => {
        console.log("Point #" + i + ": " + d + " x=" + format(_this.xScale(d)) + " y=" + format(_this.yScale(d)));
      });
    dotsInvert.exit().remove();
    dotsInvert.attr("cx", d => _this.xInvert(d))
      .attr("cy", d => _this.yInvert(d));
  }
});


export default AxisLabelerComponent;