From 49989e2873bb409d9becea07e1d67e28ec62313e Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Fri, 29 Jun 2018 17:05:03 -0400 Subject: [PATCH 01/18] build: start new package for d3chart --- packages/d3chart/CHANGELOG.md | 6 +++ packages/d3chart/README.md | 12 +++++ packages/d3chart/karma.conf.js | 45 ++++++++++++++++ packages/d3chart/package.json | 50 ++++++++++++++++++ packages/d3chart/src/index.js | 1 + packages/d3chart/tests.js | 2 + packages/d3chart/webpack.config.babel.js | 26 ++++++++++ packages/d3chart/webpack.config.test.babel.js | 52 +++++++++++++++++++ 8 files changed, 194 insertions(+) create mode 100644 packages/d3chart/CHANGELOG.md create mode 100644 packages/d3chart/README.md create mode 100644 packages/d3chart/karma.conf.js create mode 100644 packages/d3chart/package.json create mode 100644 packages/d3chart/src/index.js create mode 100644 packages/d3chart/tests.js create mode 100644 packages/d3chart/webpack.config.babel.js create mode 100644 packages/d3chart/webpack.config.test.babel.js diff --git a/packages/d3chart/CHANGELOG.md b/packages/d3chart/CHANGELOG.md new file mode 100644 index 00000000..8ff36790 --- /dev/null +++ b/packages/d3chart/CHANGELOG.md @@ -0,0 +1,6 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + diff --git a/packages/d3chart/README.md b/packages/d3chart/README.md new file mode 100644 index 00000000..ae676cdb --- /dev/null +++ b/packages/d3chart/README.md @@ -0,0 +1,12 @@ +# ![](docs/static/favicon.png) Candela D3Chart + +Candela D3Chart provides a convenient mixin for constructing freeform +visualization components conforming to the D3 reusable chart paradigm. + +Candela is an open-source framework for creating interoperable, reusable +visualization components for the web. Candela is a part of +[Kitware](http://www.kitware.com)'s [Resonant](http://resonant.kitware.com) +platform. Candela focuses on making scalable, rich visualizations available with +a normalized API for use in real-world data science applications. + +Please see our documentation at https://candela.readthedocs.io. diff --git a/packages/d3chart/karma.conf.js b/packages/d3chart/karma.conf.js new file mode 100644 index 00000000..5b88f6b6 --- /dev/null +++ b/packages/d3chart/karma.conf.js @@ -0,0 +1,45 @@ +var path = require('path'); + +var kconfig = { + singleRun: true, + client: { + captureConsole: false + }, + browsers: [ + 'ChromeHeadless' + ], + frameworks: [ + 'tap' + ], + reporters: [ + 'tap-pretty' + ], + tapReporter: { + prettify: require('tap-spec') + }, + files: [ + 'build/test.unit.js' + ] +}; + +if (process.env.COVERAGE) { + kconfig.files = [ + 'build/test.coverage.js' + ]; + kconfig.reporters = [ + 'coverage-istanbul' + ]; + kconfig.coverageIstanbulReporter = { + reports: ['text-summary', 'html'], + dir: path.resolve('../../build/coverage'), + 'report-config': { + html: { + subdir: 'html/core' + } + } + }; +} + +module.exports = function (config) { + config.set(kconfig); +}; diff --git a/packages/d3chart/package.json b/packages/d3chart/package.json new file mode 100644 index 00000000..3052d1e6 --- /dev/null +++ b/packages/d3chart/package.json @@ -0,0 +1,50 @@ +{ + "name": "@candela/d3chart", + "version": "0.23.3", + "description": "Candela D3Chart mixin library", + "main": "dist/candela-d3chart.js", + "scripts": { + "build": "webpack --mode development", + "build:prod": "webpack --mode production", + "clean": "rm -rf dist build", + "lint": "semistandard | snazzy", + "build:test": "webpack --config webpack.config.test.babel.js --mode development", + "test": "karma start --colors --log-level error" + }, + "publishConfig": { + "access": "public" + }, + "babel": { + "presets": [ + "@babel/env" + ] + }, + "keywords": [], + "author": "Kitware Inc.", + "license": "Apache-2.0", + "semistandard": { + "ignore": [ + "dist", + "build" + ] + }, + "devDependencies": { + "@babel/core": "^7.0.0-beta.44", + "@babel/preset-env": "^7.0.0-beta.44", + "@babel/register": "^7.0.0-beta.44", + "babel-loader": "^8.0.0-beta.2", + "istanbul-instrumenter-loader": "^3.0.1", + "karma": "^2.0.2", + "karma-chrome-launcher": "^2.2.0", + "karma-coverage-istanbul-reporter": "^1.4.2", + "karma-tap": "^4.1.3", + "karma-tap-pretty-reporter": "^4.0.0", + "semistandard": "^7.0.5", + "snazzy": "^4.0.0", + "tap-spec": "^4.1.1", + "tape": "^4.9.0", + "tape-catch": "^1.0.6", + "webpack": "^4.6.0", + "webpack-cli": "^2.0.14" + } +} diff --git a/packages/d3chart/src/index.js b/packages/d3chart/src/index.js new file mode 100644 index 00000000..b1bb17f7 --- /dev/null +++ b/packages/d3chart/src/index.js @@ -0,0 +1 @@ +export const value = 3; diff --git a/packages/d3chart/tests.js b/packages/d3chart/tests.js new file mode 100644 index 00000000..da9600c0 --- /dev/null +++ b/packages/d3chart/tests.js @@ -0,0 +1,2 @@ +var context = require.context('./src', true, /\.js$/); +context.keys().forEach(context); diff --git a/packages/d3chart/webpack.config.babel.js b/packages/d3chart/webpack.config.babel.js new file mode 100644 index 00000000..96c1e9ff --- /dev/null +++ b/packages/d3chart/webpack.config.babel.js @@ -0,0 +1,26 @@ +import path from 'path'; + +export default { + entry: './src/index.js', + output: { + libraryTarget: 'umd', + path: path.resolve('dist'), + filename: 'candela-d3chart.js' + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: [ + '@babel/env' + ] + } + } + } + ] + } +}; diff --git a/packages/d3chart/webpack.config.test.babel.js b/packages/d3chart/webpack.config.test.babel.js new file mode 100644 index 00000000..2dcb1396 --- /dev/null +++ b/packages/d3chart/webpack.config.test.babel.js @@ -0,0 +1,52 @@ +import path from 'path'; + +import moduleConfig from './webpack.config.babel'; + +let config = { + entry: './tests.js', + output: { + path: path.resolve('build'), + filename: 'test.unit.js' + }, + module: moduleConfig.module, + node: { + fs: 'empty' + } +}; + +if (process.env.COVERAGE) { + config.devtool = 'cheap-module-source-map'; + config.output.filename = 'test.coverage.js'; + config.module = { + rules: [ + { + enforce: 'post', + test: /\.js$/, + use: { + loader: 'istanbul-instrumenter-loader', + options: { + esModules: true + } + }, + include: [ + path.resolve('src/') + ], + exclude: /\.spec\.js$/ + }, + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: [ + '@babel/env' + ] + } + } + } + ] + }; +} + +export default config; From c5f32aefcc8abe9d4cc87b85f16a3876d7b69a3d Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Mon, 2 Jul 2018 11:33:58 -0400 Subject: [PATCH 02/18] feat: add D3Chart mixin --- packages/d3chart/package.json | 7 ++ packages/d3chart/src/D3Chart.js | 138 +++++++++++++++++++++++ packages/d3chart/src/index.js | 2 +- packages/d3chart/webpack.config.babel.js | 6 + packages/examples/d3chart/index.js | 15 +++ packages/examples/package.json | 27 ++--- 6 files changed, 181 insertions(+), 14 deletions(-) create mode 100644 packages/d3chart/src/D3Chart.js create mode 100644 packages/examples/d3chart/index.js diff --git a/packages/d3chart/package.json b/packages/d3chart/package.json index 3052d1e6..75b0d369 100644 --- a/packages/d3chart/package.json +++ b/packages/d3chart/package.json @@ -28,11 +28,18 @@ "build" ] }, + "dependencies": { + "@candela/core": "^0.23.3", + "@candela/size": "^0.23.3", + "d3-selection": "^1.1.0", + "d3-transition": "^1.1.0" + }, "devDependencies": { "@babel/core": "^7.0.0-beta.44", "@babel/preset-env": "^7.0.0-beta.44", "@babel/register": "^7.0.0-beta.44", "babel-loader": "^8.0.0-beta.2", + "d3-selection": "^1.1.0", "istanbul-instrumenter-loader": "^3.0.1", "karma": "^2.0.2", "karma-chrome-launcher": "^2.2.0", diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js new file mode 100644 index 00000000..3b9e6193 --- /dev/null +++ b/packages/d3chart/src/D3Chart.js @@ -0,0 +1,138 @@ +import { VisComponent } from '@candela/core'; +import { InitSize } from '@candela/size'; + +import { select } from 'd3-selection'; +import 'd3-transition'; + +export const Margin = Base => class extends Base { + constructor () { + super(...arguments); + + this._margin = { + top: null, + right: null, + bottom: null, + left: null + } + } + + margin (m) { + if (m === undefined) { + return {...this._margin}; + } + + let mm = {...m}; + for (let key in mm) { + if (!(key in this._margin)) { + delete mm[key]; + } + } + + console.log('mm', mm); + + this._margin = { + ...this._margin, + ...mm + }; + + return this; + } +}; + +export const D3Chart = Base => class extends Margin(InitSize(Base)) { + constructor () { + super(...arguments); + + this.svg = select(this.el) + .append('svg') + .attr('xmlns', 'http://www.w3.org/2000/svg'); + + // A root-level group element. + this.root = this.svg.append('g'); + + // Group elements to represent all four margins of the plot. + this.left = this.root.append('g') + .classed('left', true); + this.bottom = this.root.append('g') + .classed('bottom', true); + this.right = this.root.append('g') + .classed('right', true); + this.top = this.root.append('g') + .classed('top', true); + + // The central area where the main plot will go. + this.plot = this.root.append('g') + .classed('plot', true); + } + + initD3Chart () { + this.svg.attr('width', this.width) + .attr('height', this.height); + + const margin = this.margin(); + + this.left.attr('transform', `translate(0,${margin.top})`); + this.bottom.attr('transform', `translate(${margin.left},${this.height - margin.bottom})`); + this.right.attr('transform', `translate(${this.width - margin.right},${margin.top})`); + this.top.attr('transform', `translate(${margin.left},0)`); + this.plot.attr('transform', `translate(${margin.left},${margin.top})`); + } +}; + +export class Swatches extends D3Chart(VisComponent) { + constructor (el, options) { + super(el, options); + + this.width = options.width; + this.height = options.height; + this.margin(options.margin) + .initD3Chart(); + + const margin = this.margin(); + console.log(margin); + + this.left.append('rect') + .attr('x', 0) + .attr('y', 0) + .attr('width', margin.left) + .attr('height', this.height - margin.bottom - margin.top) + .style('stroke', 'black') + .style('fill', 'red'); + + this.bottom.append('rect') + .attr('x', 0) + .attr('y', 0) + .attr('width', this.width - margin.left - margin.right) + .attr('height', margin.bottom) + .style('stroke', 'black') + .style('fill', 'green'); + + this.right.append('rect') + .attr('x', 0) + .attr('y', 0) + .attr('width', margin.right) + .attr('height', this.height - margin.bottom - margin.top) + .style('stroke', 'black') + .style('fill', 'cyan'); + + this.top.append('rect') + .attr('x', 0) + .attr('y', 0) + .attr('width', this.width - margin.left - margin.right) + .attr('height', margin.top) + .style('stroke', 'black') + .style('fill', 'yellow'); + + this.plot.append('rect') + .attr('x', 0) + .attr('y', 0) + .attr('width', this.width - margin.left - margin.right) + .attr('height', this.height - margin.top - margin.bottom) + .style('stroke', 'black') + .style('fill', 'blue'); + } + + render () { + console.log('render()'); + } +}; diff --git a/packages/d3chart/src/index.js b/packages/d3chart/src/index.js index b1bb17f7..37c93e8a 100644 --- a/packages/d3chart/src/index.js +++ b/packages/d3chart/src/index.js @@ -1 +1 @@ -export const value = 3; +export { D3Chart, Swatches } from './D3Chart'; diff --git a/packages/d3chart/webpack.config.babel.js b/packages/d3chart/webpack.config.babel.js index 96c1e9ff..948cd7d3 100644 --- a/packages/d3chart/webpack.config.babel.js +++ b/packages/d3chart/webpack.config.babel.js @@ -7,6 +7,12 @@ export default { path: path.resolve('dist'), filename: 'candela-d3chart.js' }, + externals: [ + '@candela/core', + '@candela/size', + 'd3-selection', + 'd3-transition' + ], module: { rules: [ { diff --git a/packages/examples/d3chart/index.js b/packages/examples/d3chart/index.js new file mode 100644 index 00000000..354dd797 --- /dev/null +++ b/packages/examples/d3chart/index.js @@ -0,0 +1,15 @@ +import { Swatches } from '@candela/d3chart'; +import showComponent from '../util/showComponent'; + +window.onload = () => { + showComponent(Swatches, { + width: 300, + height: 300, + margin: { + top: 20, + right: 30, + bottom: 40, + left: 50 + } + }); +}; diff --git a/packages/examples/package.json b/packages/examples/package.json index 2cea77f9..51237414 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -26,19 +26,20 @@ ] }, "dependencies": { - "@candela/events": "^0.23.4", - "@candela/geojs": "^0.23.4", - "@candela/glo": "^0.23.4", - "@candela/lineup": "^0.23.4", - "@candela/onset": "^0.23.4", - "@candela/sententree": "^0.23.4", - "@candela/similaritygraph": "^0.23.4", - "@candela/size": "^0.23.4", - "@candela/stats": "^0.23.4", - "@candela/trackerdash": "^0.23.4", - "@candela/treeheatmap": "^0.23.4", - "@candela/upset": "^0.23.4", - "@candela/vega": "^0.23.4" + "@candela/d3chart": "^0.23.3", + "@candela/events": "^0.23.3", + "@candela/geojs": "^0.23.3", + "@candela/glo": "^0.23.3", + "@candela/lineup": "^0.23.3", + "@candela/onset": "^0.23.3", + "@candela/sententree": "^0.23.3", + "@candela/similaritygraph": "^0.23.3", + "@candela/size": "^0.23.3", + "@candela/stats": "^0.23.3", + "@candela/trackerdash": "^0.23.3", + "@candela/treeheatmap": "^0.23.3", + "@candela/upset": "^0.23.3", + "@candela/vega": "^0.23.3" }, "devDependencies": { "@babel/core": "^7.0.0-beta.44", From c49736212c275b120e5df2565f54b888b21a19bc Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Tue, 3 Jul 2018 15:42:32 -0400 Subject: [PATCH 03/18] feat: add Axes mixin --- packages/d3chart/src/D3Chart.js | 170 +++++++++++++++++++++++++++++++- packages/d3chart/src/index.js | 2 +- 2 files changed, 169 insertions(+), 3 deletions(-) diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index 3b9e6193..cd7b5eed 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -3,6 +3,7 @@ import { InitSize } from '@candela/size'; import { select } from 'd3-selection'; import 'd3-transition'; +import { axisLeft, axisBottom } from 'd3-axis'; export const Margin = Base => class extends Base { constructor () { @@ -13,7 +14,7 @@ export const Margin = Base => class extends Base { right: null, bottom: null, left: null - } + }; } margin (m) { @@ -37,6 +38,63 @@ export const Margin = Base => class extends Base { return this; } + + marginBounds (region) { + const margin = this.margin(); + let bounds; + + switch (region) { + case 'left': + bounds = { + x: 0, + y: margin.top, + width: margin.left, + height: this.height - margin.top - margin.bottom + }; + break; + + case 'right': + bounds = { + x: this.width - margin.right, + y: margin.top, + width: margin.right, + height: this.height - margin.top - margin.bottom + }; + break; + + case 'top': + bounds = { + x: margin.left, + y: 0, + width: this.width - margin.left - margin.right, + height: margin.top + }; + break; + + case 'bottom': + bounds = { + x: margin.left, + y: this.height - margin.bottom, + width: this.width - margin.left - margin.right, + height: margin.bottom + }; + break; + + case 'plot': + bounds = { + x: margin.left, + y: margin.top, + width: this.width - margin.left - margin.right, + height: this.height - margin.top - margin.bottom + }; + break; + + default: + throw new Error(`illegal region identifier: "${region}"`); + } + + return bounds; + } }; export const D3Chart = Base => class extends Margin(InitSize(Base)) { @@ -79,6 +137,114 @@ export const D3Chart = Base => class extends Margin(InitSize(Base)) { } }; +export const AxisChart = Base => class extends Base { + constructor () { + super(...arguments); + + this._axes = { + leftScale: null, + bottomScale: null, + leftAxis: null, + bottomAxis: null, + leftGroup: null, + bottomGroup: null + }; + } + + _setAxis (scale, direction, scaleFunc) { + const axisProp = `${direction}Axis`; + const scaleProp = `${direction}Scale`; + const groupProp = `${direction}Group`; + + const bounds = this.marginBounds('plot'); + + if (direction === 'left' || direction === 'right') { + scale.range([bounds.height, 0]); + } else { + scale.range([0, bounds.width]); + } + + this._axes[scaleProp] = scale; + + let axis = this._axes[groupProp]; + if (!axis) { + axis = this._axes[groupProp] = this[direction].append('g'); + axis.append('text') + .attr('fill', '#000') + .style('font-size', '11pt') + .style('font-weight', 'bold'); + + if (direction === 'left') { + const margin = this.margin(); + axis.attr('transform', `translate(${margin.left},0)`); + } + } else { + axis.selectAll('*').remove(); + } + + axis.call(this._axes[axisProp] = scaleFunc(scale)); + } + + leftAxis () { + return this._axes.leftAxis; + } + + bottomAxis () { + return this._axes.bottomAxis; + } + + leftScale (scale) { + if (scale) { + this._setAxis(scale, 'left', axisLeft); + return this; + } else { + return this._axes.leftScale; + } + } + + bottomScale (scale) { + if (scale) { + this._setAxis(scale, 'bottom', axisBottom); + return this; + } else { + return this._axes.bottomScale; + } + } + + leftLabel (label) { + const leftBounds = this.marginBounds('left'); + if (label) { + this._axes.leftGroup.select('text') + .attr('transform', 'rotate(-90)') + .attr('x', -leftBounds.height / 2) + .attr('y', '-40') + .text(label); + } else { + return this._axes.leftLabel; + } + } + + bottomLabel (label) { + const bottomBounds = this.marginBounds('bottom'); + if (label) { + this._axes.bottomGroup.select('text') + .attr('x', bottomBounds.width / 2) + .attr('y', '40') + .text(label); + } else { + return this._axes.bottomLabel; + } + } + + renderLeftAxis () { + this._axes.leftGroup.call(this.leftAxis()); + } + + renderBottomAxis () { + this._axes.bottomGroup.call(this.bottomAxis()); + } +}; + export class Swatches extends D3Chart(VisComponent) { constructor (el, options) { super(el, options); @@ -135,4 +301,4 @@ export class Swatches extends D3Chart(VisComponent) { render () { console.log('render()'); } -}; +} diff --git a/packages/d3chart/src/index.js b/packages/d3chart/src/index.js index 37c93e8a..f23ab73e 100644 --- a/packages/d3chart/src/index.js +++ b/packages/d3chart/src/index.js @@ -1 +1 @@ -export { D3Chart, Swatches } from './D3Chart'; +export * from './D3Chart'; From 23ee2286c22bfe24ae189bbc3190fd31e8679d00 Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Tue, 3 Jul 2018 19:22:31 -0400 Subject: [PATCH 04/18] build: fix nixpkgs version in shell.nix, and clean file up --- shell.nix | 64 +++++++++++-------------------------------------------- 1 file changed, 13 insertions(+), 51 deletions(-) diff --git a/shell.nix b/shell.nix index a3d63573..1756edcb 100644 --- a/shell.nix +++ b/shell.nix @@ -1,56 +1,18 @@ -{ pkgs ? import {} }: +{ + pkgs ? import (fetchTarball { + url = https://github.com/NixOS/nixpkgs-channels/archive/14a9ca27e69e33ac8ffb708de08883f8079f954a.tar.gz; + sha256 = "1grsq8mcpl88v6kz8dp0vsybr0wzfg4pvhamj42dpd3vgr93l2ib"; + }) {} +}: -with pkgs; - -let libs = [ - alsaLib - atk - cairo - cups - dbus - expat - fontconfig.lib - fontconfig.dev - gdk_pixbuf - glib - gnome3.gconf - gtk2 - nspr - nss - pango - python27Packages.sphinx - python27Packages.sphinx_rtd_theme - freetype - stdenv.cc.cc.lib - zlib -] ++ (with xorg; [ - libX11 - libxcb - libXcomposite - libXcursor - libXdamage - libXext - libXi - libXfixes - libXrandr - libXrender - libXScrnSaver - libXtst -]); -in stdenv.mkDerivation { +pkgs.stdenv.mkDerivation { name = "candela"; - buildInputs = [ - electron - nodejs-7_x - phantomjs2 + buildInputs = with pkgs; [ + cairo + nodejs-9_x + nodePackages_8_x.node-gyp + nodePackages.lerna pkgconfig - python2 - ] ++ libs; - - C_INCLUDE_PATH = "${fontconfig.dev}/include"; - - shellHook = '' - echo "Candela nix dev environment" - ''; + ]; } From 1db4ca4579314b006e172434c604273af3328b8c Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Wed, 4 Jul 2018 15:35:04 -0400 Subject: [PATCH 05/18] feat: add Tooltip mixin --- packages/d3chart/package.json | 1 + packages/d3chart/src/D3Chart.js | 157 ++++++++++++++++++++++- packages/d3chart/webpack.config.babel.js | 1 + 3 files changed, 157 insertions(+), 2 deletions(-) diff --git a/packages/d3chart/package.json b/packages/d3chart/package.json index 75b0d369..5ea69b0c 100644 --- a/packages/d3chart/package.json +++ b/packages/d3chart/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@candela/core": "^0.23.3", + "@candela/events": "^0.23.3", "@candela/size": "^0.23.3", "d3-selection": "^1.1.0", "d3-transition": "^1.1.0" diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index cd7b5eed..0f4f7242 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -1,4 +1,5 @@ import { VisComponent } from '@candela/core'; +import { Events } from '@candela/events'; import { InitSize } from '@candela/size'; import { select } from 'd3-selection'; @@ -29,8 +30,6 @@ export const Margin = Base => class extends Base { } } - console.log('mm', mm); - this._margin = { ...this._margin, ...mm @@ -137,6 +136,160 @@ export const D3Chart = Base => class extends Margin(InitSize(Base)) { } }; +export const Interactive = Base => class extends Base { + initInteractive () { + const plotBounds = this.marginBounds('plot'); + + const target = this.root.append('rect') + .classed('interactive-target', true) + .attr('x', plotBounds.x) + .attr('y', plotBounds.y) + .attr('width', plotBounds.width) + .attr('height', plotBounds.height) + .style('opacity', 0.0); + + this._interactive = { + target + }; + } + + target () { + return this._interactive.target; + } + + mouseCoords () { + const event = window.event; + if (event) { + const bbox = this.target().node().getBoundingClientRect(); + return { + x: event.clientX - bbox.left, + y: event.clientY - bbox.top + }; + } + } +}; + +export const Crosshairs = Base => class extends Base { + initCrosshairs () { + const plotBounds = this.marginBounds('plot'); + this.target = this.plot.append('rect', ':first-child') + .classed('crosshairs-target', true) + .attr('width', plotBounds.width) + .attr('height', plotBounds.height) + .style('opacity', 0.0); + + const g = this.plot.append('g') + .classed('crosshairs', true) + .style('pointer-events', 'none'); + + const horz = this.bottomAxis() || this.topAxis(); + const vert = this.leftAxis() || this.rightAxis(); + + this.crosshairX = g.append('line') + .classed('crosshair-x', true) + .style('opacity', 0) + .style('stroke', 'lightgray') + .attr('x1', horz.range()[0]) + .attr('x2', horz.range()[1]); + + this.crosshairY = g.append('line') + .classed('crosshair-y', true) + .style('opacity', 0) + .style('stroke', 'lightgray') + .attr('y1', vert.range()[0]) + .attr('y2', vert.range()[1]); + + this.target.on('mouseover.crosshairs', () => { + this.show(); + }) + .on('mousemove.crosshairs', () => { + const mouse = this.mouseCoords(); + this.update(mouse.x, mouse.y); + }) + .on('mouseout.crosshairs', () => { + this.hide(); + }); + } + + mouseCoords () { + const event = window.event; + if (event) { + const bbox = this.target.node().getBoundingClientRect(); + return { + x: event.pageX - bbox.left, + y: event.pageY - bbox.top + }; + } + } + + update (x, y) { + this.crosshairX.attr('y1', y) + .attr('y2', y); + + crosshairY.attr('x1', mouse.x) + .attr('x2', mouse.x); + + this.emit('crosshairs.move', window.event); + }).on('mouseout.crosshairs', () => { + g.selectAll('line') + .style('opacity', 0); + + this.emit('crosshairs.out'); + }); + + this.crosshairY.attr('x1', x) + .attr('x2', x); + } + + show () { + this.crosshairX.style('opacity', 1); + this.crosshairY.style('opacity', 1); + } + + hide () { + this.crosshairX.style('opacity', 0); + this.crosshairY.style('opacity', 0); + } +}; + +export const Tooltip = Base => class extends Base { + constructor () { + super(...arguments); + + this._tooltip = {}; + } + + initTooltip (options = {}) { + this._tooltip.tooltip = select(this.el) + .append('div') + .style('opacity', 0) + .style('position', 'absolute') + .style('text-align', options.textAlign || 'center') + .style('width', options.width || '80px') + .style('height', options.height || '30px') + .style('padding', '2px') + .style('font', options.font || '12px sans-serif') + .style('background', options.background || 'lightgreen') + .style('border', '0px') + .style('border-radius', '8px') + .style('pointer-events', 'none'); + } + + tooltip () { + return this._tooltip.tooltip; + } + + showTT () { + this.tooltip() + .style('opacity', 1); + } + + hideTT () { + this.tooltip() + .style('opacity', 0); + } +}; + export const AxisChart = Base => class extends Base { constructor () { super(...arguments); diff --git a/packages/d3chart/webpack.config.babel.js b/packages/d3chart/webpack.config.babel.js index 948cd7d3..08063bf1 100644 --- a/packages/d3chart/webpack.config.babel.js +++ b/packages/d3chart/webpack.config.babel.js @@ -9,6 +9,7 @@ export default { }, externals: [ '@candela/core', + '@candela/events', '@candela/size', 'd3-selection', 'd3-transition' From 7cc24f0465e66175feef491ba91f5b6b0a78ce47 Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Thu, 12 Jul 2018 13:07:49 -0400 Subject: [PATCH 06/18] refactor: split Margin out into own file --- packages/d3chart/src/D3Chart.js | 91 +-------------------------------- packages/d3chart/src/Margin.js | 89 ++++++++++++++++++++++++++++++++ packages/d3chart/src/index.js | 1 + 3 files changed, 91 insertions(+), 90 deletions(-) create mode 100644 packages/d3chart/src/Margin.js diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index 0f4f7242..3c9d386f 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -1,100 +1,11 @@ import { VisComponent } from '@candela/core'; -import { Events } from '@candela/events'; import { InitSize } from '@candela/size'; import { select } from 'd3-selection'; import 'd3-transition'; import { axisLeft, axisBottom } from 'd3-axis'; -export const Margin = Base => class extends Base { - constructor () { - super(...arguments); - - this._margin = { - top: null, - right: null, - bottom: null, - left: null - }; - } - - margin (m) { - if (m === undefined) { - return {...this._margin}; - } - - let mm = {...m}; - for (let key in mm) { - if (!(key in this._margin)) { - delete mm[key]; - } - } - - this._margin = { - ...this._margin, - ...mm - }; - - return this; - } - - marginBounds (region) { - const margin = this.margin(); - let bounds; - - switch (region) { - case 'left': - bounds = { - x: 0, - y: margin.top, - width: margin.left, - height: this.height - margin.top - margin.bottom - }; - break; - - case 'right': - bounds = { - x: this.width - margin.right, - y: margin.top, - width: margin.right, - height: this.height - margin.top - margin.bottom - }; - break; - - case 'top': - bounds = { - x: margin.left, - y: 0, - width: this.width - margin.left - margin.right, - height: margin.top - }; - break; - - case 'bottom': - bounds = { - x: margin.left, - y: this.height - margin.bottom, - width: this.width - margin.left - margin.right, - height: margin.bottom - }; - break; - - case 'plot': - bounds = { - x: margin.left, - y: margin.top, - width: this.width - margin.left - margin.right, - height: this.height - margin.top - margin.bottom - }; - break; - - default: - throw new Error(`illegal region identifier: "${region}"`); - } - - return bounds; - } -}; +import { Margin } from './Margin'; export const D3Chart = Base => class extends Margin(InitSize(Base)) { constructor () { diff --git a/packages/d3chart/src/Margin.js b/packages/d3chart/src/Margin.js new file mode 100644 index 00000000..1e5f5f30 --- /dev/null +++ b/packages/d3chart/src/Margin.js @@ -0,0 +1,89 @@ +export const Margin = Base => class extends Base { + constructor () { + super(...arguments); + + this._margin = { + top: null, + right: null, + bottom: null, + left: null + }; + } + + margin (m) { + if (m === undefined) { + return {...this._margin}; + } + + let mm = {...m}; + for (let key in mm) { + if (!(key in this._margin)) { + delete mm[key]; + } + } + + this._margin = { + ...this._margin, + ...mm + }; + + return this; + } + + marginBounds (region) { + const margin = this.margin(); + let bounds; + + switch (region) { + case 'left': + bounds = { + x: 0, + y: margin.top, + width: margin.left, + height: this.height - margin.top - margin.bottom + }; + break; + + case 'right': + bounds = { + x: this.width - margin.right, + y: margin.top, + width: margin.right, + height: this.height - margin.top - margin.bottom + }; + break; + + case 'top': + bounds = { + x: margin.left, + y: 0, + width: this.width - margin.left - margin.right, + height: margin.top + }; + break; + + case 'bottom': + bounds = { + x: margin.left, + y: this.height - margin.bottom, + width: this.width - margin.left - margin.right, + height: margin.bottom + }; + break; + + case 'plot': + bounds = { + x: margin.left, + y: margin.top, + width: this.width - margin.left - margin.right, + height: this.height - margin.top - margin.bottom + }; + break; + + default: + throw new Error(`illegal region identifier: "${region}"`); + } + + return bounds; + } +}; diff --git a/packages/d3chart/src/index.js b/packages/d3chart/src/index.js index f23ab73e..e6aa6eb1 100644 --- a/packages/d3chart/src/index.js +++ b/packages/d3chart/src/index.js @@ -1 +1,2 @@ export * from './D3Chart'; +export * from './Margin'; From 7b9644e19d086e420409219eec72b6048d9d16eb Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Thu, 12 Jul 2018 13:08:09 -0400 Subject: [PATCH 07/18] refactor: use correct method calls (fix from git rebase) --- packages/d3chart/src/D3Chart.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index 3c9d386f..cf0024cf 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -93,8 +93,8 @@ export const Crosshairs = Base => class extends Base { .classed('crosshairs', true) .style('pointer-events', 'none'); - const horz = this.bottomAxis() || this.topAxis(); - const vert = this.leftAxis() || this.rightAxis(); + const horz = this.bottomScale() || this.topScale(); + const vert = this.leftScale() || this.rightScale(); this.crosshairX = g.append('line') .classed('crosshair-x', true) From b0848ae929db6b865f3a47621dd796c4bd2fe6ff Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Thu, 12 Jul 2018 13:08:34 -0400 Subject: [PATCH 08/18] refactor: remove stray code from rebase --- packages/d3chart/src/D3Chart.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index cf0024cf..991e0544 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -137,17 +137,6 @@ export const Crosshairs = Base => class extends Base { this.crosshairX.attr('y1', y) .attr('y2', y); - crosshairY.attr('x1', mouse.x) - .attr('x2', mouse.x); - - this.emit('crosshairs.move', window.event); - }).on('mouseout.crosshairs', () => { - g.selectAll('line') - .style('opacity', 0); - - this.emit('crosshairs.out'); - }); - this.crosshairY.attr('x1', x) .attr('x2', x); } From 96f12379f977323c944a11ee6ed05211c5ed5429 Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Thu, 12 Jul 2018 13:08:49 -0400 Subject: [PATCH 09/18] style: fix indentation errors --- packages/d3chart/src/D3Chart.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index 991e0544..309f89fa 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -111,15 +111,13 @@ export const Crosshairs = Base => class extends Base { .attr('y2', vert.range()[1]); this.target.on('mouseover.crosshairs', () => { - this.show(); - }) - .on('mousemove.crosshairs', () => { - const mouse = this.mouseCoords(); - this.update(mouse.x, mouse.y); - }) - .on('mouseout.crosshairs', () => { - this.hide(); - }); + this.show(); + }).on('mousemove.crosshairs', () => { + const mouse = this.mouseCoords(); + this.update(mouse.x, mouse.y); + }).on('mouseout.crosshairs', () => { + this.hide(); + }); } mouseCoords () { From 3b18e3c5bd4bff6cde8be1816180149332551bd0 Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Thu, 12 Jul 2018 13:10:17 -0400 Subject: [PATCH 10/18] refactor: remove Interactive mixin --- packages/d3chart/src/D3Chart.js | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index 309f89fa..e61bff5a 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -47,39 +47,6 @@ export const D3Chart = Base => class extends Margin(InitSize(Base)) { } }; -export const Interactive = Base => class extends Base { - initInteractive () { - const plotBounds = this.marginBounds('plot'); - - const target = this.root.append('rect') - .classed('interactive-target', true) - .attr('x', plotBounds.x) - .attr('y', plotBounds.y) - .attr('width', plotBounds.width) - .attr('height', plotBounds.height) - .style('opacity', 0.0); - - this._interactive = { - target - }; - } - - target () { - return this._interactive.target; - } - - mouseCoords () { - const event = window.event; - if (event) { - const bbox = this.target().node().getBoundingClientRect(); - return { - x: event.clientX - bbox.left, - y: event.clientY - bbox.top - }; - } - } -}; - export const Crosshairs = Base => class extends Base { initCrosshairs () { const plotBounds = this.marginBounds('plot'); From 62a62d2ce8d895a3ddef3d2e8fa60ba988081f07 Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Thu, 12 Jul 2018 13:14:42 -0400 Subject: [PATCH 11/18] refactor: move Crosshairs into own file --- packages/d3chart/src/Crosshairs.js | 69 +++++++++++++++++++++++++++++ packages/d3chart/src/D3Chart.js | 70 ------------------------------ packages/d3chart/src/index.js | 1 + 3 files changed, 70 insertions(+), 70 deletions(-) create mode 100644 packages/d3chart/src/Crosshairs.js diff --git a/packages/d3chart/src/Crosshairs.js b/packages/d3chart/src/Crosshairs.js new file mode 100644 index 00000000..5eb8e340 --- /dev/null +++ b/packages/d3chart/src/Crosshairs.js @@ -0,0 +1,69 @@ +export const Crosshairs = Base => class extends Base { + initCrosshairs () { + const plotBounds = this.marginBounds('plot'); + this.target = this.plot.append('rect', ':first-child') + .classed('crosshairs-target', true) + .attr('width', plotBounds.width) + .attr('height', plotBounds.height) + .style('opacity', 0.0); + + const g = this.plot.append('g') + .classed('crosshairs', true) + .style('pointer-events', 'none'); + + const horz = this.bottomScale() || this.topScale(); + const vert = this.leftScale() || this.rightScale(); + + this.crosshairX = g.append('line') + .classed('crosshair-x', true) + .style('opacity', 0) + .style('stroke', 'lightgray') + .attr('x1', horz.range()[0]) + .attr('x2', horz.range()[1]); + + this.crosshairY = g.append('line') + .classed('crosshair-y', true) + .style('opacity', 0) + .style('stroke', 'lightgray') + .attr('y1', vert.range()[0]) + .attr('y2', vert.range()[1]); + + this.target.on('mouseover.crosshairs', () => { + this.show(); + }).on('mousemove.crosshairs', () => { + const mouse = this.mouseCoords(); + this.update(mouse.x, mouse.y); + }).on('mouseout.crosshairs', () => { + this.hide(); + }); + } + + mouseCoords () { + const event = window.event; + if (event) { + const bbox = this.target.node().getBoundingClientRect(); + return { + x: event.pageX - bbox.left, + y: event.pageY - bbox.top + }; + } + } + + update (x, y) { + this.crosshairX.attr('y1', y) + .attr('y2', y); + + this.crosshairY.attr('x1', x) + .attr('x2', x); + } + + show () { + this.crosshairX.style('opacity', 1); + this.crosshairY.style('opacity', 1); + } + + hide () { + this.crosshairX.style('opacity', 0); + this.crosshairY.style('opacity', 0); + } +}; diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index e61bff5a..5183fb1a 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -47,76 +47,6 @@ export const D3Chart = Base => class extends Margin(InitSize(Base)) { } }; -export const Crosshairs = Base => class extends Base { - initCrosshairs () { - const plotBounds = this.marginBounds('plot'); - this.target = this.plot.append('rect', ':first-child') - .classed('crosshairs-target', true) - .attr('width', plotBounds.width) - .attr('height', plotBounds.height) - .style('opacity', 0.0); - - const g = this.plot.append('g') - .classed('crosshairs', true) - .style('pointer-events', 'none'); - - const horz = this.bottomScale() || this.topScale(); - const vert = this.leftScale() || this.rightScale(); - - this.crosshairX = g.append('line') - .classed('crosshair-x', true) - .style('opacity', 0) - .style('stroke', 'lightgray') - .attr('x1', horz.range()[0]) - .attr('x2', horz.range()[1]); - - this.crosshairY = g.append('line') - .classed('crosshair-y', true) - .style('opacity', 0) - .style('stroke', 'lightgray') - .attr('y1', vert.range()[0]) - .attr('y2', vert.range()[1]); - - this.target.on('mouseover.crosshairs', () => { - this.show(); - }).on('mousemove.crosshairs', () => { - const mouse = this.mouseCoords(); - this.update(mouse.x, mouse.y); - }).on('mouseout.crosshairs', () => { - this.hide(); - }); - } - - mouseCoords () { - const event = window.event; - if (event) { - const bbox = this.target.node().getBoundingClientRect(); - return { - x: event.pageX - bbox.left, - y: event.pageY - bbox.top - }; - } - } - - update (x, y) { - this.crosshairX.attr('y1', y) - .attr('y2', y); - - this.crosshairY.attr('x1', x) - .attr('x2', x); - } - - show () { - this.crosshairX.style('opacity', 1); - this.crosshairY.style('opacity', 1); - } - - hide () { - this.crosshairX.style('opacity', 0); - this.crosshairY.style('opacity', 0); - } -}; - export const Tooltip = Base => class extends Base { constructor () { super(...arguments); diff --git a/packages/d3chart/src/index.js b/packages/d3chart/src/index.js index e6aa6eb1..bdd3a6b4 100644 --- a/packages/d3chart/src/index.js +++ b/packages/d3chart/src/index.js @@ -1,2 +1,3 @@ +export * from './Crosshairs'; export * from './D3Chart'; export * from './Margin'; From aa5452420e8a078f28ff260b44ef8adf0d009d3d Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Thu, 12 Jul 2018 15:15:31 -0400 Subject: [PATCH 12/18] refactor: split Tooltip into own file --- packages/d3chart/src/D3Chart.js | 38 ------------------------------- packages/d3chart/src/Tooltip.js | 40 +++++++++++++++++++++++++++++++++ packages/d3chart/src/index.js | 1 + 3 files changed, 41 insertions(+), 38 deletions(-) create mode 100644 packages/d3chart/src/Tooltip.js diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index 5183fb1a..95f7fa18 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -47,44 +47,6 @@ export const D3Chart = Base => class extends Margin(InitSize(Base)) { } }; -export const Tooltip = Base => class extends Base { - constructor () { - super(...arguments); - - this._tooltip = {}; - } - - initTooltip (options = {}) { - this._tooltip.tooltip = select(this.el) - .append('div') - .style('opacity', 0) - .style('position', 'absolute') - .style('text-align', options.textAlign || 'center') - .style('width', options.width || '80px') - .style('height', options.height || '30px') - .style('padding', '2px') - .style('font', options.font || '12px sans-serif') - .style('background', options.background || 'lightgreen') - .style('border', '0px') - .style('border-radius', '8px') - .style('pointer-events', 'none'); - } - - tooltip () { - return this._tooltip.tooltip; - } - - showTT () { - this.tooltip() - .style('opacity', 1); - } - - hideTT () { - this.tooltip() - .style('opacity', 0); - } -}; - export const AxisChart = Base => class extends Base { constructor () { super(...arguments); diff --git a/packages/d3chart/src/Tooltip.js b/packages/d3chart/src/Tooltip.js new file mode 100644 index 00000000..01fc1105 --- /dev/null +++ b/packages/d3chart/src/Tooltip.js @@ -0,0 +1,40 @@ +import { select } from 'd3-selection'; +import 'd3-transition'; + +export const Tooltip = Base => class extends Base { + constructor () { + super(...arguments); + + this._tooltip = {}; + } + + initTooltip (options = {}) { + this._tooltip.tooltip = select(this.el) + .append('div') + .style('opacity', 0) + .style('position', 'absolute') + .style('text-align', options.textAlign || 'center') + .style('width', options.width || '80px') + .style('height', options.height || '30px') + .style('padding', '2px') + .style('font', options.font || '12px sans-serif') + .style('background', options.background || 'lightgreen') + .style('border', '0px') + .style('border-radius', '8px') + .style('pointer-events', 'none'); + } + + tooltip () { + return this._tooltip.tooltip; + } + + showTT () { + this.tooltip() + .style('opacity', 1); + } + + hideTT () { + this.tooltip() + .style('opacity', 0); + } +}; diff --git a/packages/d3chart/src/index.js b/packages/d3chart/src/index.js index bdd3a6b4..e1486314 100644 --- a/packages/d3chart/src/index.js +++ b/packages/d3chart/src/index.js @@ -1,3 +1,4 @@ export * from './Crosshairs'; export * from './D3Chart'; export * from './Margin'; +export * from './Tooltip'; From 9c74ce16c951988db4ec7403c1a3183f9f6caeb5 Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Thu, 12 Jul 2018 15:16:08 -0400 Subject: [PATCH 13/18] refactor: split AxisChart into own file --- packages/d3chart/src/AxisChart.js | 109 ++++++++++++++++++++++++++++++ packages/d3chart/src/D3Chart.js | 109 ------------------------------ packages/d3chart/src/index.js | 1 + 3 files changed, 110 insertions(+), 109 deletions(-) create mode 100644 packages/d3chart/src/AxisChart.js diff --git a/packages/d3chart/src/AxisChart.js b/packages/d3chart/src/AxisChart.js new file mode 100644 index 00000000..c9c57e06 --- /dev/null +++ b/packages/d3chart/src/AxisChart.js @@ -0,0 +1,109 @@ +import { axisLeft, axisBottom } from 'd3-axis'; + +export const AxisChart = Base => class extends Base { + constructor () { + super(...arguments); + + this._axes = { + leftScale: null, + bottomScale: null, + leftAxis: null, + bottomAxis: null, + leftGroup: null, + bottomGroup: null + }; + } + + _setAxis (scale, direction, scaleFunc) { + const axisProp = `${direction}Axis`; + const scaleProp = `${direction}Scale`; + const groupProp = `${direction}Group`; + + const bounds = this.marginBounds('plot'); + + if (direction === 'left' || direction === 'right') { + scale.range([bounds.height, 0]); + } else { + scale.range([0, bounds.width]); + } + + this._axes[scaleProp] = scale; + + let axis = this._axes[groupProp]; + if (!axis) { + axis = this._axes[groupProp] = this[direction].append('g'); + axis.append('text') + .attr('fill', '#000') + .style('font-size', '11pt') + .style('font-weight', 'bold'); + + if (direction === 'left') { + const margin = this.margin(); + axis.attr('transform', `translate(${margin.left},0)`); + } + } else { + axis.selectAll('*').remove(); + } + + axis.call(this._axes[axisProp] = scaleFunc(scale)); + } + + leftAxis () { + return this._axes.leftAxis; + } + + bottomAxis () { + return this._axes.bottomAxis; + } + + leftScale (scale) { + if (scale) { + this._setAxis(scale, 'left', axisLeft); + return this; + } else { + return this._axes.leftScale; + } + } + + bottomScale (scale) { + if (scale) { + this._setAxis(scale, 'bottom', axisBottom); + return this; + } else { + return this._axes.bottomScale; + } + } + + leftLabel (label) { + const leftBounds = this.marginBounds('left'); + if (label) { + this._axes.leftGroup.select('text') + .attr('transform', 'rotate(-90)') + .attr('x', -leftBounds.height / 2) + .attr('y', '-40') + .text(label); + } else { + return this._axes.leftLabel; + } + } + + bottomLabel (label) { + const bottomBounds = this.marginBounds('bottom'); + if (label) { + this._axes.bottomGroup.select('text') + .attr('x', bottomBounds.width / 2) + .attr('y', '40') + .text(label); + } else { + return this._axes.bottomLabel; + } + } + + renderLeftAxis () { + this._axes.leftGroup.call(this.leftAxis()); + } + + renderBottomAxis () { + this._axes.bottomGroup.call(this.bottomAxis()); + } +}; diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index 95f7fa18..fc94c035 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -3,7 +3,6 @@ import { InitSize } from '@candela/size'; import { select } from 'd3-selection'; import 'd3-transition'; -import { axisLeft, axisBottom } from 'd3-axis'; import { Margin } from './Margin'; @@ -47,114 +46,6 @@ export const D3Chart = Base => class extends Margin(InitSize(Base)) { } }; -export const AxisChart = Base => class extends Base { - constructor () { - super(...arguments); - - this._axes = { - leftScale: null, - bottomScale: null, - leftAxis: null, - bottomAxis: null, - leftGroup: null, - bottomGroup: null - }; - } - - _setAxis (scale, direction, scaleFunc) { - const axisProp = `${direction}Axis`; - const scaleProp = `${direction}Scale`; - const groupProp = `${direction}Group`; - - const bounds = this.marginBounds('plot'); - - if (direction === 'left' || direction === 'right') { - scale.range([bounds.height, 0]); - } else { - scale.range([0, bounds.width]); - } - - this._axes[scaleProp] = scale; - - let axis = this._axes[groupProp]; - if (!axis) { - axis = this._axes[groupProp] = this[direction].append('g'); - axis.append('text') - .attr('fill', '#000') - .style('font-size', '11pt') - .style('font-weight', 'bold'); - - if (direction === 'left') { - const margin = this.margin(); - axis.attr('transform', `translate(${margin.left},0)`); - } - } else { - axis.selectAll('*').remove(); - } - - axis.call(this._axes[axisProp] = scaleFunc(scale)); - } - - leftAxis () { - return this._axes.leftAxis; - } - - bottomAxis () { - return this._axes.bottomAxis; - } - - leftScale (scale) { - if (scale) { - this._setAxis(scale, 'left', axisLeft); - return this; - } else { - return this._axes.leftScale; - } - } - - bottomScale (scale) { - if (scale) { - this._setAxis(scale, 'bottom', axisBottom); - return this; - } else { - return this._axes.bottomScale; - } - } - - leftLabel (label) { - const leftBounds = this.marginBounds('left'); - if (label) { - this._axes.leftGroup.select('text') - .attr('transform', 'rotate(-90)') - .attr('x', -leftBounds.height / 2) - .attr('y', '-40') - .text(label); - } else { - return this._axes.leftLabel; - } - } - - bottomLabel (label) { - const bottomBounds = this.marginBounds('bottom'); - if (label) { - this._axes.bottomGroup.select('text') - .attr('x', bottomBounds.width / 2) - .attr('y', '40') - .text(label); - } else { - return this._axes.bottomLabel; - } - } - - renderLeftAxis () { - this._axes.leftGroup.call(this.leftAxis()); - } - - renderBottomAxis () { - this._axes.bottomGroup.call(this.bottomAxis()); - } -}; - export class Swatches extends D3Chart(VisComponent) { constructor (el, options) { super(el, options); diff --git a/packages/d3chart/src/index.js b/packages/d3chart/src/index.js index e1486314..5e77bd8d 100644 --- a/packages/d3chart/src/index.js +++ b/packages/d3chart/src/index.js @@ -1,3 +1,4 @@ +export * from './AxisChart'; export * from './Crosshairs'; export * from './D3Chart'; export * from './Margin'; From 7f0653fe82b4212e075c68464098e10b15363faf Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Fri, 13 Jul 2018 13:16:16 -0400 Subject: [PATCH 14/18] refactor: lift Margin mixin functionality into an Impl class --- packages/d3chart/src/AxisChart.js | 8 ++--- packages/d3chart/src/Crosshairs.js | 2 +- packages/d3chart/src/D3Chart.js | 7 ++-- packages/d3chart/src/Margin.js | 55 ++++++++++++++++++------------ 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/packages/d3chart/src/AxisChart.js b/packages/d3chart/src/AxisChart.js index c9c57e06..d9a305e8 100644 --- a/packages/d3chart/src/AxisChart.js +++ b/packages/d3chart/src/AxisChart.js @@ -19,7 +19,7 @@ export const AxisChart = Base => class extends Base { const scaleProp = `${direction}Scale`; const groupProp = `${direction}Group`; - const bounds = this.marginBounds('plot'); + const bounds = this.margin.bounds('plot'); if (direction === 'left' || direction === 'right') { scale.range([bounds.height, 0]); @@ -38,7 +38,7 @@ export const AxisChart = Base => class extends Base { .style('font-weight', 'bold'); if (direction === 'left') { - const margin = this.margin(); + const margin = this.margin.get(); axis.attr('transform', `translate(${margin.left},0)`); } } else { @@ -75,7 +75,7 @@ export const AxisChart = Base => class extends Base { } leftLabel (label) { - const leftBounds = this.marginBounds('left'); + const leftBounds = this.margin.bounds('left'); if (label) { this._axes.leftGroup.select('text') .attr('transform', 'rotate(-90)') @@ -88,7 +88,7 @@ export const AxisChart = Base => class extends Base { } bottomLabel (label) { - const bottomBounds = this.marginBounds('bottom'); + const bottomBounds = this.margin.bounds('bottom'); if (label) { this._axes.bottomGroup.select('text') .attr('x', bottomBounds.width / 2) diff --git a/packages/d3chart/src/Crosshairs.js b/packages/d3chart/src/Crosshairs.js index 5eb8e340..774ee8c2 100644 --- a/packages/d3chart/src/Crosshairs.js +++ b/packages/d3chart/src/Crosshairs.js @@ -1,6 +1,6 @@ export const Crosshairs = Base => class extends Base { initCrosshairs () { - const plotBounds = this.marginBounds('plot'); + const plotBounds = this.margin.bounds('plot'); this.target = this.plot.append('rect', ':first-child') .classed('crosshairs-target', true) .attr('width', plotBounds.width) diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index fc94c035..c2020c86 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -36,7 +36,7 @@ export const D3Chart = Base => class extends Margin(InitSize(Base)) { this.svg.attr('width', this.width) .attr('height', this.height); - const margin = this.margin(); + const margin = this.margin.get(); this.left.attr('transform', `translate(0,${margin.top})`); this.bottom.attr('transform', `translate(${margin.left},${this.height - margin.bottom})`); @@ -52,11 +52,10 @@ export class Swatches extends D3Chart(VisComponent) { this.width = options.width; this.height = options.height; - this.margin(options.margin) + this.margin.set(options.margin) .initD3Chart(); - const margin = this.margin(); - console.log(margin); + const margin = this.margin.get(); this.left.append('rect') .attr('x', 0) diff --git a/packages/d3chart/src/Margin.js b/packages/d3chart/src/Margin.js index 1e5f5f30..abc4107f 100644 --- a/packages/d3chart/src/Margin.js +++ b/packages/d3chart/src/Margin.js @@ -1,8 +1,8 @@ -export const Margin = Base => class extends Base { - constructor () { - super(...arguments); +class MarginImpl { + constructor (that) { + this.that = that; - this._margin = { + this.margin = { top: null, right: null, bottom: null, @@ -10,28 +10,30 @@ export const Margin = Base => class extends Base { }; } - margin (m) { - if (m === undefined) { - return {...this._margin}; - } + get () { + return {...this.margin}; + } + set (m) { let mm = {...m}; for (let key in mm) { - if (!(key in this._margin)) { + if (!(key in this.margin)) { delete mm[key]; } } - this._margin = { - ...this._margin, + this.margin = { + ...this.margin, ...mm }; - return this; + return this.that; } - marginBounds (region) { - const margin = this.margin(); + bounds (region) { + const margin = this.get(); + const width = this.that.width; + const height = this.that.height; let bounds; switch (region) { @@ -40,16 +42,16 @@ export const Margin = Base => class extends Base { x: 0, y: margin.top, width: margin.left, - height: this.height - margin.top - margin.bottom + height: height - margin.top - margin.bottom }; break; case 'right': bounds = { - x: this.width - margin.right, + x: width - margin.right, y: margin.top, width: margin.right, - height: this.height - margin.top - margin.bottom + height: height - margin.top - margin.bottom }; break; @@ -57,7 +59,7 @@ export const Margin = Base => class extends Base { bounds = { x: margin.left, y: 0, - width: this.width - margin.left - margin.right, + width: width - margin.left - margin.right, height: margin.top }; break; @@ -65,8 +67,8 @@ export const Margin = Base => class extends Base { case 'bottom': bounds = { x: margin.left, - y: this.height - margin.bottom, - width: this.width - margin.left - margin.right, + y: height - margin.bottom, + width: width - margin.left - margin.right, height: margin.bottom }; break; @@ -75,8 +77,8 @@ export const Margin = Base => class extends Base { bounds = { x: margin.left, y: margin.top, - width: this.width - margin.left - margin.right, - height: this.height - margin.top - margin.bottom + width: width - margin.left - margin.right, + height: height - margin.top - margin.bottom }; break; @@ -86,4 +88,13 @@ export const Margin = Base => class extends Base { return bounds; } +} + +export const Margin = Base => class extends Base { + constructor () { + super(...arguments); + if (!this.margin) { + this.margin = new MarginImpl(this); + } + } }; From ade98c6f6ef04d14a66bcd5201bcada49933b813 Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Fri, 13 Jul 2018 17:41:53 -0400 Subject: [PATCH 15/18] refactor: lift Crosshairs mixin functionality into an Impl class --- packages/d3chart/src/Crosshairs.js | 48 ++++++++++++++++++++++-------- packages/d3chart/src/Margin.js | 8 ++--- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/packages/d3chart/src/Crosshairs.js b/packages/d3chart/src/Crosshairs.js index 774ee8c2..41c9f41b 100644 --- a/packages/d3chart/src/Crosshairs.js +++ b/packages/d3chart/src/Crosshairs.js @@ -1,32 +1,41 @@ -export const Crosshairs = Base => class extends Base { - initCrosshairs () { - const plotBounds = this.margin.bounds('plot'); - this.target = this.plot.append('rect', ':first-child') +import { Margin } from './Margin'; + +class CrosshairsImpl { + constructor (that) { + this.that = that; + + this.target = null; + this.crosshairX = null; + this.crosshairY = null; + } + + init () { + const that = this.that; + + const plotBounds = that.margin.bounds('plot'); + this.target = that.plot.append('rect', ':first-child') .classed('crosshairs-target', true) .attr('width', plotBounds.width) .attr('height', plotBounds.height) .style('opacity', 0.0); - const g = this.plot.append('g') + const g = that.plot.append('g') .classed('crosshairs', true) .style('pointer-events', 'none'); - const horz = this.bottomScale() || this.topScale(); - const vert = this.leftScale() || this.rightScale(); - this.crosshairX = g.append('line') .classed('crosshair-x', true) .style('opacity', 0) .style('stroke', 'lightgray') - .attr('x1', horz.range()[0]) - .attr('x2', horz.range()[1]); + .attr('x1', 0) + .attr('x2', plotBounds.width); this.crosshairY = g.append('line') .classed('crosshair-y', true) .style('opacity', 0) .style('stroke', 'lightgray') - .attr('y1', vert.range()[0]) - .attr('y2', vert.range()[1]); + .attr('y1', 0) + .attr('y2', plotBounds.height); this.target.on('mouseover.crosshairs', () => { this.show(); @@ -55,15 +64,30 @@ export const Crosshairs = Base => class extends Base { this.crosshairY.attr('x1', x) .attr('x2', x); + + return this.that; } show () { this.crosshairX.style('opacity', 1); this.crosshairY.style('opacity', 1); + + return this.that; } hide () { this.crosshairX.style('opacity', 0); this.crosshairY.style('opacity', 0); + + return this.that; + } +} + +export const Crosshairs = Base => class extends Margin(Base) { + constructor () { + super(...arguments); + if (!this.crosshairs) { + this.crosshairs = new CrosshairsImpl(this); + } } }; diff --git a/packages/d3chart/src/Margin.js b/packages/d3chart/src/Margin.js index abc4107f..db41a981 100644 --- a/packages/d3chart/src/Margin.js +++ b/packages/d3chart/src/Margin.js @@ -3,10 +3,10 @@ class MarginImpl { this.that = that; this.margin = { - top: null, - right: null, - bottom: null, - left: null + top: 0, + right: 0, + bottom: 0, + left: 0 }; } From 70291fc1c3ae3f858109890828f0def545952a3e Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Fri, 13 Jul 2018 18:15:41 -0400 Subject: [PATCH 16/18] refactor: lift Tooltip mixin into impl class --- packages/d3chart/src/Crosshairs.js | 10 +++--- packages/d3chart/src/Margin.js | 2 +- packages/d3chart/src/Tooltip.js | 53 +++++++++++++++++++++--------- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/packages/d3chart/src/Crosshairs.js b/packages/d3chart/src/Crosshairs.js index 41c9f41b..73fba0ee 100644 --- a/packages/d3chart/src/Crosshairs.js +++ b/packages/d3chart/src/Crosshairs.js @@ -41,7 +41,7 @@ class CrosshairsImpl { this.show(); }).on('mousemove.crosshairs', () => { const mouse = this.mouseCoords(); - this.update(mouse.x, mouse.y); + this.setPosition(mouse.x, mouse.y); }).on('mouseout.crosshairs', () => { this.hide(); }); @@ -58,28 +58,28 @@ class CrosshairsImpl { } } - update (x, y) { + setPosition (x, y) { this.crosshairX.attr('y1', y) .attr('y2', y); this.crosshairY.attr('x1', x) .attr('x2', x); - return this.that; + return this; } show () { this.crosshairX.style('opacity', 1); this.crosshairY.style('opacity', 1); - return this.that; + return this; } hide () { this.crosshairX.style('opacity', 0); this.crosshairY.style('opacity', 0); - return this.that; + return this; } } diff --git a/packages/d3chart/src/Margin.js b/packages/d3chart/src/Margin.js index db41a981..df86093e 100644 --- a/packages/d3chart/src/Margin.js +++ b/packages/d3chart/src/Margin.js @@ -27,7 +27,7 @@ class MarginImpl { ...mm }; - return this.that; + return this; } bounds (region) { diff --git a/packages/d3chart/src/Tooltip.js b/packages/d3chart/src/Tooltip.js index 01fc1105..3194b776 100644 --- a/packages/d3chart/src/Tooltip.js +++ b/packages/d3chart/src/Tooltip.js @@ -1,15 +1,14 @@ import { select } from 'd3-selection'; import 'd3-transition'; -export const Tooltip = Base => class extends Base { - constructor () { - super(...arguments); - - this._tooltip = {}; +class TooltipImpl { + constructor (that) { + this.that = that; + this.tooltip = null; } - initTooltip (options = {}) { - this._tooltip.tooltip = select(this.el) + init (options = {}) { + this.tooltip = select(this.that.el) .append('div') .style('opacity', 0) .style('position', 'absolute') @@ -22,19 +21,43 @@ export const Tooltip = Base => class extends Base { .style('border', '0px') .style('border-radius', '8px') .style('pointer-events', 'none'); + + return this.that; + } + + show () { + this.tooltip.style('opacity', 1); + return this; } - tooltip () { - return this._tooltip.tooltip; + hide () { + this.tooltip.style('opacity', 0); + return this; } - showTT () { - this.tooltip() - .style('opacity', 1); + setX (x) { + this.tooltip.style('left', `${x}px`); + return this; } - hideTT () { - this.tooltip() - .style('opacity', 0); + setY (y) { + this.tooltip.style('top', `${y}px`); + return this; + } + + setPosition (x, y) { + this.setX(x) + .setY(y); + + return this; + } +} + +export const Tooltip = Base => class extends Base { + constructor () { + super(...arguments); + if (!this.tooltip) { + this.tooltip = new TooltipImpl(this); + } } }; From 140b72b514a0018414f102b9b554ecf49840843e Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Fri, 13 Jul 2018 19:43:02 -0400 Subject: [PATCH 17/18] refactor: rename and lift Axes mixin into an impl class --- packages/d3chart/src/Axes.js | 117 ++++++++++++++++++++++++++++++ packages/d3chart/src/AxisChart.js | 109 ---------------------------- packages/d3chart/src/index.js | 2 +- 3 files changed, 118 insertions(+), 110 deletions(-) create mode 100644 packages/d3chart/src/Axes.js delete mode 100644 packages/d3chart/src/AxisChart.js diff --git a/packages/d3chart/src/Axes.js b/packages/d3chart/src/Axes.js new file mode 100644 index 00000000..2b3100a2 --- /dev/null +++ b/packages/d3chart/src/Axes.js @@ -0,0 +1,117 @@ +import { axisLeft, axisBottom } from 'd3-axis'; + +import { Margin } from './Margin'; + +const scaleFuncMap = { + left: axisLeft, + bottom: axisBottom +}; + +function getLabelOffset (dir, bounds) { + switch (dir) { + case 'left': + return { + rotate: 'rotate(-90)', + x: -bounds.height / 2, + y: -40 + }; + + case 'bottom': + return { + x: bounds.width / 2, + y: 40 + }; + + default: + throw new Error(`illegal direction parameter: "${dir}"`); + } +} + +class AxesImpl { + constructor (that) { + this.that = that; + + this.scale = { + left: null, + bottom: null + }; + + this.axis = { + left: null, + bottom: null + }; + + this.group = { + left: null, + bottom: null + }; + } + + setScale (direction, scale) { + const scaleFunc = scaleFuncMap[direction]; + + const bounds = this.that.margin.bounds('plot'); + + if (direction === 'left' || direction === 'right') { + scale.range([bounds.height, 0]); + } else { + scale.range([0, bounds.width]); + } + + this.scale[direction] = scale; + + let axis = this.group[direction]; + if (!axis) { + // TODO: make the dependency on D3Chart explicit. + axis = this.group[direction] = this.that[direction].append('g'); + axis.append('text') + .attr('fill', '#000') + .style('font-size', '11pt') + .style('font-weight', 'bold'); + + if (direction === 'left') { + const margin = this.that.margin.get(); + axis.attr('transform', `translate(${margin.left},0)`); + } + } else { + axis.selectAll('*').remove(); + } + + axis.call(this.axis[direction] = scaleFunc(scale)); + + return this; + } + + getAxis (dir) { + return this.axis[dir]; + } + + getScale (dir) { + return this.scale[dir]; + } + + setLabel (dir, label) { + const bounds = this.that.margin.bounds(dir); + const text = this.group[dir].select('text'); + const offset = getLabelOffset(dir, bounds); + + text.attr('transform', offset.rotate ? offset.rotate : null) + .attr('x', offset.x) + .attr('y', offset.y) + .text(label); + + return this; + } + + renderAxis (dir) { + this.group[dir].call(this.axis[dir]); + return this; + } +} + +export const Axes = Base => class extends Margin(Base) { + constructor () { + super(...arguments); + this.axes = new AxesImpl(this); + } +}; diff --git a/packages/d3chart/src/AxisChart.js b/packages/d3chart/src/AxisChart.js deleted file mode 100644 index d9a305e8..00000000 --- a/packages/d3chart/src/AxisChart.js +++ /dev/null @@ -1,109 +0,0 @@ -import { axisLeft, axisBottom } from 'd3-axis'; - -export const AxisChart = Base => class extends Base { - constructor () { - super(...arguments); - - this._axes = { - leftScale: null, - bottomScale: null, - leftAxis: null, - bottomAxis: null, - leftGroup: null, - bottomGroup: null - }; - } - - _setAxis (scale, direction, scaleFunc) { - const axisProp = `${direction}Axis`; - const scaleProp = `${direction}Scale`; - const groupProp = `${direction}Group`; - - const bounds = this.margin.bounds('plot'); - - if (direction === 'left' || direction === 'right') { - scale.range([bounds.height, 0]); - } else { - scale.range([0, bounds.width]); - } - - this._axes[scaleProp] = scale; - - let axis = this._axes[groupProp]; - if (!axis) { - axis = this._axes[groupProp] = this[direction].append('g'); - axis.append('text') - .attr('fill', '#000') - .style('font-size', '11pt') - .style('font-weight', 'bold'); - - if (direction === 'left') { - const margin = this.margin.get(); - axis.attr('transform', `translate(${margin.left},0)`); - } - } else { - axis.selectAll('*').remove(); - } - - axis.call(this._axes[axisProp] = scaleFunc(scale)); - } - - leftAxis () { - return this._axes.leftAxis; - } - - bottomAxis () { - return this._axes.bottomAxis; - } - - leftScale (scale) { - if (scale) { - this._setAxis(scale, 'left', axisLeft); - return this; - } else { - return this._axes.leftScale; - } - } - - bottomScale (scale) { - if (scale) { - this._setAxis(scale, 'bottom', axisBottom); - return this; - } else { - return this._axes.bottomScale; - } - } - - leftLabel (label) { - const leftBounds = this.margin.bounds('left'); - if (label) { - this._axes.leftGroup.select('text') - .attr('transform', 'rotate(-90)') - .attr('x', -leftBounds.height / 2) - .attr('y', '-40') - .text(label); - } else { - return this._axes.leftLabel; - } - } - - bottomLabel (label) { - const bottomBounds = this.margin.bounds('bottom'); - if (label) { - this._axes.bottomGroup.select('text') - .attr('x', bottomBounds.width / 2) - .attr('y', '40') - .text(label); - } else { - return this._axes.bottomLabel; - } - } - - renderLeftAxis () { - this._axes.leftGroup.call(this.leftAxis()); - } - - renderBottomAxis () { - this._axes.bottomGroup.call(this.bottomAxis()); - } -}; diff --git a/packages/d3chart/src/index.js b/packages/d3chart/src/index.js index 5e77bd8d..a97d8a2a 100644 --- a/packages/d3chart/src/index.js +++ b/packages/d3chart/src/index.js @@ -1,4 +1,4 @@ -export * from './AxisChart'; +export * from './Axes'; export * from './Crosshairs'; export * from './D3Chart'; export * from './Margin'; From f7a2567e7620711fecb51796cc76177b8c370383 Mon Sep 17 00:00:00 2001 From: Roni Choudhury Date: Fri, 13 Jul 2018 20:10:07 -0400 Subject: [PATCH 18/18] refactor: lift D3Chart mixin into impl class --- packages/d3chart/src/Axes.js | 7 +++-- packages/d3chart/src/Crosshairs.js | 8 +++--- packages/d3chart/src/D3Chart.js | 43 ++++++++++++++++++------------ 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/packages/d3chart/src/Axes.js b/packages/d3chart/src/Axes.js index 2b3100a2..a1c57c41 100644 --- a/packages/d3chart/src/Axes.js +++ b/packages/d3chart/src/Axes.js @@ -1,6 +1,6 @@ import { axisLeft, axisBottom } from 'd3-axis'; -import { Margin } from './Margin'; +import { D3Chart } from './D3Chart'; const scaleFuncMap = { left: axisLeft, @@ -62,8 +62,7 @@ class AxesImpl { let axis = this.group[direction]; if (!axis) { - // TODO: make the dependency on D3Chart explicit. - axis = this.group[direction] = this.that[direction].append('g'); + axis = this.group[direction] = this.that.d3chart[direction].append('g'); axis.append('text') .attr('fill', '#000') .style('font-size', '11pt') @@ -109,7 +108,7 @@ class AxesImpl { } } -export const Axes = Base => class extends Margin(Base) { +export const Axes = Base => class extends D3Chart(Base) { constructor () { super(...arguments); this.axes = new AxesImpl(this); diff --git a/packages/d3chart/src/Crosshairs.js b/packages/d3chart/src/Crosshairs.js index 73fba0ee..894009da 100644 --- a/packages/d3chart/src/Crosshairs.js +++ b/packages/d3chart/src/Crosshairs.js @@ -1,4 +1,4 @@ -import { Margin } from './Margin'; +import { D3Chart } from './D3Chart'; class CrosshairsImpl { constructor (that) { @@ -13,13 +13,13 @@ class CrosshairsImpl { const that = this.that; const plotBounds = that.margin.bounds('plot'); - this.target = that.plot.append('rect', ':first-child') + this.target = that.d3chart.plot.append('rect', ':first-child') .classed('crosshairs-target', true) .attr('width', plotBounds.width) .attr('height', plotBounds.height) .style('opacity', 0.0); - const g = that.plot.append('g') + const g = that.d3chart.plot.append('g') .classed('crosshairs', true) .style('pointer-events', 'none'); @@ -83,7 +83,7 @@ class CrosshairsImpl { } } -export const Crosshairs = Base => class extends Margin(Base) { +export const Crosshairs = Base => class extends D3Chart(Base) { constructor () { super(...arguments); if (!this.crosshairs) { diff --git a/packages/d3chart/src/D3Chart.js b/packages/d3chart/src/D3Chart.js index c2020c86..f799dd9b 100644 --- a/packages/d3chart/src/D3Chart.js +++ b/packages/d3chart/src/D3Chart.js @@ -6,11 +6,11 @@ import 'd3-transition'; import { Margin } from './Margin'; -export const D3Chart = Base => class extends Margin(InitSize(Base)) { - constructor () { - super(...arguments); +class D3ChartImpl { + constructor (that) { + this.that = that; - this.svg = select(this.el) + this.svg = select(this.that.el) .append('svg') .attr('xmlns', 'http://www.w3.org/2000/svg'); @@ -32,18 +32,27 @@ export const D3Chart = Base => class extends Margin(InitSize(Base)) { .classed('plot', true); } - initD3Chart () { - this.svg.attr('width', this.width) - .attr('height', this.height); + init () { + this.svg.attr('width', this.that.width) + .attr('height', this.that.height); - const margin = this.margin.get(); + const margin = this.that.margin.get(); this.left.attr('transform', `translate(0,${margin.top})`); - this.bottom.attr('transform', `translate(${margin.left},${this.height - margin.bottom})`); - this.right.attr('transform', `translate(${this.width - margin.right},${margin.top})`); + this.bottom.attr('transform', `translate(${margin.left},${this.that.height - margin.bottom})`); + this.right.attr('transform', `translate(${this.that.width - margin.right},${margin.top})`); this.top.attr('transform', `translate(${margin.left},0)`); this.plot.attr('transform', `translate(${margin.left},${margin.top})`); } +} + +export const D3Chart = Base => class extends Margin(InitSize(Base)) { + constructor () { + super(...arguments); + if (!this.d3chart) { + this.d3chart = new D3ChartImpl(this); + } + } }; export class Swatches extends D3Chart(VisComponent) { @@ -52,12 +61,12 @@ export class Swatches extends D3Chart(VisComponent) { this.width = options.width; this.height = options.height; - this.margin.set(options.margin) - .initD3Chart(); + this.margin.set(options.margin); + this.d3chart.init(); const margin = this.margin.get(); - this.left.append('rect') + this.d3chart.left.append('rect') .attr('x', 0) .attr('y', 0) .attr('width', margin.left) @@ -65,7 +74,7 @@ export class Swatches extends D3Chart(VisComponent) { .style('stroke', 'black') .style('fill', 'red'); - this.bottom.append('rect') + this.d3chart.bottom.append('rect') .attr('x', 0) .attr('y', 0) .attr('width', this.width - margin.left - margin.right) @@ -73,7 +82,7 @@ export class Swatches extends D3Chart(VisComponent) { .style('stroke', 'black') .style('fill', 'green'); - this.right.append('rect') + this.d3chart.right.append('rect') .attr('x', 0) .attr('y', 0) .attr('width', margin.right) @@ -81,7 +90,7 @@ export class Swatches extends D3Chart(VisComponent) { .style('stroke', 'black') .style('fill', 'cyan'); - this.top.append('rect') + this.d3chart.top.append('rect') .attr('x', 0) .attr('y', 0) .attr('width', this.width - margin.left - margin.right) @@ -89,7 +98,7 @@ export class Swatches extends D3Chart(VisComponent) { .style('stroke', 'black') .style('fill', 'yellow'); - this.plot.append('rect') + this.d3chart.plot.append('rect') .attr('x', 0) .attr('y', 0) .attr('width', this.width - margin.left - margin.right)