Compare commits

..

38 Commits

Author SHA1 Message Date
Coin de Gamma f788656bc9
Update README.md 4 years ago
Coin de Gamma b04d0fa0c4
Create FUNDING.yml 4 years ago
Coin de Gamma 1baa1a366d
Update README.md 5 years ago
Alexey Velikiy 8180e79ede
twitter/instagram links 7 years ago
Alexey Velikiy 81a63830a2 merge styles demo + sass example 8 years ago
Alexey Velikiy a70f3b1621 readme++ 8 years ago
Alexey Velikiy 64252ce5c7 tooltip-map-v1 8 years ago
Alexey Velikiy 9bd54f5b72 begin map 8 years ago
Alexey Velikiy 5c2d212cb0 lek to licence from readme 8 years ago
Alexey Velikiy dba4c8b082 add licence (mit) 8 years ago
Alexey Velikiy 04dae9d895 Update README.md 8 years ago
Alexey Velikiy 7c7232c70b nested routes 8 years ago
Alexey Velikiy d3e5bb9519 conty trees 8 years ago
Alexey Velikiy 2c45320f98 rm Visokoi Island :( 8 years ago
Alexey Velikiy 929389597e layout contry continu 8 years ago
Alexey Velikiy 869e5490d5 dmenu + better layout design 8 years ago
Alexey Velikiy 8cdd526cc0 bubbles continue 8 years ago
Alexey Velikiy 621aed87ce basic bubbles 8 years ago
Alexey Velikiy 8e91f3a2cb begin ph2 demo dev 8 years ago
Alexey Velikiy 4afe8beebd refactor hrefs 8 years ago
Alexey Velikiy 2fbfb13871 pie chart 8 years ago
Alexey Velikiy f88909e5f4 about the project 8 years ago
Alexey Velikiy 7f5a8609a0 styles examples 8 years ago
Alexey Velikiy 1a9d7e8d1d publish ++ 8 years ago
Alexey Velikiy ec30abbb58 d3ComponentsPath in config 8 years ago
Alexey Velikiy ea3b43b137 ccoomir 8 years ago
Alexey Velikiy 4c653dd026 topNav -> top=nav 8 years ago
Alexey Velikiy fec9b58130 mac sed 8 years ago
Alexey Velikiy cb6e0e90e4 publish++ 8 years ago
Alexey Velikiy d89ef424fe mv -f 8 years ago
Alexey Velikiy c80f015cc8 package publish 8 years ago
Alexey Velikiy 38ff651245 size controller 8 years ago
Alexey Velikiy f73be01676 rename app.vue 8 years ago
Alexey Velikiy 1620a5f5a2 update readme goals 8 years ago
Alexey Velikiy bea523fc4b circle mount 8 years ago
Alexey Velikiy 6f58abfc75 navigation & source link 8 years ago
Alexey Velikiy 0ad5661ee2 update docs/navigation begin 8 years ago
Alexey Velikiy 0076e160c3 skeleton 8 years ago
  1. 13
      .babelrc
  2. 9
      .editorconfig
  3. 3
      .github/FUNDING.yml
  4. 2
      .gitignore
  5. 20
      LICENSE.md
  6. 35
      README.md
  7. 40
      build/build.js
  8. 45
      build/check-versions.js
  9. 9
      build/dev-client.js
  10. 81
      build/dev-server.js
  11. 64
      build/utils.js
  12. 17
      build/vue-loader.conf.js
  13. 68
      build/webpack.base.conf.js
  14. 35
      build/webpack.dev.conf.js
  15. 102
      build/webpack.prod.conf.js
  16. 6
      config/dev.env.js
  17. 38
      config/index.js
  18. 3
      config/prod.env.js
  19. 12
      index.html
  20. 58
      package.json
  21. 32
      src/app.vue
  22. 97
      src/components/demos-navigation.vue
  23. 51
      src/components/index.vue
  24. 4
      src/config.json
  25. 9
      src/d3-components/README.md
  26. 118
      src/d3-components/bars-styles.vue
  27. 98
      src/d3-components/basic-map-tooltip/index.vue
  28. 64
      src/d3-components/basic-map-tooltip/map.vue
  29. 67
      src/d3-components/basic-map-tooltip/tooltip.vue
  30. 43
      src/d3-components/bubbles-nested-routes/bubbles.vue
  31. 102
      src/d3-components/bubbles-nested-routes/index.vue
  32. 48
      src/d3-components/bubbles-nested-routes/menu.vue
  33. 98
      src/d3-components/bubbles-nested-routes/ph2/index.js
  34. 97
      src/d3-components/bubbles-nested-routes/ph2/layout-contry.js
  35. 56
      src/d3-components/bubbles-nested-routes/ph2/layout-day.js
  36. 15
      src/d3-components/bubbles-nested-routes/ph2/layout-total.js
  37. 31
      src/d3-components/circle-mount.vue
  38. 61
      src/d3-components/pie-chart-local-component/index.vue
  39. 92
      src/d3-components/pie-chart-local-component/pie.vue
  40. 49
      src/d3-components/size-controller.vue
  41. 16
      src/main.js
  42. 45
      src/router/demos.js
  43. 17
      src/router/index.js
  44. 0
      static/.gitkeep
  45. 278
      static/css/app.ea478ad0f98c40581c472cf6cea74c24.css
  46. 1
      static/css/app.ea478ad0f98c40581c472cf6cea74c24.css.map
  47. 2
      static/js/app.bb478689be3640ebf141.js
  48. 1
      static/js/app.bb478689be3640ebf141.js.map
  49. 2
      static/js/manifest.0d9440aa0954c24a67c3.js
  50. 1
      static/js/manifest.0d9440aa0954c24a67c3.js.map
  51. 24
      static/js/vendor.03b56a03b17565331709.js
  52. 1
      static/js/vendor.03b56a03b17565331709.js.map

13
.babelrc

@ -0,0 +1,13 @@
{
"presets": [
["es2015", { "modules": false }],
"stage-2"
],
"plugins": ["transform-runtime"],
"comments": false,
"env": {
"test": {
"plugins": [ "istanbul" ]
}
}
}

9
.editorconfig

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

3
.github/FUNDING.yml

@ -0,0 +1,3 @@
# These are supported funding model platforms
patreon: corpglory

2
.gitignore vendored

@ -1,4 +1,4 @@
.DS_Store
node_modules/
dist/
npm-debug.log
dist

20
LICENSE.md

@ -0,0 +1,20 @@
Copyright (c) 2015-2017 CorpGlory Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

35
README.md

@ -0,0 +1,35 @@
# d3vue
A list of Vue.js / D3.js [examples](https://github.com/corpglory/d3vue/tree/master/src/d3-components)
https://corpglory.github.io/d3vue/
## Goals
* Collect best practices
* Make edu materials for Vue.js/D3.js learners
* Show advantages of using D3.js on top of Vue.js
## Build Setup
``` bash
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
```
For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
## License
See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).
## About CorpGlory Inc.
The project developed by [CorpGlory Inc.](https://corpglory.com/), a company which provides high quality software development, data visualization, Grafana and monitoring consulting.
See also https://chartwerk.io/ -- make your d3/vue visualisations reusable and run them in Grafana.
Follow as on [__Twitter__](http://twitter.com/corpglory) and [__Instagram__](https://www.instagram.com/corpglory/)

40
build/build.js

@ -0,0 +1,40 @@
// https://github.com/shelljs/shelljs
require('./check-versions')()
process.env.NODE_ENV = 'production'
var ora = require('ora')
var path = require('path')
var chalk = require('chalk')
var shell = require('shelljs')
var webpack = require('webpack')
var config = require('../config')
var webpackConfig = require('./webpack.prod.conf')
var spinner = ora('building for production...')
spinner.start()
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
shell.rm('-rf', assetsPath)
shell.mkdir('-p', assetsPath)
shell.config.silent = true
shell.cp('-R', 'static/*', assetsPath)
shell.config.silent = false
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})

45
build/check-versions.js

@ -0,0 +1,45 @@
var chalk = require('chalk')
var semver = require('semver')
var packageConfig = require('../package.json')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
},
{
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
}
]
module.exports = function () {
var warnings = []
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

9
build/dev-client.js

@ -0,0 +1,9 @@
/* eslint-disable */
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})

