Skip to content

Commit

Permalink
feat(plugin): Intent to ship TableView plugin
Browse files Browse the repository at this point in the history
Implement new TableView plugin

Close #1873
  • Loading branch information
netil committed Aug 9, 2021
1 parent 437c007 commit 215b611
Show file tree
Hide file tree
Showing 10 changed files with 551 additions and 3 deletions.
4 changes: 1 addition & 3 deletions config/webpack/plugin.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ const config = {
filename: `billboardjs-plugin-[name].js`,
library: ["bb", "plugin", "[name]"],
libraryExport: "default",
libraryTarget: "umd",
umdNamedDefine: true,
globalObject: "this"
publicPath: "/dist/plugin"
},
plugins: [
new webpack.BannerPlugin({
Expand Down
33 changes: 33 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -3045,6 +3045,39 @@ d3.select(".chart_area")
bubblecompare: {minR: 11, maxR: 74, expandScale: 1.1}
}]
}
},
TableView: {
description: "Generates table view for bound dataset.<br>Must load or import plugin before the use.",
options: {
data: {
x: "x",
columns: [
["x", "2012", "2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021"],
["data1", 1230, 2380, 1200, 1238, 1500, 2500, 2540, 1265, 550, 240],
["data2", 500, 120, 100, 200, 840, 935, 825, 1123, 385, 980],
["data3", 1223, 153, 850, 300, 250, 3120, 1205, 840, 999, 1280],
["data4", 1130, 2135, 1020, 1138, 2119, 1228, 3256, 138, 2355, 220],
["data5", 1223, 2310, 1210, 2220, 1238, 1205, 2120, 2113, 1185, 1098]
],
types: {
data3: "area",
data4: "step",
data5: "bar"
}
},
axis: {
x: {
type: "category"
}
},
_plugins: [{
tableview: {
title: "My Yearly Data List",
categoryTitle: "Year",
style: true
}
}]
}
}
},
Point: {
Expand Down
3 changes: 3 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ <h4 class="sub_tit">Sample code</h4>
}),
plugins_bubblecompare: path.map(function(p) {
return p + "plugin/billboardjs-plugin-bubblecompare.js"
}),
plugins_tableview: path.map(function(p) {
return p + "plugin/billboardjs-plugin-tableview.js"
})
});

Expand Down
107 changes: 107 additions & 0 deletions src/Plugin/tableview/Options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Copyright (c) 2021 ~ present NAVER Corp.
* billboard.js project is licensed under the MIT license
*/
/**
* TableView plugin option class
* @class TableviewOptions
* @param {Options} options TableView plugin options
* @augments Plugin
* @returns {TableviewOptions}
* @private
*/
export default class Options {
constructor() {
return {
/**
* Set tableview holder selector.
* - **NOTE:** If not set, will append new holder element dynamically right after chart element.
* @name selector
* @memberof plugin-tableview
* @type {string}
* @default undefined
* @example
* selector: "#table-holder"
*/
selector: undefined,

/**
* Set category title text
* @name categoryTitle
* @memberof plugin-tableview
* @type {string}
* @default "Category"
* @example
* categoryTitle: "#table-holder"
*/
categoryTitle: "Category",

/**
* Set category text format function.
* @name categoryFormat
* @memberof plugin-tableview
* @type {Function}
* @returns {string}
* @default function(v) { // will return formatted value according x Axis type }}
* @example
* categoryFormat: "#table-holder"
*/
categoryFormat: function(v: Date|number|string): string {
let category = v;

if (this.$$.axis.isCategorized()) {
category = this.$$.categoryName(v);
} else if (this.$$.axis.isTimeSeries()) {
category = (v as Date).toLocaleDateString();
}

return category as string;
},

/**
* Set tableview holder class name.
* @name class
* @memberof plugin-tableview
* @type {string}
* @default undefined
* @example
* class: "table-class-name"
*/
class: undefined,

/**
* Set to apply default style(`.bb-tableview`) to tableview element.
* @name style
* @memberof plugin-tableview
* @type {boolean}
* @default true
* @example
* style: false
*/
style: true,

/**
* Set tableview title text.
* - **NOTE:** If set [title.text](https://naver.github.io/billboard.js/release/latest/doc/Options.html#.title), will be used when this option value is empty.
* @name title
* @memberof plugin-tableview
* @type {string}
* @default undefined
* @example
* title: "Table Title Text"
*/
title: undefined,

/**
* Update tableview from data visibility update(ex. legend toggle).
* @name updateOnToggle
* @memberof plugin-tableview
* @type {boolean}
* @default true
* @example
* legendToggleUpdate: false
*/
updateOnToggle: true
};
}
}
52 changes: 52 additions & 0 deletions src/Plugin/tableview/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2021 ~ present NAVER Corp.
* billboard.js project is licensed under the MIT license
*/
/**
* Constants values for plugin option
* @ignore
*/
const defaultStyle = {
id: "__tableview-style__",
class: "bb-tableview",
rule: `.bb-tableview {
border-collapse:collapse;
border-spacing:0;
background:#fff;
min-width:100%;
margin-top:10px;
font-family:sans-serif;
font-size:.9em;
}
.bb-tableview tr:hover {
background:#eef7ff;
}
.bb-tableview thead tr {
background:#f8f8f8;
}
.bb-tableview caption,.bb-tableview td,.bb-tableview th {
text-align: center;
border:1px solid silver;
padding:.5em;
}
.bb-tableview caption {
font-size:1.1em;
font-weight:700;
margin-bottom: -1px;
}`
};

