Programmatic trellis layout

Use datamodel and muze api to programatically create trellis

main
run-button
run-button
reset-button

const formatter = (val) => {
  if (val > 1000000) {
    return `${(val / 1000000).toFixed(2)} M`
  } else if (val > 1000) {
    return `${(val / 1000).toFixed(2)} K`
  } return val.toFixed(2);
}

const rootData = new DataModel(data, schema);
const canvases = [];

const div = document.createElement('div')
div.className = 'chart-header muze-header-cell';
div.innerHTML = 'Charting the Great Twitter Bot Purge of 2018 (A Trellis Example)';
document.getElementById('chart-container').appendChild(div);

users.forEach((user, i) => {
  const newDomNode = document.createElement('div')
  newDomNode.className = 'chart-div';
  newDomNode.id = `chart${i + 1}`;
  newDomNode.style.overflow = 'auto';
  document.getElementById('chart-container').appendChild(newDomNode);

  const canvas = env.canvas()
    .rows([[], ['followers']])
    .columns(['time'])
    .data(rootData.select(f => f.user.value === user))
    .width(250)
    .height(200)
    .transform({
      lastPoint: dt => {
        const dataLength = dt.getData().data.length;
        return dt.select((fields, i) => {
          if (i === dataLength - 1) {
            return true
          } return false
        })
      }
    })
    .layers([{
      mark: 'line',
      name: 'lineLayer'
    }, {
      mark: 'point',
      source: 'lastPoint'
    }, {
      mark: 'text',
      encoding: {
        text: {
          field: 'followers',
          formatter: (val) => formatter(val)
        },
        color: {
          value: () => "#858585"
        }
      },
      encodingTransform: require('layers', ['lineLayer', () => {
        return (points, layer, dep) => {
          const width = layer.measurement().width;
          const height = layer.measurement().height;
          const smartlabel = dep.smartLabel;

          return points.map(point => {

            const size = smartlabel.getOriSize(point.text);
            if (point.update.y + size.height > height) {
              point.update.y -= size.height / 2;
            } else {
              point.update.y += size.height / 2;
            }
            if (point.update.x + size.width / 2 > width) {
              point.update.x -= size.width / 2 + 1;
            }
            return point;
          })
        }
      }]),
      source: 'lastPoint'
    }])
    .config({
      border: {
        showValueBorders: {
          right: false,
          bottom: false
        }
      },
      gridLines: {
        y: {
          show: false
        }
      },
      axes: {
        y: {
          tickFormat: (val, rawValue, j, labels) => {
            if (j === 0 || j === labels.length - 1) {
              return formatter(val);
            } return '';
          },
          showAxisName: false
        },
        x: {
          show: false
        }
      },
        interaction: {
            tooltip: {
                formatter: (dm) => {
                    const dataArr = dm.getData().data;
                    const fieldsConfig = dm.getFieldsConfig();
                    const followers = fieldsConfig.followers.index;
                    return [
                        ['Followers: ', {
                            value: formatter(dataArr[0][followers]),
                        }]
                    ];
                }
            }
      }
    })
    .subtitle(html`<a href = "https://www.twitter.com/@${user}" class= "twitter-link">@${user}</a>`, { position: 'bottom', align: 'center' })
    .mount(`#chart${i + 1}`);
  canvases.push(canvas);
});
});