81
build/dev-server.js

@ -0,0 +1,81 @@
require('./check-versions')()
var config = require('../config')
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
var opn = require('opn')
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port
// automatically open browser, if not set will be false
var autoOpenBrowser = !!config.dev.autoOpenBrowser
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable
var app = express()
var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => {}
})
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(options.filter || context, options))
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
var uri = 'http://localhost:' + port
devMiddleware.waitUntilValid(function () {
console.log('> Listening at ' + uri + '\n')
})
module.exports = app.listen(port, function (err) {
if (err) {
console.log(err)
return
}
// when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
})

64
build/utils.js

@ -0,0 +1,64 @@
var path = require('path')
var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
// generate loader string to be used with extract text plugin
function generateLoaders (loaders) {
var sourceLoader = loaders.map(function (loader) {
var extraParamChar
if (/\?/.test(loader)) {
loader = loader.replace(/\?/, '-loader?')
extraParamChar = '&'
} else {
loader = loader + '-loader'
extraParamChar = '?'
}
return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
}).join('!')
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: sourceLoader,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader', sourceLoader].join('!')
}
}
// http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
return {
css: generateLoaders(['css']),
postcss: generateLoaders(['css']),
less: generateLoaders(['css', 'less']),
sass: generateLoaders(['css', 'sass?indentedSyntax']),
scss: generateLoaders(['css', 'sass']),
stylus: generateLoaders(['css', 'stylus']),
styl: generateLoaders(['css', 'stylus'])
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = []
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
loader: loader
})
}
return output
}

17
build/vue-loader.conf.js

@ -0,0 +1,17 @@
var utils = require('./utils')
var config = require('../config')
var isProduction = process.env.NODE_ENV === 'production'
module.exports = {
loaders: utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap,
extract: isProduction
}),
postcss: [
require('autoprefixer')({
browsers: ['last 2 versions']
})
]
}

68
build/webpack.base.conf.js

@ -0,0 +1,68 @@
var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
modules: [
resolve('src'),
resolve('node_modules')
],
alias: {
'vue$': 'vue/dist/vue.common.js',
'src': resolve('src'),
'assets': resolve('src/assets'),
'components': resolve('src/components')
}
},
module: {
rules: [
{
test: /\.scss$/,
loaders: ["style", "css", "sass"]
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}

35
build/webpack.dev.conf.js

@ -0,0 +1,35 @@
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
module.exports = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// cheap-module-eval-source-map is faster for development
devtool: '#cheap-module-eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
new FriendlyErrorsPlugin()
]
})

102
build/webpack.prod.conf.js

@ -0,0 +1,102 @@
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig

6
config/dev.env.js

@ -0,0 +1,6 @@
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

38
config/index.js

@ -0,0 +1,38 @@
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: require('./dev.env'),
port: 8080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
}
}

3
config/prod.env.js

@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: '"production"'
}

12
index.html

@ -1 +1,11 @@
<!DOCTYPE html><html><head><meta charset=utf-8><title>d3vue</title><link href=static/css/app.ea478ad0f98c40581c472cf6cea74c24.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/manifest.0d9440aa0954c24a67c3.js></script><script type=text/javascript src=static/js/vendor.03b56a03b17565331709.js></script><script type=text/javascript src=static/js/app.bb478689be3640ebf141.js></script></body></html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>d3vue</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

58
package.json

