├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .rubocop.yml ├── CHANGELOG.adoc ├── Gemfile ├── LICENSE.adoc ├── README.adoc ├── Rakefile ├── asciidoctor-chart.gemspec ├── cypress.config.js ├── cypress ├── e2e │ └── visual.regression.cy.js ├── plugins │ └── index.js ├── snapshots │ ├── base │ │ └── visual.regression.cy.js │ │ │ ├── c3.js-base-1657462460688.png │ │ │ ├── c3.js-base.png │ │ │ ├── chart.js-base-1657462456765.png │ │ │ ├── chart.js-base.png │ │ │ ├── chartist.js-base-1657462465362.png │ │ │ └── chartist.js-base.png │ └── diff │ │ └── .gitkeep └── support │ ├── commands.js │ ├── commands │ └── beforeCompareSnapshotCommand.js │ └── e2e.js ├── examples ├── c3js.adoc ├── chart-c3js.png ├── chart-chartjs.png ├── chartist.adoc ├── chartjs.adoc ├── docinfo.html ├── fonts │ ├── OpenSans-Italic-VariableFont_wdth,wght.ttf │ └── OpenSans-VariableFont_wdth,wght.ttf └── sample-data.csv ├── lib ├── asciidoctor-chart.rb └── asciidoctor │ ├── chart.rb │ └── chart │ ├── block_macro_processor.rb │ ├── block_processor.rb │ ├── chart_block.rb │ ├── converter.rb │ ├── converter │ ├── c3js.rb │ ├── chartist.rb │ └── chartjs.rb │ ├── docinfo_processor.rb │ ├── html5_chart_converter_ext.rb │ ├── plain_ruby_csv.rb │ ├── plain_ruby_random.rb │ ├── preprocessor.rb │ ├── registry.rb │ └── version.rb ├── package-lock.json ├── package.json ├── spec ├── block_processor_spec.rb ├── chartjs_converter_spec.rb ├── docinfo_processor_spec.rb ├── fixtures │ ├── sample-data.csv │ └── templates │ │ └── erb │ │ └── paragraph.html.erb ├── spec_helper.rb └── template_converter_spec.rb └── tasks ├── bundler.rake ├── clean.rake ├── rspec.rake └── rubocop.rake /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: 2.7 17 | bundler-cache: true 18 | - uses: actions/setup-node@v3 19 | with: 20 | node-version: 16 21 | cache: 'npm' 22 | - run: bundle install 23 | - run: bundle exec rake lint 24 | - run: bundle exec rake spec 25 | - run: bundle exec rake 26 | - run: node -v 27 | - run: npm -v 28 | - run: npm ci 29 | - run: npm t 30 | - uses: actions/upload-artifact@v3 31 | if: ${{ always() }} 32 | with: 33 | name: snapshots 34 | path: cypress/snapshots/**/*.png 35 | - uses: actions/upload-artifact@v3 36 | if: ${{ always() }} 37 | with: 38 | name: reports 39 | path: cypress/reports/**/*.* 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-ruby@v1 13 | with: 14 | ruby-version: '2.6' 15 | - name: Install and test 16 | run: | 17 | bundle install 18 | bundle exec rake 19 | - name: Configure credentials 20 | run: | 21 | mkdir -p $HOME/.gem 22 | touch $HOME/.gem/credentials 23 | chmod 0600 $HOME/.gem/credentials 24 | printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}" > $HOME/.gem/credentials 25 | env: 26 | RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }} 27 | - name: Build gem 28 | run: | 29 | bundle exec rake build 30 | - name: Publish to rubygems.org 31 | run: | 32 | gem push pkg/asciidoctor-chart-${GITHUB_REF#refs/tags/v}.gem 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /Gemfile.lock 3 | /pkg/ 4 | /examples/.bundle 5 | /examples/vendor 6 | /examples/*.html 7 | !/examples/docinfo.html 8 | /examples/Gemfile.lock 9 | /.idea 10 | /out 11 | /vendor 12 | /node_modules 13 | /cypress/snapshots/diff/**/*.png 14 | /cypress/snapshots/actual/**/*.png 15 | /cypress/videos/*.mp4 16 | /cypress/screenshots/ 17 | /cypress/reports 18 | /cypress/results -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: enable 3 | SuggestExtensions: false 4 | Layout/HeredocIndentation: 5 | Enabled: false 6 | Layout/SpaceInsideBlockBraces: 7 | SpaceBeforeBlockParameters: false 8 | Naming/HeredocDelimiterNaming: 9 | Enabled: false 10 | Naming/AccessorMethodName: 11 | Enabled: false # fixme: reenable 12 | Naming/FileName: 13 | Enabled: false 14 | Lint/RedundantStringCoercion: 15 | Enabled: false # fixme: reenable 16 | Lint/DuplicateBranch: 17 | Enabled: false 18 | Style/MethodDefParentheses: 19 | Enabled: false 20 | Style/Documentation: 21 | Enabled: false # fixme: reenable 22 | Metrics/BlockNesting: 23 | Max: 10 24 | Metrics/ClassLength: 25 | Enabled: false 26 | Metrics/MethodLength: 27 | Enabled: false 28 | Metrics/AbcSize: 29 | Enabled: false 30 | Metrics/CyclomaticComplexity: 31 | Enabled: false 32 | Metrics/BlockLength: 33 | Enabled: false 34 | Gemspec/RequiredRubyVersion: 35 | Enabled: false -------------------------------------------------------------------------------- /CHANGELOG.adoc: -------------------------------------------------------------------------------- 1 | = Asciidoctor Chart Changelog 2 | :icons: font 3 | :uri-repo: https://github.com/asciidoctor/asciidoctor-chart 4 | 5 | This document provides a high-level view of the changes introduced in Asciidoctor Chart by release. 6 | For an even more detailed look at what has changed, refer to the {uri-repo}/commits/[commit history] on GitHub. 7 | 8 | The format is based on https://keepachangelog.com/en/1.0.0/[Keep a Changelog], 9 | and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Versioning]. 10 | 11 | == Unreleased 12 | 13 | 14 | == 1.0.0 (2022-10-08) 15 | 16 | === ✨ Added 17 | 18 | * Introduce a changelog (#30) 19 | * Add support for using the JavaScript libraries offline (#25) 20 | * Add captioned title on charts (#18) 21 | * Improved documentation 22 | * Add attributes to define chart resources directory (#26) 23 | * Introduce visual regression tests (#34) 24 | 25 | === 🎨 Changed 26 | 27 | * Use 0.11.x as default version for Chartist.js instead of latest (#28) 28 | * Update c3.js defaults to latest version 0.7.20 (#32) 29 | * Upgrade Chart.js from 1.0.2 to 3.7.0 and update code accordingly (#17) 30 | * Use https protocol (instead of http) when loading scripts from cdnjs.cloudflare.com (#17) 31 | * Apply RuboCop to asciidoctor-chart.gemspec (#17) 32 | * Enable multi-factor authentication when publishing to https://rubygems.org/ (#17) 33 | 34 | === 🐞 Fixed 35 | 36 | * Fix an issue to allow more than one Chart.js graphs per page (#13) 37 | * Include assets (styles, scripts) when an chart engine is used (#24) 38 | * Fix an issue with Chart.js where text was overlapping the graph when width was specified (#17) 39 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Look in the gemspec file for runtime and development dependencies 6 | gemspec 7 | 8 | group :lint do 9 | gem 'rubocop', '~> 1.24.1', require: false 10 | end 11 | -------------------------------------------------------------------------------- /LICENSE.adoc: -------------------------------------------------------------------------------- 1 | .The MIT License 2 | .... 3 | Copyright (C) 2014-2020 Guillaume Grossetie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | .... 23 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Asciidoctor Chart 2 | // Aliases: 3 | :project-name: Asciidoctor Chart 4 | :project-handle: asciidoctor-chart 5 | // Variables: 6 | :release-version: 1.0.0.alpha.1 7 | :uri-repo: https://github.com/asciidoctor/asciidoctor-chart 8 | // Settings: 9 | :idprefix: 10 | :idseparator: - 11 | 12 | image:https://github.com/asciidoctor/asciidoctor-chart/workflows/CI/badge.svg[link=https://github.com/asciidoctor/asciidoctor-chart/actions?query=workflow%3ACI] 13 | image:https://img.shields.io/gem/v/asciidoctor-chart?include_prereleases[link=https://rubygems.org/search?query=asciidoctor-chart] 14 | 15 | A set of Asciidoctor extensions that adds a chart block and block macro for including charts powered by https://c3js.org/[C3.js], https://gionkunz.github.io/chartist-js/[Chartist], or https://www.chartjs.org/[Chart.js] in your AsciiDoc document. 16 | 17 | == Prerequisites 18 | 19 | All that's needed is Ruby 2.5 or better (or JRuby 9.2 or better) and a few Ruby gems (including at least Asciidoctor 2.0.0), which we explain how to install in the next section. 20 | 21 | To check if you have Ruby available, use the `ruby` command to query the version installed: 22 | 23 | $ ruby -e 'puts RUBY_VERSION' 24 | 25 | Make sure this command reports a Ruby version that's at least 2.5. 26 | If so, you may proceed. 27 | 28 | == Getting Started 29 | 30 | You can get {project-name} by <>. 31 | ifndef::env-site[You can also <> if you want to use a development version or participate in development.] 32 | 33 | === Install the Published Gem 34 | 35 | To install {project-name}, first make sure you have satisfied the <>. 36 | Then, install the gem from RubyGems.org using the following command: 37 | 38 | $ gem install asciidoctor-chart 39 | 40 | === Enable the Extension 41 | 42 | Assuming all the required gems install properly, you can enable the extension using `--require` option (or `-r` for short) from the Asciidoctor CLI: 43 | 44 | $ asciidoctor --require asciidoctor-chart my-doc.adoc 45 | 46 | === Usage 47 | 48 | Line chart powered by C3.js (default) declared as a literal block:: 49 | + 50 | ---- 51 | [chart,line] 52 | .... 53 | January,February,March,April,May,June,July 54 | 28,48,40,19,86,27,90 55 | 65,59,80,81,56,55,40 56 | .... 57 | ---- 58 | + 59 | image::./examples/chart-c3js.png[] 60 | 61 | Line chart powered by Chart.js declared as a block macro with a CSV file as target:: 62 | + 63 | ---- 64 | chart::sample-data.csv[line,engine="chartjs"] 65 | ---- 66 | + 67 | image::./examples/chart-chartjs.png[] 68 | 69 | For more examples, see {uri-repo}/blob/master/examples/example.adoc[example.adoc]. 70 | 71 | === Configuration 72 | 73 | [cols="1s,1,3"] 74 | |=== 75 | |Attribute{nbsp}Name |Value(s)|Description 76 | 77 | |c3jsdir 78 | | 79 | |Overrides c3.js directory, where the following files `c3.min.css` and `c3.min.js` are expected. Default is `https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/`. 80 | 81 | |chartjsdir 82 | | 83 | |Overrides chart.js directory, where the following file `chart.min.js` is expected. Default is `https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/`. 84 | 85 | |chartistdir 86 | | 87 | |Overrides chartist.js directory, where the following files `chartist.min.css` and `chartist.min.js` are expected. Default is `https://cdn.jsdelivr.net/chartist.js/0.11.x/`. 88 | 89 | 90 | |d3jsdir 91 | | 92 | |Overrides d3.js directory, where the following file `d3.min.js` is expected. Default is `https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/`. 93 | |=== 94 | 95 | 96 | == Authors 97 | 98 | {project-name} was written by https://github.com/mogztter/[Guillaume Grossetie]. 99 | 100 | == Copyright 101 | 102 | Copyright (C) 2014-present Guillaume Grossetie 103 | Free use of this software is granted under the terms of the MIT License. 104 | 105 | For the full text of the license, see the <> file. 106 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $default_tasks = [] # rubocop:disable Style/GlobalVars 4 | Dir.glob('tasks/*.rake').each {|file| load file } 5 | task default: $default_tasks unless $default_tasks.empty? # rubocop:disable Style/GlobalVars 6 | -------------------------------------------------------------------------------- /asciidoctor-chart.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require_relative 'lib/asciidoctor/chart/version' 5 | rescue LoadError 6 | require 'asciidoctor/chart/version' 7 | end 8 | 9 | Gem::Specification.new do |s| 10 | s.name = 'asciidoctor-chart' 11 | s.version = Asciidoctor::Chart::VERSION 12 | s.summary = 'Adds a chart block and block macro to AsciiDoc' 13 | s.description = "A set of Asciidoctor extensions that add a chart block and block macro to AsciiDoc 14 | for including charts in your AsciiDoc document." 15 | s.authors = ['Guillaume Grossetie'] 16 | s.email = 'ggrossetie@gmail.com' 17 | s.homepage = 'https://asciidoctor.org' 18 | s.license = 'MIT' 19 | # NOTE: required ruby version is informational only; 20 | # it's not enforced since it can't be overridden and can cause builds to break 21 | # s.required_ruby_version = '>= 2.7.0' 22 | s.metadata = { 23 | 'bug_tracker_uri' => 'https://github.com/asciidoctor/asciidoctor-chart/issues', 24 | # 'changelog_uri' => 'https://github.com/asciidoctor/asciidoctor-chart/blob/master/CHANGELOG.adoc', 25 | 'community_chat_uri' => 'https://asciidoctor.zulipchat.com', 26 | 'source_code_uri' => 'https://github.com/asciidoctor/asciidoctor-chart', 27 | 'rubygems_mfa_required' => 'true' 28 | } 29 | 30 | # NOTE: the logic to build the list of files is designed to produce a usable package 31 | # even when the git command is not available 32 | begin 33 | files = (result = `git ls-files -z`.split "\0").empty? ? Dir['**/*'] : result 34 | rescue StandardError 35 | files = Dir['**/*'] 36 | end 37 | s.files = files.grep %r{^(?:(?:data|lib)/.+|(?:CHANGELOG|LICENSE|NOTICE|README)\.adoc|\.yardopts|#{s.name}\.gemspec)$} 38 | s.executables = (files.grep %r{^bin/}).map {|f| File.basename f } 39 | s.require_paths = ['lib'] 40 | 41 | s.add_runtime_dependency 'asciidoctor', '~> 2.0' 42 | s.add_runtime_dependency 'tilt', '~> 2.0.0' 43 | s.add_development_dependency 'rake', '~> 13.0.0' 44 | s.add_development_dependency 'rspec', '~> 3.9.0' 45 | end 46 | -------------------------------------------------------------------------------- /cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | screenshotsFolder: 'cypress/snapshots/actual', 5 | trashAssetsBeforeRuns: true, 6 | video: false, 7 | env: { 8 | failSilently: false, 9 | }, 10 | e2e: { 11 | setupNodeEvents(on, config) { 12 | return require('./cypress/plugins/index.js')(on, config) 13 | }, 14 | }, 15 | reporter: "cypress-multi-reporters", 16 | reporterOptions: { 17 | reporterEnabled: 'mocha-junit-reporter, cypress-mochawesome-reporter', 18 | mochaJunitReporterReporterOptions: { 19 | mochaFile: "./cypress/results/junit.xml", 20 | toConsole: false 21 | }, 22 | cypressMochawesomeReporterReporterOptions: { 23 | reportDir: "cypress/reports", 24 | charts: true, 25 | embeddedScreenshots: true, 26 | inlineAssets: true 27 | }, 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /cypress/e2e/visual.regression.cy.js: -------------------------------------------------------------------------------- 1 | describe('visual regression tests', () => { 2 | 3 | it('should display the Chart.js charts correctly', () => { 4 | cy.visit('./examples/chartjs.html').wait(3000).compareSnapshot('chart.js') 5 | }); 6 | 7 | it('should display the C3.js charts correctly', () => { 8 | cy.visit('./examples/c3js.html').wait(3000).compareSnapshot('c3.js') 9 | }); 10 | 11 | it('should display the Chartist.js charts correctly', () => { 12 | cy.visit('./examples/chartist.html').wait(3000).compareSnapshot('chartist.js') 13 | }); 14 | 15 | }) 16 | 17 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | const compareSnapshotsPlugin = require('cypress-visual-regression/dist/plugin'); 2 | 3 | module.exports = (on, config) => { 4 | compareSnapshotsPlugin(on, config); 5 | 6 | require('cypress-mochawesome-reporter/plugin')(on); 7 | 8 | // force color profile 9 | on('before:browser:launch', (browser = {}, launchOptions) => { 10 | if (browser.family === 'chromium' && browser.name !== 'electron') { 11 | launchOptions.args.push('--force-color-profile=srgb'); 12 | } 13 | }); 14 | 15 | return { 16 | browsers: config.browsers.filter( 17 | (b) => b.family === 'chromium' && b.name !== 'electron' 18 | ), 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /cypress/snapshots/base/visual.regression.cy.js/c3.js-base-1657462460688.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/cypress/snapshots/base/visual.regression.cy.js/c3.js-base-1657462460688.png -------------------------------------------------------------------------------- /cypress/snapshots/base/visual.regression.cy.js/c3.js-base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/cypress/snapshots/base/visual.regression.cy.js/c3.js-base.png -------------------------------------------------------------------------------- /cypress/snapshots/base/visual.regression.cy.js/chart.js-base-1657462456765.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/cypress/snapshots/base/visual.regression.cy.js/chart.js-base-1657462456765.png -------------------------------------------------------------------------------- /cypress/snapshots/base/visual.regression.cy.js/chart.js-base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/cypress/snapshots/base/visual.regression.cy.js/chart.js-base.png -------------------------------------------------------------------------------- /cypress/snapshots/base/visual.regression.cy.js/chartist.js-base-1657462465362.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/cypress/snapshots/base/visual.regression.cy.js/chartist.js-base-1657462465362.png -------------------------------------------------------------------------------- /cypress/snapshots/base/visual.regression.cy.js/chartist.js-base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/cypress/snapshots/base/visual.regression.cy.js/chartist.js-base.png -------------------------------------------------------------------------------- /cypress/snapshots/diff/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/cypress/snapshots/diff/.gitkeep -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | const compareSnapshotCommand = require('cypress-visual-regression/dist/command'); 2 | const beforeCompareSnapshotCommand = require('./commands/beforeCompareSnapshotCommand'); 3 | 4 | // noinspection JSCheckFunctionSignatures 5 | compareSnapshotCommand({ 6 | "errorThreshold": 0.01, 7 | "capture": "fullPage", 8 | }); 9 | beforeCompareSnapshotCommand( 10 | "div#footer-text" 11 | ) -------------------------------------------------------------------------------- /cypress/support/commands/beforeCompareSnapshotCommand.js: -------------------------------------------------------------------------------- 1 | // noinspection JSValidateTypes 2 | /** 3 | * To be called after you setup the command, in order to add a 4 | * hook that does stuff before the command is triggered 5 | */ 6 | function beforeCompareSnapshotCommand( 7 | /** Element you want to ignore */ 8 | ignoredElementsQuerySelector, 9 | /** Main app element (if you want for the page to be loaded before triggering the command) */ 10 | appContentQuerySelector = "body" 11 | ) { 12 | // noinspection JSCheckFunctionSignatures 13 | Cypress.Commands.overwrite("compareSnapshot", (originalFn, ...args) => { 14 | // noinspection JSCheckFunctionSignatures 15 | return cy 16 | // wait for content to be ready 17 | .get(appContentQuerySelector) 18 | // hide ignored elements 19 | .then($app => { 20 | return new Cypress.Promise((resolve, reject) => { 21 | setTimeout(() => { 22 | $app.find(ignoredElementsQuerySelector).css("visibility", "hidden"); 23 | resolve(); 24 | // add a very small delay to wait for the elements to be there, but you should 25 | // make sure your test already handles this 26 | }, 300); 27 | }); 28 | }) 29 | .then(() => { 30 | return originalFn(...args); 31 | }); 32 | }); 33 | } 34 | 35 | module.exports = beforeCompareSnapshotCommand; -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | import 'cypress-mochawesome-reporter/register' -------------------------------------------------------------------------------- /examples/c3js.adoc: -------------------------------------------------------------------------------- 1 | = C3.js Chart Block Macro Extension 2 | 3 | chart::sample-data.csv[height=200,width=500] 4 | 5 | chart::sample-data.csv[spline,500,400] 6 | 7 | // Set axis labels (c3js only, see https://c3js.org/samples/axes_label.html) 8 | chart::sample-data.csv[step, 500, 400, axis-x-label="X Label", axis-y-label="Y Label"] 9 | 10 | // Set data names (c3js only, see https://c3js.org/samples/data_name.html) 11 | chart::sample-data.csv[line, data-names="{'0':'Name 1', '1':'Name 2'}"] 12 | 13 | [chart,line] 14 | .... 15 | January,February,March,April,May,June,July 16 | 28,48,40,19,86,27,90 17 | 65,59,80,81,56,55,40 18 | .... 19 | -------------------------------------------------------------------------------- /examples/chart-c3js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/examples/chart-c3js.png -------------------------------------------------------------------------------- /examples/chart-chartjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/examples/chart-chartjs.png -------------------------------------------------------------------------------- /examples/chartist.adoc: -------------------------------------------------------------------------------- 1 | = Chartist.js Chart Block Macro Extension 2 | 3 | chart::sample-data.csv[bar,600,300,engine="chartist"] -------------------------------------------------------------------------------- /examples/chartjs.adoc: -------------------------------------------------------------------------------- 1 | = Chart.js Chart Block Macro Extension 2 | 3 | chart::sample-data.csv[line,engine="chartjs",id="chartjs-line"] -------------------------------------------------------------------------------- /examples/docinfo.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/examples/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf -------------------------------------------------------------------------------- /examples/fonts/OpenSans-VariableFont_wdth,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asciidoctor/asciidoctor-chart/06ef15c1f874ef74ef18e5a9ad98c68717b59c69/examples/fonts/OpenSans-VariableFont_wdth,wght.ttf -------------------------------------------------------------------------------- /examples/sample-data.csv: -------------------------------------------------------------------------------- 1 | January,February,March,April,May,June,July 2 | 28,48,40,19,86,27,90 3 | 65,59,80,81,56,55,40 -------------------------------------------------------------------------------- /lib/asciidoctor-chart.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'asciidoctor/chart' 4 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'asciidoctor/extensions' 4 | require 'tilt' 5 | require_relative 'chart/html5_chart_converter_ext' 6 | require_relative 'chart/registry' 7 | require_relative 'chart/preprocessor' 8 | require_relative 'chart/block_macro_processor' 9 | require_relative 'chart/block_processor' 10 | require_relative 'chart/docinfo_processor' 11 | require_relative 'chart/chart_block' 12 | require_relative 'chart/plain_ruby_csv' 13 | require_relative 'chart/plain_ruby_random' 14 | require_relative 'chart/converter' 15 | require_relative 'chart/converter/c3js' 16 | require_relative 'chart/converter/chartist' 17 | require_relative 'chart/converter/chartjs' 18 | 19 | CHART_HTML_TEMPLATE = Tilt.new('chart.html.erb', format: :html5, pretty: true, disable_escape: true) do |_t| 20 | <<-ERB 21 | <%= Class.new.extend(Asciidoctor::Chart::Html5ChartConverterExt).convert_chart self %> 22 | ERB 23 | end 24 | 25 | # providers 26 | Asciidoctor::Chart::Registry.register Asciidoctor::Chart::Converter::Html5ChartjsConverter.new, 'chartjs' 27 | Asciidoctor::Chart::Registry.register Asciidoctor::Chart::Converter::Html5ChartistConverter.new, 'chartist' 28 | Asciidoctor::Chart::Registry.register Asciidoctor::Chart::Converter::Html5C3jsConverter.new, 'c3js' 29 | 30 | def register_chart_converter converter 31 | if (converter.instance_of? Asciidoctor::Converter::TemplateConverter) || (converter.respond_to? 'register') 32 | # Template based converter 33 | converter.register 'chart', CHART_HTML_TEMPLATE 34 | else 35 | converter.extend(Asciidoctor::Chart::Html5ChartConverterExt) 36 | end 37 | end 38 | 39 | Asciidoctor::Extensions.register do 40 | return unless document.basebackend? 'html' 41 | 42 | converter = document.converter 43 | if converter.instance_of? Asciidoctor::Converter::CompositeConverter 44 | register_chart_converter(converter.converters[0]) 45 | else 46 | register_chart_converter(converter) 47 | end 48 | preprocessor Asciidoctor::Chart::Preprocessor 49 | block_macro Asciidoctor::Chart::BlockMacroProcessor 50 | block Asciidoctor::Chart::BlockProcessor 51 | docinfo_processor Asciidoctor::Chart::DocinfoProcessor 52 | end 53 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/block_macro_processor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | class BlockMacroProcessor < ::Asciidoctor::Extensions::BlockMacroProcessor 6 | use_dsl 7 | named :chart 8 | name_positional_attributes 'type', 'width', 'height', 'axis-x-label', 'axis-y-label', 'data-names' 9 | 10 | def process parent, target, attrs 11 | data_path = parent.normalize_asset_path target, 'target' 12 | read_data = parent.read_asset data_path, warn_on_failure: true, normalize: true 13 | return if read_data.nil? || read_data.empty? 14 | 15 | raw_data = PlainRubyCSV.parse read_data 16 | Asciidoctor::Chart::ChartBlock.new parent, raw_data, attrs 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/block_processor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | class BlockProcessor < ::Asciidoctor::Extensions::BlockProcessor 6 | use_dsl 7 | named :chart 8 | on_context :literal 9 | name_positional_attributes 'type', 'width', 'height', 'axis-x-label', 'axis-y-label', 'data-names' 10 | parse_content_as :raw 11 | 12 | def process parent, reader, attrs 13 | raw_data = PlainRubyCSV.parse reader.source 14 | Asciidoctor::Chart::ChartBlock.new parent, raw_data, attrs 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/chart_block.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | class ChartBlock < Asciidoctor::Block 6 | def initialize(parent, data, attrs) 7 | engine = parent.document.get_engine attrs 8 | caption = attrs.delete 'caption' 9 | title = attrs.delete 'title' 10 | block_attributes = attrs.merge({ 11 | 'id' => attrs['id'] || "chart#{PlainRubyRandom.uuid}", 12 | 'type' => attrs['type'] || 'line', 13 | 'engine' => engine, 14 | 'data-raw' => data 15 | }) 16 | super parent, :chart, { source: nil, attributes: block_attributes, subs: nil } 17 | @title = title 18 | assign_caption(caption, 'figure') 19 | end 20 | end 21 | 22 | module ChartBlockTracker 23 | attr_reader :x_chart 24 | 25 | def self.extended instance 26 | instance.instance_variable_set :@x_chart, { 27 | engines: Set.new 28 | } 29 | end 30 | 31 | def get_engine attrs 32 | engine = if attrs.key? 'engine' 33 | attrs['engine'].downcase 34 | elsif @attributes.key? 'chart-engine' 35 | @attributes['chart-engine'].downcase 36 | else 37 | 'c3js' 38 | end 39 | @x_chart[:engines].add(engine) 40 | 41 | engine 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/converter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | module Converter 6 | class Base 7 | def handles? type 8 | respond_to? %(convert_#{type}_chart) 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/converter/c3js.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | module Converter 6 | class Html5C3jsConverter < Asciidoctor::Chart::Converter::Base 7 | def convert_line_chart node 8 | data, labels = prepare_data node 9 | chart_generate_script = chart_line_script node, data, labels 10 | <<~HTML 11 | #{chart_block_element node} 12 | #{chart_generate_script} 13 | HTML 14 | end 15 | 16 | def convert_bar_chart node 17 | data, labels = prepare_data node 18 | <<~HTML 19 | #{chart_block_element node} 20 | #{chart_bar_script node, data, labels} 21 | HTML 22 | end 23 | 24 | def convert_step_chart node 25 | data, labels = prepare_data node 26 | <<~HTML 27 | #{chart_block_element node} 28 | #{chart_step_script node, data, labels} 29 | HTML 30 | end 31 | 32 | def convert_spline_chart node 33 | data, labels = prepare_data node 34 | <<~HTML 35 | #{chart_block_element node} 36 | #{chart_spline_script node, data, labels} 37 | HTML 38 | end 39 | 40 | def convert_pie_chart node 41 | raw_data = node.attr 'data-raw', [] 42 | <<~HTML 43 | #{chart_block_element node} 44 | #{chart_pie_script node, raw_data} 45 | HTML 46 | end 47 | 48 | private 49 | 50 | def prepare_data node 51 | raw_data = node.attr 'data-raw', [] 52 | return [[], []] if raw_data.length <= 1 # question: should we warn? 53 | 54 | labels = raw_data[0] 55 | raw_data.shift 56 | raw_data.map.with_index {|row, index| row.unshift index.to_s } 57 | [raw_data, labels] 58 | end 59 | 60 | def chart_bar_script node, data, labels 61 | chart_height = get_chart_height node 62 | chart_width = get_chart_width node 63 | axis_x_label = get_axis_x_label node 64 | axis_y_label = get_axis_y_label node 65 | data_names = get_data_names node 66 | <<~HTML 67 | 88 | HTML 89 | end 90 | 91 | def chart_line_script node, data, labels 92 | chart_height = get_chart_height node 93 | chart_width = get_chart_width node 94 | axis_x_label = get_axis_x_label node 95 | axis_y_label = get_axis_y_label node 96 | data_names = get_data_names node 97 | <<~HTML 98 | 118 | HTML 119 | end 120 | 121 | def chart_step_script node, data, labels 122 | chart_height = get_chart_height node 123 | chart_width = get_chart_width node 124 | axis_x_label = get_axis_x_label node 125 | axis_y_label = get_axis_y_label node 126 | data_names = get_data_names node 127 | <<~HTML 128 | 149 | HTML 150 | end 151 | 152 | def chart_spline_script node, data, labels 153 | chart_height = get_chart_height node 154 | chart_width = get_chart_width node 155 | axis_x_label = get_axis_x_label node 156 | axis_y_label = get_axis_y_label node 157 | data_names = get_data_names node 158 | <<~HTML 159 | 180 | HTML 181 | end 182 | 183 | def chart_pie_script node, raw_data 184 | chart_height = get_chart_height node 185 | chart_width = get_chart_width node 186 | <<~HTML 187 | 197 | HTML 198 | end 199 | 200 | def chart_block_element node 201 | title_element = node.title? ? %(\n
#{node.captioned_title}
) : '' 202 | %(
203 |
204 |
205 |
#{title_element} 206 |
) 207 | end 208 | 209 | def get_chart_height node 210 | node.attr 'height', '400' 211 | end 212 | 213 | def get_chart_width node 214 | node.attr 'width', '600' 215 | end 216 | 217 | def get_axis_x_label node 218 | node.attr?('axis-x-label') ? CGI.unescapeHTML(node.attr('axis-x-label')) : '' 219 | end 220 | 221 | def get_axis_y_label node 222 | node.attr?('axis-y-label') ? CGI.unescapeHTML(node.attr('axis-y-label')) : '' 223 | end 224 | 225 | def get_data_names node 226 | node.attr?('data-names') ? CGI.unescapeHTML(node.attr('data-names')) : '{}' 227 | end 228 | end 229 | end 230 | end 231 | end 232 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/converter/chartist.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | module Converter 6 | class Html5ChartistConverter < Asciidoctor::Chart::Converter::Base 7 | def convert_line_chart node 8 | data, labels = prepare_data node 9 | <<~HTML 10 | #{chart_block_element node} 11 | #{chart_line_script node, data, labels} 12 | HTML 13 | end 14 | 15 | def convert_bar_chart node 16 | data, labels = prepare_data node 17 | <<~HTML 18 | #{chart_block_element node} 19 | #{chart_bar_script node, data, labels} 20 | HTML 21 | end 22 | 23 | private 24 | 25 | def prepare_data node 26 | raw_data = node.attr 'data-raw', [] 27 | return [[], []] if raw_data.length <= 1 # question: should we warn? 28 | 29 | labels = raw_data[0] 30 | raw_data.shift 31 | [raw_data, labels] 32 | end 33 | 34 | def chart_block_element node 35 | title_element = node.title? ? %(\n
#{node.captioned_title}
) : '' 36 | %(
37 |
38 |
39 |
#{title_element} 40 |
) 41 | end 42 | 43 | def chart_line_script node, data, labels 44 | chart_height = node.attr 'height', '400' 45 | chart_width = node.attr 'width', '600' 46 | <<~HTML 47 | 59 | HTML 60 | end 61 | 62 | def chart_bar_script node, data, labels 63 | chart_height = node.attr 'height', '400' 64 | <<~HTML 65 | 76 | HTML 77 | end 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/converter/chartjs.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | module Converter 6 | class Html5ChartjsConverter < Asciidoctor::Chart::Converter::Base 7 | CSS_VALUE_UNIT_RX = /^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)$/.freeze 8 | DEFAULT_COLORS = [{ r: 220, g: 220, b: 220 }, { r: 151, g: 187, b: 205 }].freeze 9 | 10 | def convert_line_chart node 11 | data, labels = prepare_data node 12 | datasets = data.map do |set| 13 | color = DEFAULT_COLORS[data.index(set) % 2] 14 | color_rgba = "rgba(#{color[:r]},#{color[:g]},#{color[:b]},1.0)" 15 | <<~JSON 16 | { 17 | borderColor: "#{color_rgba}", 18 | backgroundColor: "#{color_rgba}", 19 | fill: false, 20 | tension: 0.1, 21 | data: #{set.to_s} 22 | } 23 | JSON 24 | end.join ',' 25 | 26 | chart_id = node.attr 'id' 27 | inline_styles = [] 28 | if (chart_height = get_height node) 29 | inline_styles.push("height: #{chart_height}") 30 | end 31 | if (chart_width = get_width node) 32 | inline_styles.push("max-width: #{chart_width}") 33 | end 34 | maintain_aspect_ratio = chart_height.nil? && chart_width.nil? 35 | title_element = node.title? ? %(\n
#{node.captioned_title}
) : '' 36 | <<~HTML 37 |
38 |
39 | 40 |
#{title_element} 41 |
42 | 66 | HTML 67 | end 68 | 69 | def prepare_data node 70 | raw_data = node.attr 'data-raw', [] 71 | return [[], []] if raw_data.length <= 1 # question: should we warn? 72 | 73 | labels = raw_data[0] 74 | raw_data.shift 75 | [raw_data, labels] 76 | end 77 | 78 | private 79 | 80 | def get_height node 81 | return unless (height = node.attr 'height') 82 | 83 | to_css_size height 84 | end 85 | 86 | def get_width node 87 | return unless (width = node.attr 'width') 88 | 89 | to_css_size width 90 | end 91 | 92 | def to_css_size str 93 | return str unless (parts = str.match(CSS_VALUE_UNIT_RX)) 94 | 95 | value, unit = parts.captures 96 | unit = 'px' if unit == '' 97 | "#{value}#{unit}" 98 | end 99 | end 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/docinfo_processor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | class DocinfoProcessor < ::Asciidoctor::Extensions::DocinfoProcessor 6 | use_dsl 7 | # at_location :head 8 | 9 | C3JS_DIR_ATTR = 'c3jsdir' 10 | C3JS_DEFAULT_PATH = 'https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/' 11 | 12 | CHARTJS_DIR_ATTR = 'chartjsdir' 13 | CHARTJS_DEFAULT_PATH = 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/' 14 | 15 | CHARTIST_DIR_ATTR = 'chartistdir' 16 | CHARTIST_DEFAULT_PATH = 'https://cdn.jsdelivr.net/npm/chartist@0.11.x/dist/' 17 | 18 | D3JS_DIR_ATTR = 'd3jsdir' 19 | D3JS_DEFAULT_PATH = 'https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/' 20 | 21 | DEFAULT_STYLE = <<~HTML.chomp 22 | 40 | HTML 41 | 42 | def process(doc) 43 | engines = doc.x_chart[:engines] 44 | (engines.map {|engine| send(engine, doc) }.to_a + [DEFAULT_STYLE]).join "\n" 45 | end 46 | 47 | private 48 | 49 | def get_path(doc, attr_name, default_path, asset_to_include) 50 | doc.normalize_web_path asset_to_include, (doc.attr attr_name, default_path), false 51 | end 52 | 53 | def create_script_directive(doc, attr_name, default_path, asset_to_include) 54 | %() 55 | end 56 | 57 | def create_link_css_directive(doc, attr_name, default_path, asset_to_include) 58 | %() 59 | end 60 | 61 | def chartjs(doc) 62 | create_script_directive(doc, CHARTJS_DIR_ATTR, CHARTJS_DEFAULT_PATH, 'chart.min.js') 63 | end 64 | 65 | def chartist(doc) 66 | result = [] 67 | result << create_link_css_directive(doc, CHARTIST_DIR_ATTR, CHARTIST_DEFAULT_PATH, 'chartist.min.css') 68 | result << create_script_directive(doc, CHARTIST_DIR_ATTR, CHARTIST_DEFAULT_PATH, 'chartist.min.js') 69 | result 70 | end 71 | 72 | def c3js(doc) 73 | result = [] 74 | result << create_link_css_directive(doc, C3JS_DIR_ATTR, C3JS_DEFAULT_PATH, 'c3.min.css') 75 | result << create_script_directive(doc, D3JS_DIR_ATTR, D3JS_DEFAULT_PATH, 'd3.min.js') 76 | result << create_script_directive(doc, C3JS_DIR_ATTR, C3JS_DEFAULT_PATH, 'c3.min.js') 77 | result 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/html5_chart_converter_ext.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | module Html5ChartConverterExt 6 | def convert_chart node 7 | chart_engine = node.attr 'engine' 8 | chart_type = node.attr 'type' 9 | chart_converter = Asciidoctor::Chart::Registry.for chart_engine 10 | 11 | if chart_converter 12 | if chart_converter.handles? chart_type 13 | chart_converter.send "convert_#{chart_type}_chart", node 14 | else 15 | logger.warn %(missing chart convert handler for type '#{chart_type}' in #{chart_converter}) 16 | nil 17 | end 18 | else 19 | logger.warn %(missing chart convert for engine '#{chart_engine}') 20 | nil 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/plain_ruby_csv.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | class PlainRubyCSV 6 | def self.parse data 7 | result = [] 8 | data.each_line do |line| 9 | line_chomp = line.chomp 10 | result.push line_chomp.split ',' 11 | end 12 | result 13 | end 14 | 15 | def self.read(filename) 16 | result = [] 17 | (File.open filename).each do |line| 18 | line_chomp = line.chomp 19 | result.push line_chomp.split ',' 20 | end 21 | result 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/plain_ruby_random.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | class PlainRubyRandom 6 | def self.uuid 7 | (0...8).map { (65 + (rand 26)).chr }.join 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/preprocessor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | class Preprocessor < Asciidoctor::Extensions::Preprocessor 6 | def process document, reader 7 | document.extend(ChartBlockTracker) 8 | 9 | reader 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | module Registry 6 | @registry = {} 7 | 8 | def self.register converter, engine 9 | @registry[engine] = converter 10 | end 11 | 12 | def self.for engine 13 | @registry[engine] 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/asciidoctor/chart/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asciidoctor 4 | module Chart 5 | VERSION = '1.0.0' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asciidoctor-chart", 3 | "private": true, 4 | "scripts": { 5 | "convert:examples": "bundle exec asciidoctor -a stylesheet! -a !webfonts -a docinfo=shared -r ./lib/asciidoctor-chart examples/*.adoc", 6 | "cy:run": "npx cypress run", 7 | "cy:open": "npx cypress open", 8 | "cy:visual-regression-update-base": "npm run convert:examples && npx cypress run -b chrome --env type=base --config screenshotsFolder=cypress/snapshots/base", 9 | "precy:visual-regression": "npm run convert:examples", 10 | "cy:visual-regression": "npx cypress run -b chrome --env type=actual", 11 | "test": "npm run cy:visual-regression" 12 | }, 13 | "devDependencies": { 14 | "cypress": "10.0.3", 15 | "cypress-mochawesome-reporter": "^3.1.0", 16 | "cypress-multi-reporters": "^1.6.0", 17 | "cypress-visual-regression": "1.7.0", 18 | "mocha-junit-reporter": "^2.0.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec/block_processor_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'asciidoctor' 4 | require_relative '../lib/asciidoctor-chart' 5 | 6 | describe 'Asciidoctor::Chart::BlockProcessor' do 7 | it 'should convert a chart using the default engine (c3js)' do 8 | input = <<~'ADOC' 9 | [chart#month-stats,line] 10 | .... 11 | January,February,March,April,May,June,July 12 | 28,48,40,19,86,27,90 13 | 65,59,80,81,56,55,40 14 | .... 15 | ADOC 16 | output = Asciidoctor.convert(input, standalone: false) 17 | (expect output.strip).to eql %(
18 |
19 |
20 |
21 |
22 | ) 42 | end 43 | it 'should convert a chart with a title' do 44 | input = <<~'ADOC' 45 | .Sales 46 | [chart#sales] 47 | .... 48 | January,February,March,April,May,June,July 49 | 28,48,40,19,86,27,90 50 | 65,59,80,81,56,55,40 51 | .... 52 | ADOC 53 | output = Asciidoctor.convert(input, standalone: false) 54 | (expect output.strip).to eql %(
55 |
56 |
57 |
58 |
Figure 1. Sales
59 |
60 | ) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/chartjs_converter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'asciidoctor' 4 | require_relative '../lib/asciidoctor-chart' 5 | 6 | describe 'Asciidoctor::Chart::Converter::Html5ChartjsConverter' do 7 | chartjs_converter = Asciidoctor::Chart::Converter::Html5ChartjsConverter.new 8 | doc = Asciidoctor.load('') 9 | data = [ 10 | %w[January February March April May June July], 11 | [28, 48, 40, 19, 86, 27, 90], 12 | [65, 59, 80, 81, 56, 55, 40] 13 | ] 14 | it 'should use px unit when no unit is defined' do 15 | node = Asciidoctor::Chart::ChartBlock.new doc, data, { 16 | 'width' => '600', 17 | 'height' => '400' 18 | } 19 | html = chartjs_converter.convert_line_chart node 20 | (expect html).to include %(
) 21 | end 22 | it 'should preserve units when defined on width or height' do 23 | node = Asciidoctor::Chart::ChartBlock.new doc, data, { 24 | 'width' => '80%', 25 | 'height' => '20vh' 26 | } 27 | html = chartjs_converter.convert_line_chart node 28 | (expect html).to include %(
) 29 | end 30 | it 'should disable maintain aspect ratio when width or height is defined' do 31 | node = Asciidoctor::Chart::ChartBlock.new doc, data, { 'width' => '600' } 32 | html = chartjs_converter.convert_line_chart node 33 | (expect html).to include 'maintainAspectRatio: false' 34 | 35 | node = Asciidoctor::Chart::ChartBlock.new doc, data, { 'width' => '400' } 36 | html = chartjs_converter.convert_line_chart node 37 | (expect html).to include 'maintainAspectRatio: false' 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/docinfo_processor_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'asciidoctor' 4 | require_relative 'spec_helper' 5 | require_relative '../lib/asciidoctor-chart' 6 | 7 | describe 'Asciidoctor::Chart::DocinfoProcessor' do 8 | it 'should append only the required styles and scripts (C3.js)' do 9 | input = <<~'ADOC' 10 | [chart#month-stats,line] 11 | .... 12 | January,February,March,April,May,June,July 13 | 28,48,40,19,86,27,90 14 | 65,59,80,81,56,55,40 15 | .... 16 | ADOC 17 | output = Asciidoctor.convert(input, standalone: true) 18 | (expect output.strip).to include %( 19 | 20 | 21 | ) 39 | (expect output.strip).not_to include %( 56 | ) 74 | (expect output.strip).not_to include %( 90 | ) 108 | (expect output.strip).not_to include %( 127 | 128 | 129 | 130 | ) 148 | (expect output.strip).not_to include %( 167 | 168 | ) 186 | (expect output.strip).not_to include %( 205 | ) 223 | (expect output.strip).not_to include %() 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /tasks/bundler.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'bundler/gem_tasks' 5 | $default_tasks << :build # rubocop:disable Style/GlobalVars 6 | rescue LoadError 7 | warn $ERROR_INFO.message 8 | end 9 | -------------------------------------------------------------------------------- /tasks/clean.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rake/clean' 4 | -------------------------------------------------------------------------------- /tasks/rspec.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'rspec/core/rake_task' 5 | RSpec::Core::RakeTask.new :spec do |t| 6 | t.verbose = true 7 | opts = %w[-f progress] 8 | opts.append '-t', '~visual', '-t', '~cli' if ENV['UNIT'] 9 | opts.unshift '-w' if $VERBOSE || ENV['COVERAGE'] 10 | t.rspec_opts = opts 11 | end 12 | rescue LoadError 13 | warn $ERROR_INFO.message 14 | end 15 | -------------------------------------------------------------------------------- /tasks/rubocop.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | begin 4 | require 'rubocop/rake_task' 5 | RuboCop::RakeTask.new :lint do |t| 6 | t.patterns = Dir['lib/**/*.rb'] + %w[Rakefile Gemfile tasks/*.rake spec/**/*.rb asciidoctor-chart.gemspec] 7 | end 8 | rescue LoadError => e 9 | task :lint do 10 | raise 'Failed to load lint task. 11 | Install required gems using: bundle --path=.bundle/gems 12 | Then, invoke Rake using: bundle exec rake', cause: e 13 | end 14 | end 15 | --------------------------------------------------------------------------------