// template
const tpl = {
body: `<caption>{=title}</caption>
<thead><tr>{=thead}</tr></thead>
<tbody>{=tbody}</tbody>`,
thead: `<th scope="col">{=title}</th>`,
tbodyHeader: `<th scope="row">{=value}</th>`,
tbody: `<td>{=value}</td>`
};

export {
defaultStyle,
tpl
};
171 changes: 171 additions & 0 deletions src/Plugin/tableview/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/**
* Copyright (c) 2021 ~ present NAVER Corp.
* billboard.js project is licensed under the MIT license
*/
import Plugin from "../Plugin";
import Options from "./Options";
import {defaultStyle, tpl} from "./const";
import {loadConfig} from "../../config/config";
import {isNumber, tplProcess} from "../../module/util";

/**
* Table view plugin.<br>
* Generates table view for bound dataset.
* - **NOTE:**
* - Plugins aren't built-in. Need to be loaded or imported to be used.
* - Non required modules from billboard.js core, need to be installed separately.
* @class plugin-tableview
* @param {object} options table view plugin options
* @augments Plugin
* @returns {TableView}
* @example
* // Plugin must be loaded before the use.
* <script src="$YOUR_PATH/plugin/billboardjs-plugin-tableview.js"></script>
*
* var chart = bb.generate({
* ...
* plugins: [
* new bb.plugin.tableview({
* selector: "#my-table-view",
* categoryTitle: "Category",
* categoryFormat: function(v) {
* // do some transformation
* ...
* return v;
* },
* class: "my-class-name",
* style: true,
* title: "My Data List",
* updateOnToggle: false
* }),
* ]
* });
* @example
* import {bb} from "billboard.js";
* import TableView from "billboard.js/dist/billboardjs-plugin-tableview.esm";
*
* bb.generate({
* ...
* plugins: [
* new TableView({ ... })
* ]
* })
*/
export default class TableView extends Plugin {
static version = `0.0.1`;
private config;
private element;

constructor(options) {
super(options);
this.config = new Options();

return this;
}

$beforeInit(): void {
loadConfig.call(this, this.options);
}

$init(): void {
const {class: className, selector, style} = this.config;
let element = document.querySelector(
selector || `.${className || defaultStyle.class}`
);

if (!element) {
const chart = this.$$.$el.chart.node();

element = document.createElement("table");
chart.parentNode.insertBefore(element, chart.nextSibling);
}

if (element.tagName !== "TABLE") {
const table = document.createElement("table");

element.appendChild(table);
element = table;
}

// append default css style
if (style && !document.getElementById(defaultStyle.id)) {
const s = document.createElement("style");

s.id = defaultStyle.id;
s.innerHTML = defaultStyle.rule;

(document.head || document.getElementsByTagName("head")[0])
.appendChild(s);
}

element.classList.add(...[style && defaultStyle.class, className].filter(Boolean));

this.element = element;
}

/**
* Generate table
* @private
*/
generateTable(): void {
const {$$, config, element} = this;
const dataToShow = $$.filterTargetsToShow($$.data.targets);

let thead = tplProcess(tpl.thead, {
title: dataToShow.length ? this.config.categoryTitle : ""
});
let tbody = "";
const rows: number|string[][] = [];

dataToShow.forEach(v => {
thead += tplProcess(tpl.thead, {title: v.id});

// make up value rows
v.values.forEach((d, i: number) => {
if (!rows[i]) {
rows[i] = [d.x];
}

rows[i].push(d.value);
});
});

rows.forEach(v => {
tbody += `<tr>${
v.map((d, i) => tplProcess(i ? tpl.tbody : tpl.tbodyHeader, {
value: i === 0 ?
config.categoryFormat.bind(this)(d) :
(isNumber(d) ? d.toLocaleString() : "")
})).join("")
}</tr>`;
});

const rx = /<[^>]+><\/[^>]+>/g;
const r = tplProcess(tpl.body, {
...config,
title: config.title || $$.config.title_text || "",
thead,
tbody
}).replace(rx, "");

element.innerHTML = r;
}

$redraw(): void {
const {state} = this.$$;
const doNotUpdate = state.resizing || (!this.config.updateOnToggle && state.toggling);

!doNotUpdate && this.generateTable();
}

$willDestroy(): void {
this.element.parentNode.removeChild(this.element);

// remove default css style when left one chart instance
if (this.$$.charts.length === 1) {
const s = document.getElementById(defaultStyle.id);

s?.parentNode?.removeChild(s);
}
}
}
Loading

0 comments on commit 215b611

Please sign in to comment.