@ -0,0 +1,58 @@
{
"name": "d3vue",
"version": "1.0.0",
"description": "An educational with list of Vue.JS vs D3.js examples",
"author": "Alexey Velikiy [CorpGlory] <twitter.com/corpglory>",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"build": "node build/build.js",
"publish": "npm run build && git checkout gh-pages && rm -rf static; rm index.html ; mv dist/static . && mv dist/index.html . && sed -i '' 's,/static,static,g' index.html && git add . && git commit -m release && git push && git checkout master"
},
"dependencies": {
"d3": "^4.6.0",
"topojson": "^2.2.0",
"vue": "^2.1.10",
"vue-resource": "^1.2.1",
"vue-router": "^2.2.0"
},
"devDependencies": {
"autoprefixer": "^6.7.2",
"babel-core": "^6.22.1",
"babel-loader": "^6.2.10",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.3.0",
"css-loader": "^0.26.1",
"eventsource-polyfill": "^0.9.6",
"express": "^4.14.1",
"extract-text-webpack-plugin": "^2.0.0-rc.2",
"file-loader": "^0.10.0",
"friendly-errors-webpack-plugin": "^1.1.3",
"function-bind": "^1.1.0",
"html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.3",
"node-sass": "^4.5.0",
"opn": "^4.0.2",
"ora": "^1.1.0",
"sass-loader": "^6.0.3",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"url-loader": "^0.5.7",
"vue-loader": "^10.3.0",
"vue-style-loader": "^2.0.0",
"vue-template-compiler": "^2.1.10",
"webpack": "^2.2.1",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.16.1",
"webpack-merge": "^2.6.1"
},
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
}
}

32
src/app.vue

@ -0,0 +1,32 @@
<template>
<div id="app">
<a href='#/'> <h1> D3.js vs Vue.js examples </h1> </a>
<h2>{{ $route.name }}</h2>
<top-nav />
<router-view></router-view>
</div>
</template>
<script>
import topNav from './components/demos-navigation'
export default {
name: 'app',
components: { topNav }
}
</script>
<style scoped>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
margin-top: 30px;
}
a {
color: black;
text-decoration: none;
}
</style>

97
src/components/demos-navigation.vue

@ -0,0 +1,97 @@
<template>
<div id="holder">
<div class="links">
<router-link v-if="prev" :to="prev.path" class="prev"> < prev </router-link>
<a :href="sourceHref" class="source"> source </a>
<router-link v-if="next" :to="next.path" class="next"> next > </router-link>
</div>
</div>
</template>
<script>
import { routes } from 'router/demos';
const config = require('config.json');
export default {
computed: {
prev: function() {
var index = this.findIndex();
if(index == -1) {
return undefined;
}
return index > 0 ? routes[index - 1] : undefined;
},
next: function() {
var index = this.findIndex();
if(index == -1) {
return undefined;
}
return index + 1 < routes.length ? routes[index + 1] : undefined;
},
sourceHref: function() {
var index = this.findIndex();
if(index == -1) {
return config.githubLink;
}
return routes[index].source;
}
},
methods: {
findIndex: function() {
var me = this.$route.name;
return routes.findIndex(r => r.name === me);
}
}
}
</script>
<style scoped>
#links {
display: block;
width: 300px;
height: 40px;
position: relative;
}
a {
display: block;
position: absolute;
width: 100px;
text-decoration: none;
font-weight: bold;
color: blue;
}
a:hover {
text-decoration: underline;
}
.prev {
left: 0px;
text-align: left;
}
.source {
left:100px;
text-align: center;
}
.next {
right: 0px;
text-align: right;
}
#holder {
width: 300px;
margin: auto;
position: relative;
height: 30px;
/*border-top: 1px dashed gray;*/
border-bottom: 1px dashed gray;
padding-top: 10px;
margin-top: 10px;
margin-bottom: 10px;
}
</style>

51
src/components/index.vue

@ -0,0 +1,51 @@
<template>
<div class="hello">
<ul>
<li v-for="item in items">
<router-link :to="item.path">{{ item.name }}</router-link>
</li>
</ul>
</div>
</template>
<script>
import { routes } from 'router/demos'
export default {
data () {
return {
items: routes
}
}
}
</script>
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
/*list-style-type: none;*/
padding: 0;
margin: auto;
width: 300px;
}
li {
/*display: inline-block;*/
margin: 0 0px;
list-style: decimal
}
a {
color: blue;
display: block;
text-decoration: none;
text-align: left;
}
a:hover {
text-decoration: underline;;
}
</style>

4
src/config.json

@ -0,0 +1,4 @@
{
"githubLink": "https://github.com/corpglory/d3vue/",
"d3ComponentsPath": "tree/master/src/d3-components"
}

9
src/d3-components/README.md

@ -0,0 +1,9 @@
# d3vue
See live demos: [corpglory.github.io/d3vue](https://corpglory.github.io/d3vue)
## Goals
* Help to learn Vue.js by people who already know D3.js
* Show advantages of using D3.js on top of Vue.js
* Collect best practices of problem solving with good software design

118
src/d3-components/bars-styles.vue

@ -0,0 +1,118 @@
<!--
Links:
Object attr: https://www.w3schools.com/xml/dom_attribute.asp
Scopes css: https://github.com/vuejs/vue-loader/blob/master/docs/en/features/scoped-css.md
.vue files: https://vuejs.org/v2/guide/single-file-components.html
D3.append: https://github.com/d3/d3-selection/blob/master/src/selection/index.js
Installation of SASS tutorial: https://www.youtube.com/watch?v=fIpLr04f8Ms
SASS: http://sass-lang.com/guide
-->
<template>
<svg width="500" height="300"></svg>
</template>
<script>
const d3 = require('d3');
export default {
mounted: function() {
var svg = d3.select(this.$el);
var width = +svg.attr('width');
var height = +svg.attr('height');
var data = [
{name: 'one', val: 100},
{name: 'two', val: 150},
{name: 'three', val: 200}
];
var x = d3.scaleBand()
.rangeRound([0, width]).padding(0.1)
.domain(data.map(d => d.name));
var y = d3.scaleLinear()
.rangeRound([height * 0.3 - 20, 0])
.domain([0, d3.max(data, d => d.val)])
function addRectsWithName(elem, name) {
elem
.append('text')
.text(name)
.attr('x', width / 2)
.attr('y', 5)
.attr('text-anchor', 'middle');
elem.selectAll('rect')
.data(data)
.enter()
.append('rect')
// We add attr here
.attr('x', d => x(d.name))
.attr('class', d => d.name)
.attr('y', d => y(d.val))
.attr('width', x.bandwidth())
.attr('height', d => y.range()[0] - y(d.val))
}
svg
.append('g')
.attr('id', 'bars-style')
.attr('transform', `translate(0, 20)`)
.call(addRectsWithName, 'Basic styles');
// vue loader will substitute data attribute for styles
var STYLE_MODULE_NAME = this.$el.attributes[0].name;
svg
.append('g')
.attr('transform', `translate(0, ${height * 0.3 + 20})`)
.call(addRectsWithName, 'Scoped styles')
.selectAll('rect')
.attr(STYLE_MODULE_NAME, '')
svg
.append('g')
.attr('id', 'bars-style-sass')
.attr('transform', `translate(0, ${height * 0.6 + 20})`)
.call(addRectsWithName, 'Sass styles');
}
}
</script>
<style>
#bars-style .one {
fill: #ffc300
}
#bars-style .two {
fill: #c70039
}
#bars-style .three {
fill: #571845
}
</style>
<style scoped>
.one {
fill: #154890
}
.two {
fill: #e1d4c0
}
.three {
fill: #ff6600
}
</style>
<style lang="sass">
#bars-style-sass
.one
fill: #AA5C39
.two
fill: #5B9632
.three
fill: #2A4F6E
</style>

98
src/d3-components/basic-map-tooltip/index.vue

@ -0,0 +1,98 @@
<!--
Links:
Events: https://vuejs.org/v2/guide/events.html
Computed properties: https://vuejs.org/v2/guide/computed.html
Data:
http://worldpopulationreview.com/states/
-->
<template>
<div id="holder">
<div class="mapHolder">
<us-map
v-on:stateSelected="onStateSelected"
v-on:stateDeselected="onStateDeselected"
/>
</div>
<tooltip
v-if="currentState"
:title="currentState.Name"
:description="currentStateDescription"
/>
</div>
</template>
<script>
var _ = require('lodash');
const map = require('d3-components/basic-map-tooltip/map');
const tooltip = require('d3-components/basic-map-tooltip/tooltip');
const STATES_DATA_PATH = 'static/data/states-data.csv';
// lets load with vue-resource, but parse with d3
// just because we can
import * as d3 from 'd3-dsv';
export default {
components: {
usMap: map,
tooltip: tooltip
},
created: function() {
var that = this;
this.$http.get(STATES_DATA_PATH)
.then(function(res) {
this.statesData = {};
d3.dsvFormat(';')
.parse(res.data, d => {
var population = d["2017 Population"].split(',').join('');
d.value = +population;
that.statesData[d.STATE_ABBR] = d;
delete d["2017 Population"];
delete d["STATE_ABBR"];
return d;
});
})
},
data: function() {
return {
statesData: undefined,
currentState: undefined
}
},
computed: {
currentStateDescription: function() {
return "Population: " + this.currentState.value;
}
},
methods: {
onStateSelected: function(stateCode) {
this.currentState = this.statesData[stateCode];
},
onStateDeselected: function(stateCode) {
this.currentState = undefined;
}
}
}
</script>
<style scoped>
#holder {
position: relative;
height: 300px;
width: 500px;
margin: auto;
}
.mapHolder {
position: absolute;
margin: auto;
}
</style>

64
src/d3-components/basic-map-tooltip/map.vue

@ -0,0 +1,64 @@
<!--
It would be great if you color the map by population using data from index.vue
Based on:
http://bl.ocks.org/rveciana/a2a1c21ca1c71cd3ec116cc911e5fce9
http://bl.ocks.org/mapsam/6083585
Links:
-->
<template>
<svg width="500" height="300"></svg>
</template>
<script>
const d3 = require('d3');
const topojson = require('topojson')
export default {
mounted: function() {
var v = this;
var svg = d3.select(this.$el);
var width = +svg.attr('width');
var height = +svg.attr('height');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
d3.json("static/data/us.json", function(error, us) {
var g = svg.append('g');
g
.selectAll('.state')
.data(topojson.feature(us, us.objects.usStates).features)
.enter()
.append("path")
.attr("class", "state")
.attr("d", path)
.on('mouseover', function(d) {
v.$emit('stateSelected', d.properties.STATE_ABBR)
})
.on('mouseout', function(d) {
v.$emit('stateDeselected', d.properties.STATE_ABBR)
})
g.attr('transform', 'scale(0.57)')
});
}
// TODO: fire events
}
</script>
<style>
.state {
fill: #ccc;
stroke: #fff;
}
.state:hover {
fill: steelblue;
}
</style>

67
src/d3-components/basic-map-tooltip/tooltip.vue

@ -0,0 +1,67 @@
<template>
<div id="tooltipPositioner">
<div id="tooltip">
<div id="tooltipContainer">
<div class="title">{{title}}</div>
<div class="description">{{description}}</div>
</div>
</div>
</div>
</template>
<script>
module.exports = {
name: 'tooltip',
props: ['title', 'description']
}
</script>
<style scoped>
#tooltipPositioner {
position: relative;
left: 50%;
top: 350px;
width: 240px;
}
#tooltipContainer {
position: absolute;
bottom: 0;
width: 230px;
left: -125px;
font-size: 12px;
line-height: 16px;
padding: 10px;
border-radius: 3px;
background: rgba(255,255,255,0.9);
color: #000;
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
-moz-box-shadow: 0 1px 5px rgba(0,0,0,0.4);
border:1px solid rgba(200,200,200,0.85);
text-align:center;
}
#tooltip {
text-align:center;
z-index: 1000;
position: absolute;
display: block;
}
#tooltip .description {
color:#666;
font-size: 11px;
text-align:center;
font-style:italic;
}
#tooltip .title {
text-align: left;
font-size: 13px;
text-align:center;
}
</style>

43
src/d3-components/bubbles-nested-routes/bubbles.vue

@ -0,0 +1,43 @@
<!--
Based on:
Links:
-->
<template>
<svg width="600" height="500"></svg>
</template>
<script>
import PH2 from './ph2/index';
const d3 = require('d3');
export default {
props: ['groupBy', 'data'],
watch: {
groupBy: function(groupBy) {
this.ph2.setLayout(groupBy);
},
data: function(data) {
this.ph2.setData(data);
}
},
mounted: function() {
var svg = d3.select(this.$el);
var width = +svg.attr('width');
var height = +svg.attr('height');
var that = this;
var bubblesG = svg.append('g').attr('transform', `translate(${width/2}, ${height/2})`)
// TODO: remove it later
d3.csv('static/data/earthquakes.csv', function(data) {
that.ph2 = new PH2(bubblesG, data, that.groupBy);
});
}
}
</script>

102
src/d3-components/bubbles-nested-routes/index.vue

@ -0,0 +1,102 @@
<!--
Explanation:
It is not really nester routes.
It is magic of aliases and $watch route.
See /src/router/demos.js (Bubbles: nested routes part)
and /src/router/index.js
Links:
Dynamic-matching: https://router.vuejs.org/en/essentials/dynamic-matching.html
Nested Routes: https://router.vuejs.org/en/essentials/nested-routes.html
-->
<template>
<div class="holder">
<!-- <h4>Earthquakes in september 2012</h4> -->
<groupMenu :links="links" :activeLink="activeLink" />
<bubbles :groupBy="activeLink.layout" />
</div>
</template>
<script>
const menu = require('d3-components/bubbles-nested-routes/menu');
const bubbles = require('d3-components/bubbles-nested-routes/bubbles');
const MY_URL_PREFIX = 'bubbles-nested-routes';
const _ = require('lodash');
const LINKS = [
{
name: 'None',
path: '',
layout: 'total'
},
{
name: 'By country',
path: '/country',
layout: 'country'
},
{
name: 'By day',
path: '/day',
layout: 'day'
}
].map(d => {
d.path = '#/' + MY_URL_PREFIX + d.path;
d.active = false;
return d;
});
export default {
components: {
groupMenu: menu,
bubbles: bubbles
},
data: function() {
return {
activeLink: this.findActiveLink()
}
},
computed: {
links: function() {
return LINKS;
}
},
methods: {
findActiveLink: function() {
_.each(LINKS, l => {
l.active = false;
})
var link = _.find(LINKS, l => l.path == '#' + this.$route.path);
if(!link) {
link = LINKS[0];
}
return link;
}
},
watch: {
'$route' (to, from) {
this.activeLink = this.findActiveLink();
}
}
}
</script>
<style scoped>
.holder {
width: 500px;
margin: auto;
}
.menu ul {
padding: 0;
margin: 0;
}
.menu li {
text-align: left;
}
</style>

48
src/d3-components/bubbles-nested-routes/menu.vue

@ -0,0 +1,48 @@
<template>
<div class="holder">
<ul class="menu">
<li>Group by: </li>
<li v-for="link in alinks">
<a :href="link.item.path" :class="{active: link.active}"> {{link.item.name}} </a>
</li>
</ul>
</div>
</template>
<script>
const _ = require('lodash');
export default {
props: ['links', 'activeLink'],
computed: {
alinks: function() {
var that = this;
return _.map(this.links, l => ({
item: l,
active: (l.path == that.activeLink.path)
}))
}
}
}
</script>
<style scoped>
.menu {
padding: 0;
margin: 0;
}
.menu li {
text-align: left;
display: inline-block;
text-align: left;
margin: 0px;
margin-right: 10px;
}
.menu li a {
text-decoration: none;
}
.menu li a.active {
text-decoration: underline;
}
</style>

98
src/d3-components/bubbles-nested-routes/ph2/index.js vendored

@ -0,0 +1,98 @@
// http://vallandingham.me/bubble_charts_with_d3v4.html
import layoutTotal from './layout-total';
import layoutCountry from './layout-contry';
import layoutDay from './layout-day';
const d3 = require('d3');
import _ from 'lodash';
// TODO: do it d3-way so I can use selection.call()
export default class PH2 {
constructor(elem, data, layoutName) {
this.svg = elem;
this.data = data;
this.setData(data);
this._init();
this.layout = undefined;
this.setLayout(layoutName);
}
setLayout(layoutName) {
if(layoutName === undefined) {
throw new Error('layoutName is undefined');
}
if(!this.layouts) {
throw new Error('Layouts are not defined');
}
if(!this.layouts[layoutName]) {
throw new Error('Can`t find layout ' + layoutName);
}
if(this.layout) {
this.layout.exit();
}
this.layout = this.layouts[layoutName];
if(!this.layout.inited) {
this.layout.init();
this.layout.inited = true;
}
this.layout.enter();
this.simulation.alpha(1);
this.simulation.restart();
}
setData(data) {
this.nodes = data.map(d => ({
r: +d.Magnitude
}))
}
_init() {
this.simulation = d3.forceSimulation()
.force("index-collide", d3.forceCollide(d => d.r + 5).iterations(16))
.force("index-x", d3.forceX().strength(0.04))
.force("index-y", d3.forceY().strength(0.04))
this._initLayouts();
var node = this.svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(this.nodes)
.enter()
.append("circle")
.attr("r", d => d.r )
var that = this;
var ticked = function() {
node
.attr("cx", d => d.x )
.attr("cy", d => d.y );
if(that.layout.ticked) {
that.layout.ticked();
}
}
this.simulation
.nodes(this.nodes)
.on("tick", ticked);
}
_initLayouts() {
this.layouts = {
'total': layoutTotal,
'country': layoutCountry,
'day': layoutDay
};
for (var k in this.layouts) {
var v = this.layouts[k];
this.layouts[k] = new v(this.svg, this.data, this.simulation);
}
}
}

97
src/d3-components/bubbles-nested-routes/ph2/layout-contry.js vendored

@ -0,0 +1,97 @@
const d3 = require('d3');
const _ = require('lodash');
export default class {
constructor(elem, data, simulation) {
this.elem = elem;
this.data = data;
this.simulation = simulation;
}
init() {
var groups = _(this.data)
.map((k, i) => ({
location: k.Location.split(', ')[1],
index: i
}))
.groupBy('location')
.value();
this.countries = _.map(groups, (v, k) => ({ key: k, r: -30 }));
this.links = [];
console.log(groups[this.countries[0].key][0]);
for(var i = 0; i < this.countries.length; i++) {
for(var j = 0; j < groups[this.countries[i].key].length; j++) {
this.links.push({
source: i + this.data.length,
target: groups[this.countries[i].key][j].index
});
}
}
}
enter() {
this.nodesBefore = this.simulation.nodes();
this.simulation
.force("country-link", d3.forceLink().id(d => d.index))
.force("country-charge", d3.forceManyBody().strength(-30))
.force("country-x", d3.forceX().strength(0.11))
.force("country-y", d3.forceY().strength(0.11))
//console.log(this.simulation.force("link").distance());
this.simulation.force("link")//.distance(-10)
this.simulation.nodes(_.concat(this.nodesBefore, this.countries));
this.countryG = this.elem.append("g");
this.countyLabels = this.countryG
.selectAll('text')
.data(this.countries)
.enter()
.append('text')
.text(d => d.key)
.attr("text-anchor", "middle")
.attr("font-size", "8")
this.linesG = this.elem.insert("g", ":first-child");
this.lines = this.linesG
.selectAll("line")
.data(this.links)
.enter()
.append("line")
.attr("stroke", "#d7d7d7")
this.simulation
.force("country-link")
.links(this.links);
}
ticked() {
this.lines
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
this.countyLabels
.attr('x', d => d.x)
.attr('y', d => d.y)
}
exit() {
this.simulation
.nodes(this.nodesBefore);
this.simulation
.force("country-link", null)
.force("country-charge", null)
.force("country-x", null)
.force("country-y", null)
this.linesG.remove();
this.countryG.remove();
}
}

56
src/d3-components/bubbles-nested-routes/ph2/layout-day.js vendored

@ -0,0 +1,56 @@
const d3 = require('d3');
const _ = require('lodash');
const DAY_RANGE = d3.range(14, 24);
const WIDTH = 500;
export default class {
constructor(elem, data, simulation) {
this.elem = elem;
this.data = data;
this.simulation = simulation;
}
init() {
this.dayX = _.map(
this.data, d => this._dayToX(+d.Date.split('-')[0])
);
}
enter() {
this.forceIndexCollide = this.simulation.force("index-collide");
this.simulation.force("index-collide", null);
this.forceIndexY = this.simulation.force("index-y");
this.simulation.force("index-y", null);
this.simulation.force("day-collide", d3.forceCollide(d => d.r + 2).iterations(16))
this.daysG = this.elem.append('g');
this.daysG
.selectAll('text')
.data(DAY_RANGE)
.enter()
.append('text')
.text(d => d)
.attr('x', this._dayToX)
.attr('font-size', 10)
.attr('fill', 'gray')
.attr('text-anchor', 'middle')
var that = this;
this.simulation.force(
'day-x', d3.forceX(d => that.dayX[d.index]).strength(1)
);
this.simulation.force("day-y", d3.forceY().strength(0.005));
}
exit() {
this.daysG.remove();
this.simulation.force('day-x', null);
this.simulation.force("index-collide", this.forceIndexCollide);
this.simulation.force("index-y", this.forceIndexY);
}
_dayToX(day) {
return (day - DAY_RANGE[0]) * WIDTH / DAY_RANGE.length - WIDTH * 0.48
}
}

15
src/d3-components/bubbles-nested-routes/ph2/layout-total.js vendored

@ -0,0 +1,15 @@
export default class {
constructor(elem, data, simulation) {
this.elem = elem;
this.simulation = simulation;
}
init() {
}
enter() {
}
exit() {
}
}

31
src/d3-components/circle-mount.vue

@ -0,0 +1,31 @@
<!--
Links:
Components: https://vuejs.org/v2/guide/components.html
.vue files: https://vuejs.org/v2/guide/single-file-components.html
Advanced:
Component Lifecyrcle: https://vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
Virtual DOM: https://medium.com/js-dojo/whats-new-in-vue-js-2-0-virtual-dom-dc4b5b827f40#.hexwxh9m3
-->
<template>
<svg width="500" height="300"></svg>
</template>
<script>
const d3 = require('d3');
export default {
mounted: function() {
// this.#el - is the root element in <template>
// in this case it is <svg> tag
d3.select(this.$el)
.append('circle')
.attr('cx', '250')
.attr('cy', '150')
.attr('r', '100')
}
}
</script>
<style>
</style>

61
src/d3-components/pie-chart-local-component/index.vue

@ -0,0 +1,61 @@
<!--
Links:
Local Registration: https://vuejs.org/v2/guide/components.html#Local-Registration
Props: https://vuejs.org/v2/guide/components.html#Passing-Data-with-Props
Methods: https://vuejs.org/v2/guide/events.html#Method-Event-Handlers
Conditional Rendering: https://vuejs.org/v2/guide/conditional.html
Data:
http://www.studentsoftheworld.info/penpals/stats.php3?Pays=
-->
<template>
<div>
<h4>Most popular female names in the world</h4>
<pie :data="names" />
<br>
<button v-if="canAddAName" v-on:click="addName">Add a name</button>
</div>
</template>
<script>
var _ = require('lodash')
const pie = require('d3-components/pie-chart-local-component/pie')
const NAMES = [
{ name: 'Sarah', value: 2502 },
{ name: 'Emma', value: 2005 },
{ name: 'Laura', value: 1968 },
{ name: 'Chloé', value: 1863 },
{ name: 'Marie', value: 1810 },
{ name: 'Emily', value: 1637 },
{ name: 'Léa', value: 1592 },
{ name: 'Camille', value: 1572 },
{ name: 'Anna', value: 1433 },
{ name: 'Manon', value: 1403 }
]
export default {
components: {
pie: pie
},
data: function() {
return {
names: _.take(NAMES, 3)
}
},
computed: {
canAddAName: function() {
return this.names.length < NAMES.length;
}
},
methods: {
addName: function() {
this.names.push(NAMES[this.names.length]);
}
}
}
</script>

92
src/d3-components/pie-chart-local-component/pie.vue

@ -0,0 +1,92 @@
<!--
Based on:
https://bl.ocks.org/shimizu/f90651541575f348a129444003a73467
Links:
Props: https://vuejs.org/v2/guide/components.html#Passing-Data-with-Props
Methods: https://vuejs.org/v2/guide/events.html#Method-Event-Handlers
-->
<template>
<svg width="500" height="300"></svg>
</template>
<script>
const d3 = require('d3');
export default {
mounted: function() {
var svg = d3.select(this.$el);
var width = +svg.attr('width');
var height = +svg.attr('height');
var margin = { top:20, left:0, bottom:30, right:0 };
var chartWidth = width - (margin.left + margin.right);
var chartHeight = height - (margin.top + margin.bottom);
this.chartLayer = svg
.append('g')
.attr(
"transform",
`translate(${margin.left}, ${margin.top})`
);
this.arc = d3.arc()
.outerRadius(chartHeight / 2)
.innerRadius(chartHeight / 4)
.padAngle(0.03)
.cornerRadius(8)
this.pieG = this.chartLayer
.append("g")
.attr(
"transform",
`translate(${chartWidth / 2}, ${chartHeight / 2})`
)
this.drawChart(this.data);
},
props: ['data'],
watch: {
data: function(newData) {
this.drawChart(newData);
}
},
methods: {
drawChart: function(data) {
var arcs = d3.pie()
.sort(null)
.value(function(d) { return d.value; })
(data)
var block = this.pieG.selectAll(".arc")
.data(arcs)
block.select('path').attr('d', this.arc)
var newBlock = block
.enter()
.append("g")
.classed("arc", true)
newBlock.append("path")
.attr("d", this.arc)
.attr("id", function(d, i) { return "arc-" + i })
.attr("stroke", "gray")
.attr("fill", d => d3.interpolateCool(Math.random()))
newBlock.append("text")
.attr("dx", 10)
.attr("dy", -5)
.append("textPath")
.attr("xlink:href", function(d, i) { return "#arc-" + i; })
.text(function(d) { return d.data.name })
}
}
}
</script>

49
src/d3-components/size-controller.vue

@ -0,0 +1,49 @@
<!--
Links
Data: https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function
Form Input Bindings: https://vuejs.org/v2/guide/forms.html#Basic-Usage
Watchers: https://vuejs.org/v2/guide/computed.html#Watchers
-->
<template>
<div>
<svg width="500" height="300"></svg>
<br>
<input
type="range"
v-model="circleSize"
min="1"
max="100"
step="1"
>
</div>
</template>
<script>
const d3 = require('d3');
export default {
data: function() {
return {
circleSize: 50
}
},
mounted: function(createElement) {
var svg = d3.select(this.$el).select('svg');
this.circle = svg
.append('circle')
.attr('cx', '250')
.attr('cy', '150')
.attr('r', this.circleSize)
},
watch: {
circleSize: function(newValue) {
this.circle
.attr('r', newValue)
}
}
}
</script>
<style>
</style>

16
src/main.js

@ -0,0 +1,16 @@
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import app from './app'
import router from './router'
import VueResource from 'vue-resource'
Vue.use(VueResource);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<app/>',
components: { app }
})

45
src/router/demos.js

@ -0,0 +1,45 @@
const config = require('config.json');
export const routes = [
{
name: 'Circle: mount',
path: '/circle-mount',
component: require('d3-components/circle-mount')
},
{
name: 'Circle: size controller',
path: '/size-controller',
component: require('d3-components/size-controller')
},
{
name: 'Bars: styles',
path: '/bars-styles',
component: require('d3-components/bars-styles')
},
{
name: 'Pie chart: local component & props',
path: '/pie-chart-local-component',
folder: true,
component: require('d3-components/pie-chart-local-component/index')
},
{
name: 'Basic map: html tooltip & events',
path: '/basic-map-tooltip',
folder: true,
component: require('d3-components/basic-map-tooltip/index')
},
{
name: 'Bubbles: nested routes',
path: '/bubbles-nested-routes',
alias: '/bubbles-nested-routes/*',
folder: true,
component: require('d3-components/bubbles-nested-routes/index')
}
].map(r => {
var res = r;
res.source = config.githubLink +
config.d3ComponentsPath +
r.path +
(r.folder ? '' : '.vue')
return res;
})

17
src/router/index.js

@ -0,0 +1,17 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from 'components/index'
import {routes as demoRoutes} from './demos'
Vue.use(VueRouter)
export default new VueRouter({
routes: [
{
path: '/',
name: 'Menu',
component: Index
}, ... demoRoutes
]
})

0
static/.gitkeep

278
static/css/app.ea478ad0f98c40581c472cf6cea74c24.css

@ -1,278 +0,0 @@
#app[data-v-41b7bac0] {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
margin-top: 30px;
}
a[data-v-41b7bac0] {
color: black;
text-decoration: none;
}
#links[data-v-098bb42e] {
display: block;
width: 300px;
height: 40px;
position: relative;
}
a[data-v-098bb42e] {
display: block;
position: absolute;
width: 100px;
text-decoration: none;
font-weight: bold;
color: blue;
}
a[data-v-098bb42e]:hover {
text-decoration: underline;
}
.prev[data-v-098bb42e] {
left: 0px;
text-align: left;
}
.source[data-v-098bb42e] {
left:100px;
text-align: center;
}
.next[data-v-098bb42e] {
right: 0px;
text-align: right;
}
#holder[data-v-098bb42e] {
width: 300px;
margin: auto;
position: relative;
height: 30px;
/*border-top: 1px dashed gray;*/
border-bottom: 1px dashed gray;
padding-top: 10px;
margin-top: 10px;
margin-bottom: 10px;
}
#bars-style .one {
fill: #ffc300
}
#bars-style .two {
fill: #c70039
}
#bars-style .three {
fill: #571845
}
.one[data-v-63d4ce13] {
fill: #154890
}
.two[data-v-63d4ce13] {
fill: #e1d4c0
}
.three[data-v-63d4ce13] {
fill: #ff6600
}
#bars-style-sass .one {
fill: #AA5C39;
}
#bars-style-sass .two {
fill: #5B9632;
}
#bars-style-sass .three {
fill: #2A4F6E;
}
#holder[data-v-39fd153a] {
position: relative;
height: 300px;
width: 500px;
margin: auto;
}
.mapHolder[data-v-39fd153a] {
position: absolute;
margin: auto;
}
.state {
fill: #ccc;
stroke: #fff;
}
.state:hover {
fill: steelblue;
}
#tooltipPositioner[data-v-6039702a] {
position: relative;
left: 50%;
top: 350px;
width: 240px;
}
#tooltipContainer[data-v-6039702a] {
position: absolute;
bottom: 0;
width: 230px;
left: -125px;
font-size: 12px;
line-height: 16px;
padding: 10px;
border-radius: 3px;
background: rgba(255,255,255,0.9);
color: #000;
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
-moz-box-shadow: 0 1px 5px rgba(0,0,0,0.4);
border:1px solid rgba(200,200,200,0.85);
text-align:center;
}
#tooltip[data-v-6039702a] {
text-align:center;
z-index: 1000;
position: absolute;
display: block;
}
#tooltip .description[data-v-6039702a] {
color:#666;
font-size: 11px;
text-align:center;
font-style:italic;
}
#tooltip .title[data-v-6039702a] {
text-align: left;
font-size: 13px;
text-align:center;
}
.holder[data-v-28f41ffe] {
width: 500px;
margin: auto;
}
.menu ul[data-v-28f41ffe] {
padding: 0;
margin: 0;
}
.menu li[data-v-28f41ffe] {
text-align: left;
}
.menu[data-v-37ada400] {
padding: 0;
margin: 0;
}
.menu li[data-v-37ada400] {
text-align: left;
display: inline-block;
text-align: left;
margin: 0px;
margin-right: 10px;
}
.menu li a[data-v-37ada400] {
text-decoration: none;
}
.menu li a.active[data-v-37ada400] {
text-decoration: underline;
}
h1[data-v-417810ac], h2[data-v-417810ac] {
font-weight: normal;
}
ul[data-v-417810ac] {
/*list-style-type: none;*/
padding: 0;
margin: auto;
width: 300px;
}
li[data-v-417810ac] {
/*display: inline-block;*/
margin: 0 0px;
list-style: decimal
}
a[data-v-417810ac] {
color: blue;
display: block;
text-decoration: none;
text-align: left;
}
a[data-v-417810ac]:hover {
text-decoration: underline;
}
/*# sourceMappingURL=app.ea478ad0f98c40581c472cf6cea74c24.css.map*/

1
static/css/app.ea478ad0f98c40581c472cf6cea74c24.css.map

File diff suppressed because one or more lines are too long

2
static/js/app.bb478689be3640ebf141.js

File diff suppressed because one or more lines are too long

1
static/js/app.bb478689be3640ebf141.js.map

File diff suppressed because one or more lines are too long

2
static/js/manifest.0d9440aa0954c24a67c3.js

@ -1,2 +0,0 @@
!function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var n=window.webpackJsonp;window.webpackJsonp=function(t,c,i){for(var u,a,f,s=0,l=[];s<t.length;s++)a=t[s],o[a]&&l.push(o[a][0]),o[a]=0;for(u in c)Object.prototype.hasOwnProperty.call(c,u)&&(e[u]=c[u]);for(n&&n(t,c,i);l.length;)l.shift()();if(i)for(s=0;s<i.length;s++)f=r(r.s=i[s]);return f};var t={},o={2:0};r.e=function(e){function n(){c.onerror=c.onload=null,clearTimeout(i);var r=o[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}if(0===o[e])return Promise.resolve();if(o[e])return o[e][2];var t=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.timeout=12e4,r.nc&&c.setAttribute("nonce",r.nc),c.src=r.p+"static/js/"+e+"."+{0:"03b56a03b17565331709",1:"bb478689be3640ebf141"}[e]+".js";var i=setTimeout(n,12e4);c.onerror=c.onload=n;var u=new Promise(function(r,n){o[e]=[r,n]});return o[e][2]=u,t.appendChild(c),u},r.m=e,r.c=t,r.i=function(e){return e},r.d=function(e,n,t){r.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r.p="/",r.oe=function(e){throw console.error(e),e}}([]);
//# sourceMappingURL=manifest.0d9440aa0954c24a67c3.js.map

1
static/js/manifest.0d9440aa0954c24a67c3.js.map

File diff suppressed because one or more lines are too long

24
static/js/vendor.03b56a03b17565331709.js

File diff suppressed because one or more lines are too long

1
static/js/vendor.03b56a03b17565331709.js.map

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save