npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@preschen/gen-vis

v2.0.0

Published

A declarative d3 visualisation library

Readme

gen-vis

Examples: five charts of different looks, their definitions and CSS can be edited on the page, the charts follow as you type.

A declarative d3 visualisation library: a chart is described by a JSON definition (the def) and a data file (CSV, TSV, JSON or parquet). Line, point, bar, stacked bar and area charts, polar plots (e.g. radar and rose charts) and maps with legends, hover, facets and form elements are supported.

Usage

npm install @preschen/gen-vis vue

Standalone script

The standalone script includes Vue, d3 and the styles, for pages without a bundler, e.g. of a CDN. As a module of the page (dist-lib/esm/gen-vis.js) it loads 90 kB (gzipped) at first and the parts which not every chart needs when a chart needs them, e.g. the ones of maps, from the files next to it:

<div class="genVis" data-def-file="/data/bev/def.json"></div>

<script type="module">
import { mountGenVisByClass } from 'https://cdn.jsdelivr.net/npm/@preschen/gen-vis@2/dist-lib/esm/gen-vis.js';
mountGenVisByClass('genVis');
</script>

As a single file (dist-lib/gen-vis.js, 415 kB, 141 kB gzipped) it has all parts in itself and provides the functions as globals, e.g. of a page which loads its scripts without modules:

<script src="https://cdn.jsdelivr.net/npm/@preschen/gen-vis@2/dist-lib/gen-vis.js"></script>
<script>mountGenVisByClass('genVis')</script>

npm run lib builds both in dist-lib/, the single file also as gen-vis-<version>.js, e.g. for an upload to a server of its own.

mountGenVisByClass(className) mounts all elements with the class which are not mounted yet and returns their components, mountGenVisElement(element, props) mounts a single element (again, if it is mounted) and returns its component, e.g. for its methods, both set the class vis-mounted of the element. unmountGenVisElement(element) removes the visualisation, e.g. before a page of a single page application removes the element. The props are taken from the data- attributes, see below, and from props, e.g. the events:

GenVis.mountGenVisElement(element, { onSelect: e => location.href = `/region/${e.key}` });

Vue component

import GenVis from '@preschen/gen-vis';

app.use(GenVis);
<GenVis def-file="/data/bev/def.json"/>
<GenVis :def="def" :data="rows"/>

import { GenVis } from '@preschen/gen-vis' registers the component locally instead. vue is a peer dependency, the package uses the Vue of the application. mountGenVisElement, mountGenVisByClass and unmountGenVisElement are exported as well. The package has the types of TypeScript, e.g. of the props, the events and the methods.

Parts which not every chart needs are loaded when a chart needs them, so a bundler of the application puts them into chunks of their own, the rest is ~65 kB gzipped (without Vue). The sizes are gzipped: the check of the definitions (~5 kB) with the first one, the colors of d3 (d3-scale-chromatic, ~8 kB) for definitions which name a scheme or an interpolator, the parts of maps (d3-geo, topojson-client, ~13 kB) with the first map, the parts of polar plots (~5 kB) with the first one, the transitions (d3-transition, ~5 kB) for definitions with a transition, the transforms (~2 kB), the legend of scales of colors (~2 kB), the languages of Intl (~1 kB) other than de and en, the parquet reader (~17 kB) for parquet data and the PNG and the CSV (~11 kB) with the first one.

Props

| Prop | Attribute | Description | |------------|-----------------|-------------| | defFile | data-def-file | url of the definition | | def | data-def | the definition, an object or a JSON string | | data | data-data | the data as rows, a CSV/JSON string or parquet (an ArrayBuffer or Uint8Array), if not given it is loaded from the data url of the definition | | debug | data-debug | shows the prepared definition | | state | data-state | the changes of the user, see state, the attribute is JSON | | download | data-download | a button at the right of the footer to save it as a PNG, a string is the name of the file (default the title) | | copy | data-copy | a button at the right of the footer to copy the PNG to the clipboard (only on https or localhost) | | csv | data-csv | a button at the right of the footer to save the data shown as CSV, a string is the name of the file (default the title) | | imageWidth | data-image-width | the width of the PNG in pixels, the one of the definition otherwise or 1200, screen for the one on the screen (as it is seen, e.g. on a phone) |

The attributes are strings: data-download, data-csv, data-copy and data-debug without a value or "true" are on, "false" is off, another value of data-download or data-csv is the name of the file.

Every visualisation has its own state, several of them can be used on a page. If def, defFile or data change, the visualisation is loaded again, a new state is applied without loading it again. Errors are shown in place of the visualisation, e.g. of a definition which can not be drawn.

Events and slots

Besides update:state (see state) the component emits rendered once it is drawn the first time, error with the message of an error and hover and select (a click, a tap or enter and space of the hover by the keyboard) with the rows under the mouse, hover with null at its end:

{ key: 2023, title: "2023", rows: [{ x: 2023, y: 4, land: "Wien" }, ...], nearest: { x: 2023, y: 4, land: "Wien" } }

key is the value of the position, e.g. of the horizontal axis or the key of the region of a map, title the title of the hover, the rows are the values of the mappings, nearest the row of the element under the mouse.

The slots replace parts of the visualisation, hover the content of the hover (it also has entries, the formatted values of the default table), header the title and the subtitle and footer the footer. buttons adds buttons of the page at the right of the footer, before the ones of copy, csv and download, e.g. own icons, with save(), copy() and canCopy of the PNG and csv() of the data:

<GenVis def-file="/data/bev/def.json" @select="open">
    <template #hover="{ title, nearest }">
        <strong>{{ title }}</strong> {{ nearest?.land }}: {{ nearest?.y }}
    </template>
    <template #footer="{ footer }"><small v-html="footer"/></template>
    <template #buttons="{ save }"><button @click="save">PNG</button></template>
</GenVis>

Without the slot, e.g. with the standalone script, hover of the options are templates of HTML of the hover: title, with {title}, the default one, e.g. the date, above the table or the rows, and row, a line per row instead of the table, with {land} (the name of the category of the mapping land), {land.unit} (another hover prop), {y} (the formatted value) and {y.value} (the value). Both have the globals, the values are text, e.g. < of the data is shown as it is:

"options": { "hover": { "title": "<b>{title}</b>", "row": "{land}: {y} <small>{land.unit}</small>" } }

"mode": "point" of the hover of the options shows the row of the point nearest to the mouse (within radius pixels, 30 by default) instead of the rows of the nearest position of the axis, e.g. of a scatter plot, in cartesian and polar plots.

PNG

The image is a copy of the visualisation with the same state, drawn outside of the screen in the width of imageWidth, so it is the same on every screen, e.g. the facets have the columns of this width, or with screen in the width it has on the screen, e.g. the layout of a phone. It is twice the size, narrow ones more (at least 1200 pixels wide, e.g. four times of 360), has no form elements, their selection is a line below the subtitle, e.g. Einheit: Anteil · Jahr: 2024, the legends only have the entries shown and the legend of the facets is left out, their titles name them. The titles of the buttons are in the language of options.locale, see options.

Besides the buttons of download and copy, the methods of the component are exportPng(name) (saves the file), copyPng() (copies it, in the click of the user) and image() (the PNG as a Blob), e.g. for a button of the page:

<GenVis ref="chart" def-file="/data/bev/def.json"/>
<button @click="$refs.chart.exportPng('bev')">PNG</button>

Layout shifts

The height of a visualisation is known when it is drawn, so while it is loaded it has the space of the css variable --gen-vis-loading-height (0 by default, the root has the class vis-loading), e.g. of a typical one or per chart, so the page does not move when it is drawn. A visualisation which was drawn before on the page (with the same definition and width), e.g. after a navigation of the page, has the height it had then. A definition which is changed is drawn when it is loaded, until then the one before is shown.

.vis-outer { --gen-vis-loading-height: 450px; }

With the standalone script the page is shown before the script mounts the visualisations, so the space of the empty elements is set by the page:

.genVis { --gen-vis-loading-height: 450px; }
.genVis:empty { min-height: var(--gen-vis-loading-height); }
<div class="genVis" data-def-file="/data/map.json" style="--gen-vis-loading-height: 720px"></div>

Accessibility and the data

Every facet is named by the title and the name of the facet, the entries of the legends are checkboxes of the keyboard, the titles of the form elements are their labels. A facet with a hover is in the order of the tab key: the arrows to the left and the right move the hover to the position before and after (of a map the regions by their names, of the point mode the points from the left), the ones up and down to the row above and below, home and end to the first and the last position, enter and space select it (the event select, as a click), escape hides it. For screen readers it is an application (role="application", aria-roledescription of the chart of the locale), so they pass the keys to it, its description names the keys (keys of the locale), and they read the hover when it changes (aria-live). A facet without a hover is an image (role="img").

The data shown can be saved as CSV: the button of csv or the method exportCsv(name) save the rows shown (of the visible categories and the filter) in the columns of the mappings, with the values as they are in the data.

State

The state holds what the user changed compared to the definition: the globals of the form elements and the entries of the legends shown or hidden.

{ "globals": { "column": "share" }, "visible": { "year": { "2019": true, "2022": false } } }

The component emits update:state on every change of the user, so with v-model:state the page can keep it, e.g. in the local storage. A given state is applied when the visualisation is loaded, a new one (null for the definition) is applied without loading it again. Globals, values and entries the definition does not have (anymore) are ignored, new entries of the definition keep their defaults.

<GenVis def-file="/data/bev/def.json" v-model:state="state"/>

Styles

The styles of the package are in the cascade layer gen-vis, so every style of the page overrides them, regardless of its specificity and of the order of the styles, e.g.:

.vis-form-element { display: block; margin: 6px 4px; }
.vis-subtitle { font-size: 15px; }

The charts fill the width of .vis inside its padding and border, so the page can style it as a card (vis-outer is the root of the component):

.vis { padding: 20px; border-radius: 12px; background: white; }

All classes of the package start with vis-, so styles of the page for other elements do not apply to them, e.g. .title of a CSS framework, the others are the id of a plot, e.g. plot-0, and the classes of the axes of d3, tick and domain:

| Part | Classes | |------|---------| | the visualisation | vis-mounted (an element mounted by mountGenVisByClass or mountGenVisElement), vis-outer (the root, vis-loading while it is loaded), vis, vis-header (vis-title, vis-subtitle, in the PNG vis-selection), vis-body, vis-error | | form elements | vis-form-elements, vis-form-element (vis-form-element-title, vis-radio, vis-slider, vis-slider-value), the id of a form element and of its entries as data-id, e.g. .vis-form-element[data-id="scale"] | | legends | vis-legends, vis-legend (vis-legend-title, vis-legend-entries, vis-legend-entry, the mapping as data-dim, an entry with data-key and data-visible), vis-color-legend (vis-color-scale, vis-legend-missing, vis-swatch) | | facets | vis-facet (a facet), vis-facet-title, vis-svg | | plots | vis-plot (with the id of the plot and its data-plot, vis-below or vis-above of its layer), vis-group (the elements of a group of a plot with an element per row), vis-highlight (the highlighted elements), vis-features (geo:base), the keys of the features of maps as data-geo-key | | axes | vis-axis and vis-axis-bottom, -top, -left, -right, -angular or -radial (the mapping as data-mapping), vis-axis-title, vis-grid | | annotations | vis-annotations (the plot), vis-annotation and vis-band, vis-line, vis-text or vis-circle, vis-annotation-label, vis-annotation-labels | | hover | vis-hover (vis-hover-title, vis-hover-entries, vis-hover-entry, vis-hover-row of a template, vis-nearest, the cells of the values vis-value, the mapping of a cell as data-mapping, the hover prop of a category other than its name as data-prop), vis-hover-marker, vis-events | | footer | vis-footer (vis-footer-content, vis-buttons, the buttons vis-copy, vis-csv and vis-download) |

Styles of the page which are in a cascade layer themselves only override the package if their layer is declared after it, e.g. @layer gen-vis, page;.

The font and the colors of the package are css variables, e.g. for a dark page (the default in brackets):

.vis {
    --gen-vis-font-family: Arial, sans-serif;  /* Century Gothic */
    --gen-vis-grid-color: #444;                /* #CCC, the grid lines */
    --gen-vis-hover-background: #222D;         /* #FFFFFFCC */
    --gen-vis-halo-color: #111;                /* white, behind labels on the plots */
    --gen-vis-background: #111;                /* #FFF, of the PNG */
    color: #EEE;                               /* the texts, axes and ticks */
}

The others are --gen-vis-marker-color (#AAA, the line of the hover), --gen-vis-band-color (#EEE), --gen-vis-line-color (#999), --gen-vis-annotation-color (#444 of texts, #555 of labels, #666 of circles), --gen-vis-missing-color (#EEE, the missing values of a legend), --gen-vis-button-color (#BBB), --gen-vis-button-hover-color (#777), --gen-vis-focus-color (#1E4F77, the entries of legends and the facets) and --gen-vis-error-color (#B00). The colors of annotations are only the ones without a color of their props.

Definition

{
    "$schema": "https://unpkg.com/@preschen/gen-vis@2/schema.json",
    "parent": "../shared/def.json",
    "data": "data.csv",
    "dataFormat": "csv",
    "transform": [ ... ],
    "options": { ... },
    "globals": { ... },
    "filter": { ... },
    "mapping": { ... },
    "plot": [ ... ],
    "annotations": [ ... ],
    "facets": { ... },
    "formElements": [ ... ],
    "geo": { ... }
}

See data (also dataFormat), transform, options, mapping, plot, annotations, facets, formElements, globals and filter and maps (geo).

Relative urls are resolved against the definition referencing them, e.g. data.csv is next to the definition file, the data of a parent against the loaded definition. A definition is deep merged into its parent (and its parent into its own parent), arrays are replaced, not merged, except arrays whose entries all have an id, e.g. the form elements and their values, these are merged by the ids, entries with a new id are appended. The merged definition is checked for common mistakes, e.g. unknown plot types, the findings are logged as warnings in the console, a definition which can not be drawn, e.g. a mapping which is no object, is an error which names the part. Only the footer and the templates of the hover (options.hover) are HTML, e.g. the link of a source, so a definition is trusted, texts of users, e.g. of a form of a page, must not be part of it, the values of the globals and of the data in them are text. All other texts, e.g. the title, the names of the legends and the hover and the titles of the axes, are text, < is shown as it is.

parent can also be a list of definitions (mixins), merged in their order, later ones override earlier ones, e.g. a chart type and a form element:

{
    "parent": ["../shared/years.json", "../shared/scale-switch.json"],
    "data": "data.csv"
}

A parent shared by several mixins is merged once, before the first mixin using it, so it does not override the mixins in between.

The charts of a page share their requests of the last 5 minutes, e.g. a parent used by all of them or a csv of two charts is loaded once, later ones load again, e.g. for updated data. The parents of a definition and its data are requested at once.

The package contains a JSON Schema of the definitions, schema.json. With "$schema" in a definition, editors like VS Code complete and check it:

{
    "$schema": "https://unpkg.com/@preschen/gen-vis@2/schema.json",
    ...
}

or with a local path, e.g. "./node_modules/@preschen/gen-vis/schema.json". The schema also allows parts of definitions, e.g. definitions with a parent.

Data

data is CSV, TSV, JSON (a list of rows) or parquet, by the extension of the url (.csv, .tsv, .json, .parquet), without one CSV or JSON by the content. dataFormat sets it, e.g. for an url of an api, also the one of a parent, data which can not be read is an error of its url:

{ "data": "/api/prices?year=2024", "dataFormat": "json" }

Parquet is read with hyparquet (no wasm), it is only loaded for parquet data. Only the columns the definition can use are read: the ones of the mappings (also of the patches of form elements, a column template in every value of its globals), of the transforms and of the values of form elements, so a wide file costs only its columns which are used. Integers of 64 bits are numbers, unless they are too large for them, e.g. ids, then they are strings, timestamps are the ones of the other formats, dates (without a time) and timestamps without a time zone (e.g. of pandas, polars and duckdb) are strings as the ones of csv, e.g. 2024-06-01 and 2024-06-01T13:00:00.000, see type of mapping. Snappy, the default of arrow (R, Python), pandas and duckdb, is read directly, other compressions, e.g. zstd of polars, load the decompressors of hyparquet-compressors (~75 kB gzipped), the standalone script reads only snappy and uncompressed parquet. Keys of categories are compared as strings, e.g. the years of a parquet or JSON column as keys of props.

transform

Transforms compute columns of the data in the browser, e.g. an index of a base year which a slider changes, instead of columns of every base year in the file. A transform (or a list of them, in their order) computes the column as (by default its column, which it replaces) of the values of column, in groups of the rows of the same values of the columns by (a column or a list), the mappings and the later transforms can use the new column:

"globals": { "base": "2019" },
"transform": [
    { "type": "year", "column": "date", "as": "year" },
    { "type": "index", "column": "value", "as": "index", "by": "type", "base": { "year": "{base}" } }
],
"formElements": [{ "id": "base", "name": "Basisjahr", "ref": "base", "type": "slider", "values": { "column": "year" } }],
"mapping": { "y": { "column": "index", "type": "numeric", ... } }
  • year: the year of a date, e.g. 2024 of 2024-06-01.
  • align: the same day of a date in the year year (2020 by default, a leap year), e.g. to compare the years of daily data on one axis, with year as the categories.
  • The dates of year and align are the ones of the time zone of the scale of the column (column or as), local or UTC of a utc scale.
  • index: the values relative to the mean of the values of the rows of base in the group, times scale (100 by default). base is a column and its value, e.g. {"year": "{base}"}, or a range [from, to] (null is open), e.g. of dates {"date": ["2019-01-01", "2019-12-31"]}.
  • share: the share of the value of the sum of the values of the group, e.g. "by": "date" for the shares of the categories of a date.
  • rolling: the mean of the values of size rows of the group, the ones before the row ("align": "end", the default) or around it ("center"), only of whole windows, missing values are left out, in the order of the column order, e.g. {"type": "rolling", "column": "value", "as": "mean28", "by": "country", "order": "date", "size": 28}.
  • cumulative: the sum of the values of the group up to the row, in the order of order, e.g. the sum of a year so far with "by": "year".

The strings of a transform are templates of the globals, e.g. {base}, the transforms are computed again when the globals change, from the first one which they change. The CSV of the data has the computed values. See data/bev/def-index.json.

options

title, subtitle, footer (HTML), width (the width of the container if not given), height (of a facet, 400 by default) and margins ({"top", "right", "bottom", "left"} in pixels). height can be a prop based on totalWidth, see props. {name} in title, subtitle and footer is replaced by the value of the global, e.g. "Durchschnitt {base} = 100" of a form element of the base year, also in the image and the name of its file, see templates.

locale sets the number and date formats of the axes and the hover, also of axes without format: de (the default) or en, a language of the browser (Intl), e.g. fr, it or de-CH, or an object with a base locale and the parts which are changed, see d3's formatLocale and timeFormatLocale:

"locale": { "base": "de", "number": { "currency": ["", " EUR"] } }

A language of Intl has its separators of numbers, the formats of dates and times and the names of the days and months (of the gregorian calendar) of the browser, the currency is the euro and the texts are in English (German for German languages). timeTicks of the object are the tick formats of time axes without format, by the interval of the date (millisecond, second, minute, hour, day, week, month, year), texts the titles of the buttons (download, copy, copied and csv) and the texts of screen readers (chart, the kind of a facet with a hover, and keys, the description of its keys).

coord is the coordinate system of the plots, cartesian (the default, a horizontal and a vertical axis), polar (an angle and a radius, see polar plots), geo (a map, see maps) or a registered one, see extensions.

transition moves the elements to their new values in this many milliseconds (true for 300) after a change of a legend, a form element or the state, e.g. "transition": 400: the points, bars and lines of the draw before and the axes of cartesian plots move, new elements are drawn at once, a new width is drawn at once, nothing moves if the user prefers less motion (prefers-reduced-motion of the system).

fontFamily sets the font, by default the css variable --gen-vis-font-family or Century Gothic, so the font of all visualisations of a page can be set with css (see styles for the colors):

.vis { --gen-vis-font-family: Arial, sans-serif; }

mapping

Maps columns of the data to named dimensions, the plots and all other parts of the definition refer to these names. null removes a part of a mapping of a parent or of a patch, e.g. "hover": null.

"y": {
    "column": "value",
    "type": "numeric",
    "stacked": false,
    "scale": { "type": "linear", "orientation": "vertical", "domain": [0, null] },
    "axis": { "position": "left", "ticks": 8, "format": ".1f", "grid": true, "title": { "name": "€/l", "offset": 50 } },
    "hover": { "format": ".2f" }
}
  • type: numeric, date or categorical. Numeric and date values which are missing, invalid or infinite are null: they are gaps in lines and areas, points and bars are not drawn, and they are not shown in the hover. Dates are timestamps or strings. A date without a time zone, e.g. 2024-06-01, 2024-06, 2024, 2024-06-01 13:00 (also with a T, seconds and their fraction, 24:00 is the end of the day) or 01.06.2024 (the day first, also with a time), is the one of the time zone of the scale, local of a time scale (and the other ones) and UTC of a utc scale, so it is the same day and time in every time zone (Date.parse reads a date without a time as midnight UTC, the day before west of UTC in local time, and 01.06.2024 as the 6th of January), the others are read by Date.parse, e.g. 2024-06-01T13:00:00Z.

  • scale: type is a d3 scale (linear, time, log, point, band, ...), continuous scales need a numeric or date type (positions of dates a time or utc one), point and band a categorical one, ordinal scales are of any values, e.g. the colors of categories (range). orientation (horizontal or vertical) places the scale on the plot, angular or radial in polar plots. domain fixes the domain, null entries are taken from the data, the values are converted as the ones of the data, e.g. dates or numbers of strings, entries can be references, e.g. [0, "@max"] of a global, the domain of a point or band scale are its categories in their order. domainRel (relative to the positions, e.g. of a log or a sqrt scale) and domainAbs (absolute) extend it, by default a domain of a position taken from the data is extended by 2%. nice rounds the ends taken from the data of a numeric mapping, e.g. 0.951 to 1, of a log scale to powers of 10, true for steps of about a tenth of the domain or the number of steps, e.g. so the legend of colors ends at 100%. padding for categorical scales. inset is the space at the start and the end of the range in pixels (of positions), a number for both or [start, end], e.g. 10 so the first and the last point are not at the edges, of every type of scale, also of dates, e.g. instead of a domainAbs of milliseconds. A scale without orientation has the range given, e.g. colors or the radius of points.

  • Scales of colors: sequential and diverging scales (a domain with a middle entry, e.g. [null, 0, null]) have an interpolator of d3, e.g. Blues, Viridis or RdYlGn, sequentialQuantile the colors of the quantiles of the values. quantize, quantile and threshold scales (the domain are the values between the classes) are classes of colors, the range or a scheme of d3, e.g. Blues, with classes colors, ordinal scales the colors of categories. Scales without orientation are the same in all facets.

  • name: the title of the legend.

  • axis: position (top, bottom, left, right, in polar plots angular and radial), ticks, tickSpacing (without ticks a tick per this many pixels of the axis, e.g. 80, so a narrow chart has fewer ticks, instead of a relative prop of ticks), values (fixed ticks, the ones outside of the domain are left out), format (d3 number or time format), rotate (the angle of the labels in degrees, positive counterclockwise, negative clockwise), grid (lines at the ticks), title ({"name", "offset"}, the distance from the axis, by default 8 pixels inside the edge of the margin, of a radial axis 16 pixels outside of the circle) and padding.

  • hover: the hover shows the values of the vertical axis at the position of the mouse, of horizontal bars (a horizontal mapping is stacked, or the vertical one is categorical and the horizontal one is not) the values of the horizontal axis at the vertical position. Of several mappings of an orientation, e.g. a second vertical axis, the stacked one or the one with a hover (or the first one) has the values of the hover, the other numeric ones with a hover are further columns. On touch devices it is shown by a tap and stays until a tap outside of the plot, horizontal swipes move it, vertical ones scroll the page. format of the horizontal and vertical axis, it defaults to the axis format, without one dates are the date of the locale and numbers have its decimal mark and up to 6 decimals (without trailing zeros), thousands separators only if the domain reaches 10 000, so years are e.g. 2024. For categorical mappings props are the columns of the entries, the name of the category first, e.g. {"unit": "@unit"} adds a column of the prop unit of the categories, "name": null leaves out the name.

  • props: the categories and their props, e.g. colors. common props are used for all categories, name and visible are set by default. Only visible categories are shown. The order of the categories is the order of the legend, the facets and the stacks, not the order of the rows. Keys which are integers, e.g. years, are ordered ascending by JavaScript, first. order sets the order: "ascending", "descending" (numbers by their value, e.g. the newest year first) or a list of keys, which are first, the others after them. A patch of the props by a form element, e.g. other colors, replaces them, the entries of the legend keep their state. ranks are the props of the categories by their place in the order, of the first one, the second one, ..., the last entry of all others, e.g. the newest year is red, whichever year it is, the props of a category override them, they override common:

    "year": { "column": "year", "props": { "fromData": true, "order": "descending", "common": { "stroke-width": 2 },
        "ranks": [{ "color": "#C3423F", "opacity": 1 }, { "color": "#CF8E02", "opacity": 0.7 }, { "color": "#BBB", "opacity": 0.3 }] } }

    With "fromData": true the values of the column which are not listed are categories as well, after the listed ones, in ascending order (numbers by their value), e.g. new regions of the data, also of the column of a form element, e.g. of a template {col}. scheme sets the prop color of the categories in their order, a d3 scheme, e.g. Tableau10 or Blues (of the number of categories), a color of the props is kept:

    "land": { "column": "Bundesland", "props": { "fromData": true, "scheme": "Tableau10", "categories": { "ÖSTERREICH": { "name": "Gesamt", "color": "#000" } } } }
  • legend: a toggle for every category, symbol draws svg elements (with props) of the given size before the name, or is a shortcut in the color of the category (@color): "line" (with the stroke-width, stroke-dasharray and opacity of the categories if they have them, a width of 2 otherwise), "rect" or "circle" (with their opacity), the object {"type": "line", "size": 20, "props": {"stroke-width": 3}} sets the size (16 by default) and props of the element. A click shows or hides the category, a double click shows only it, the next double click all of them. The entries are checkboxes of the keyboard (tab, enter or space), the mouse and the focus highlight their category. A mapping with a scale but without props has the colors of its scale as legend, a gradient or the classes, the ticks of a gradient at the positions of their colors, e.g. of a sqrt scale steps of 1, 2 and 5 with more space for the small values, format of the values, by default the one of the hover, missing an entry beside them, e.g. of the regions of a map without a value ({"name": "keine Daten", "color": "#DADADA"}, the color of geo:base).

  • stacked: stacks the values of the axis of the values, a vertical, a horizontal (of horizontal bars) or a radial one, the first category is at the bottom. @y:start and @y:end are the start and the end of the stacked value, @y:start:scaled and @y:end:scaled their positions, e.g. y0 and y1 of cartesian:bar, @y:height:scaled the difference, e.g. the height of an svg:rect.

plot

A plot or a list of plots, drawn in order:

{
    "type": "cartesian:line",
    "categories": ["nuts"],
    "props": {
        "stroke": "@color",
        "fill": "none",
        "d": { "x": "@x:scaled", "y": "@y:scaled" },
        "highlight-stroke-width": 3
    }
}
  • type: the types are named by their coordinate system:
    • svg:circle, svg:rect, svg:line, svg:text: an svg element per row in any coordinate system, the props are its attributes, e.g. "cx": "@x:scaled"
    • cartesian:line: a line per group, d with x and y
    • cartesian:area: an area per group, d with x, y0 and y1
    • cartesian:bar: a bar per row from y0 to y1, by default from the position of 0, e.g. "y1": "@y:scaled", or a stacked value with "y0": "@y:start:scaled", "y1": "@y:end:scaled". The bars are centered at x, in the middle of a band, width defaults to the width of a band or the step of a point scale, a continuous scale needs a width. With x1 the bars are horizontal, from x0 (by default 0) to x1 at y, height is their thickness, e.g. of the countries of a band scale of y.
    • polar:* of polar plots and geo:* of maps
  • categories: the rows are grouped by this mapping or these mappings, the props of their categories are available in the plot props. Mappings without props only group the rows, e.g. a line per id.
  • id: the class and the data-plot of the group of the plot, plot-<index> by default, the plots of a parent are merged by their ids (if all of them have one).
  • props: svg attributes (and text). Props starting with highlight- are used for the elements of the category under the mouse or the legend entry.
  • highlight: group (default) highlights the elements of the category under the mouse, row only the element of its row, e.g. the segment of a stacked bar, an entry of the legend highlights all of its category.
  • curve: the interpolation of the paths and areas between their points, linear (default), monotoneX, natural, catmullRom, basis, step, stepBefore or stepAfter. monotoneX is smooth without overshooting the values, e.g. for monthly data, basis does not pass through the points. linearClosed, catmullRomClosed and basisClosed connect the last point with the first one, e.g. of radar charts.
  • data: rows of the plot instead of the rows of the data, e.g. events or a target value, see below.
  • select: one row of every group, of the rows with values of the mappings of the props: first or last (in the order of the rows) or the one with the least or the most value of a mapping, { "min": "y" } or { "max": "x" }, e.g. labels at the ends of lines.
  • dodge (svg:text): the texts are moved apart vertically to at least this distance in pixels, e.g. labels at the ends of lines.
  • layer: below draws the plot below the axes and grid lines, e.g. a background, above above the other plots, e.g. labels, by default the plots are drawn between them in their order.
  • facet: a key or a list of keys of the facets of the plot, by default it is in all facets.

Labels of values, of the bars of a band scale at their center, with the format of the hover of the mapping (@x:formatted), see data/bev/def-hbar.json:

{
    "type": "svg:text",
    "props": { "x": "@x:scaled", "dx": 4, "y": "@y:scaled:center", "dominant-baseline": "middle", "text": "@x:formatted" }
}

Labels at the ends of lines, in the color of the line:

{
    "type": "svg:text",
    "categories": ["nuts"],
    "select": { "max": "x" },
    "dodge": 12,
    "props": { "x": "@x:scaled", "y": "@y:scaled", "dx": 4, "text": "@name", "fill": "@color" }
}

The rows of data have the names of the mappings and others, e.g. a label, all of them are names of the props. The values of the mappings are converted as the ones of the data, e.g. dates, and the values of the rows are props, e.g. references to globals. A row with a value of the mapping of the facets is only in its facet. data can also be the url of a file of rows (dataFormat as the one of the definition), relative to the definition, its values are not props. A text at a value of the vertical axis, at the end of the horizontal one:

{
    "type": "svg:text",
    "data": [{ "y": "@target", "label": "Ziel" }],
    "props": { "x": "@x:scaled:max", "y": "@y:scaled", "dy": -4, "text-anchor": "end", "text": "@label" }
}

Polar plots

With "coord": "polar" the plots are in a circle in the center of the facet, the mapping with the angular orientation goes around it, clockwise from the top, the one with the radial orientation from the center outwards, the margins are the space of the labels:

"options": { "coord": "polar", "height": 400, "margins": {"top": 30, "right": 60, "bottom": 30, "left": 60} },
"transform": [{ "type": "year", "column": "date", "as": "year" }, { "type": "align", "column": "date" }],
"mapping": {
    "x": {
        "column": "date", "type": "date",
        "scale": { "type": "utc", "orientation": "angular", "domain": ["2020-01-01", "2021-01-01"] },
        "axis": { "position": "angular", "ticks": 12, "format": "%b", "grid": true }
    },
    "y": {
        "column": "value", "type": "numeric",
        "scale": { "orientation": "radial", "domain": [0, null] },
        "axis": { "position": "radial", "ticks": 4, "grid": true }
    },
    "year": { "column": "year", "type": "categorical", "legend": {}, "props": { "fromData": true, "scheme": "Tableau10" } }
},
"plot": { "type": "polar:line", "categories": ["year"], "props": { "stroke": "@color", "fill": "none", "d": { "angle": "@x:scaled", "radius": "@y:scaled" } } }

The years of daily data, the dates aligned to 2020, a line per year.

  • The angle is a cycle, the end of its domain is at the angle of its start, e.g. a year from 2020-01-01 to 2021-01-01, a domain taken from the data is not extended. The categories of point and band scales have the same distance, also the last and the first one.
  • The range of the radius is [0, "@radius"] by default, the half of the smaller side of the facet, e.g. [20, "@radius"] leaves a hole in the center.
  • angular axes are around the circle, grid draws lines from the center. radial axes go from the center outwards, at the angle of the axis (degrees, clockwise from the top, 0 by default), grid draws circles, or polygons through the ticks of the angle with "gridShape": "polygon", e.g. of radar charts. The labels of radial axes are above the plots.
  • The plot types: polar:line (a line per group, d with angle and radius), polar:area (an area per group, d with angle, innerRadius and outerRadius), polar:arc (a segment of a ring per row, props angle, innerRadius and outerRadius, optional width in radians, padAngle and cornerRadius, e.g. the bars of a rose chart), and polar:circle and polar:text (an element per row at angle and radius). The angles of a band scale are in the center of the band. The svg: types draw with the center as origin, the cartesian: types are cartesian only.
  • Stacked values (stacked of the radius) are stacked from the center, e.g. "innerRadius": "@y:start:scaled", "outerRadius": "@y:end:scaled" of polar:arc.
  • The hover shows the values of the angle nearest to the mouse, also across the top, it is in the center, on the other side of the marker.

See data/bev/def-radar.json and data/rechtsform/def-rose.json.

Maps

With "coord": "geo" the plots are a map, geo is its geometry and the mapping (join) whose values are the keys of the features:

"options": { "coord": "geo", "height": 480 },
"geo": {
    "data": "../geo/europe.json",
    "key": "id",
    "join": "country",
    "fit": "data",
    "projection": { "type": "azimuthalEqualArea", "rotate": [-10, -52] }
},
"globals": { "type": "euroSuper95" },
"filter": { "type": "@type" },
"mapping": {
    "country": { "column": "country", "type": "categorical" },
    "type": { "column": "variable", "type": "categorical" },
    "price": {
        "name": "€/Liter", "column": "value", "type": "numeric",
        "scale": { "type": "sequential", "interpolator": "YlOrRd" },
        "legend": { "format": ",.1f" }, "hover": { "format": "$,.3f" }
    }
},
"plot": [
    { "type": "geo:base", "props": { "fill": "#EEE", "stroke": "white" } },
    { "type": "geo:region", "categories": ["country"], "props": { "fill": "@price:scaled", "highlight-stroke": "#333" } }
]
  • geo.data is the url of GeoJSON or TopoJSON (or the GeoJSON or TopoJSON itself), object the object of TopoJSON (the first one by default), key the key of the features (id, the default, or a property, e.g. NUTS_ID) and name the property of their names (name by default). Polygons of GeoJSON of both orders of their rings are drawn, e.g. of RFC 7946. include are the keys of the only features, exclude the ones of features which are left out, they are not drawn at all, e.g. "include": ["AT", "DE", ...] for the countries of the EU or "exclude": ["RU", "BY"].
  • projection is a d3 projection (type, mercator by default, e.g. conicConformal, azimuthalEqualArea or equalEarth) and its parameters. The projection is fitted to the facet, so the section of the map is the one of fit: all features (default), the ones with data ("data") or the ones of a list of keys, e.g. ["AT", "DE", "CH"]. The parameters change the shape, e.g. rotate ([-longitude, -latitude]) is the center of an azimuthal projection, "rotate": [-10, -52] of azimuthalEqualArea is the common projection of Europe (EPSG:3035), parallels are the ones of a conic projection, e.g. "parallels": [46, 49] of conicConformal for Austria, rotate of mercator its central meridian.
  • The plot types: geo:base (all features, e.g. as background, the props are fixed), geo:region (the feature of every row), geo:circle and geo:text (an element per row at the center of its feature or at the lon and lat of its props, e.g. proportional circles with a sqrt scale of r).
  • The hover shows the region under the mouse: its name (the name of the props of the join mapping, of the feature or the key) and the rows of it with the values of the first numeric mapping with a hover, the region is highlighted.
  • One row per region is usually shown, filter (see formElements) or facets select them, e.g. a map per fuel type.

See data/bev/def-map.json, data/sprit-nuts/def-map.json and the maps of data/energy/. The geometries of data/geo/ are of Eurostat (GISCO), see data/geo/README.md.

Annotations

annotations (an annotation or a list) are bands, lines, texts and circles at values of mappings, e.g. estimated values or events, the keys are the names of the mappings, a value or a range [from, to], values are converted as the ones of the rows, e.g. dates, and can be references to globals, e.g. "@target":

"annotations": [
    { "type": "band", "x": ["2025-07-01", null], "label": "Schätzung" },
    { "type": "line", "y": 0, "props": { "stroke": "#999", "stroke-dasharray": "3 3" } },
    { "type": "text", "x": "2020-03-16", "y": 30, "text": "Lockdown" },
    { "type": "circle", "x": "2022-02-24", "y": 1.8, "above": true }
]
  • band: a rectangle, a mapping without value is over the whole plot area, e.g. a band of the horizontal axis has the height of the plot area, null in a range is the edge of the domain, a category of a band or point scale is its band, label is its name.
  • line: a value of the horizontal axis is a vertical line, of the vertical one a horizontal line, with a label at its end.
  • text (text) and circle at the values, without one (or with null) of an axis at the start of the other one.
  • label and text are templates of the globals, e.g. "Ziel {target}".
  • props are svg attributes, e.g. fill or stroke, the defaults are grey, their names are the ones of the facet and of the annotation, e.g. @y, above draws the annotation above the plots, by default it is below the plots and grid lines, labels are above them. facet is a key or a list of keys of the facets of the annotation, by default it is in all facets.
  • Polar plots: a band of the angle is a sector (also across the top, e.g. from November to February), of the radius a ring, a line of the angle a spoke, of the radius a circle. Maps: text and circle at lon and lat.
  • The classes are vis-annotation and vis-band, vis-line, vis-text or vis-circle, the labels vis-annotation-label, see styles.

An annotation is a plot of the type annotation:band, annotation:line, annotation:text or annotation:circle of a row of its values (the plot annotation-<index>, in the layer below or above). These types draw an annotation per row, so the annotations can also be rows of data, e.g. a file of events, the props can have the names of the rows:

{ "type": "annotation:line", "data": "events.json", "props": { "stroke": "@color" } }

See data/sprit-nuts/def.json, data/rechtsform/def-stacked-p.json and data/energy/weather/temperature-polar.json.

Props of the definition

Most values of a definition can be props:

  • fixed values, e.g. 3 or "none"
  • references "@name", see below, also as {"prop": "ref", "ref": "name"}
  • {"prop": "fixed", "value": ...}: a value which is not a prop, e.g. an object or a text starting with @
  • {"prop": "relative", "ref": "innerWidth", "ratio": 0.01}: a ratio of a reference
  • {"prop": "steps", "ref": "totalWidth", "steps": [{"cut": 0, "value": 220}, {"cut": 550, "value": 250}]}: the value of the last step with a cut below the reference
  • {"prop": "format", "ref": "y", "format": ",.1f"}: the reference as a text of a d3 format in the locale, of a date mapping a time format, e.g. "%b %Y", e.g. a label of a value

Objects without prop are nested props, e.g. d of a path.

The names of the references depend on the part of the definition, the inner parts add names:

| Part | Names | |------|-------| | everywhere | the globals and totalWidth, the width of the visualisation | | options.height, facets.cols, facets.scales, filter | only these | | the props of legend (also of the symbol) and hover of a mapping | the props of its categories, e.g. @color and @name | | axis.ticks, scale.domain, the values and props of annotations, the values of the data of a plot | the size of the facet: width, innerWidth, height, innerHeight | | scale.range | the sizes of the coordinate system, e.g. @radius of polar plots, width and height are the ones of the inner area | | the props of a plot | the props of its categories and the values of the row: a mapping (e.g. @x), the value as a text of the format of its hover (@x:formatted, e.g. 1.234,5, a label), the scaled value (@x:scaled, @x:scaled:center for the center of a band, @x:scaled:0 for the position of 0, @x:scaled:min and @x:scaled:max of the domain), stacked values (@y:start, @y:end:scaled, ..., see stacked) |

Inner names replace outer ones, in a plot the mappings replace globals of the same name, e.g. @year is the one of the row. A reference to an unknown name is undefined, validateDef warns of it.

facets

A plot for every category of dim (the name of a mapping with props), in cols columns (1 by default). The mappings listed in scales share their scale across all facets. cols can be a prop based on totalWidth, scales a reference to globals.

formElements and globals

Form elements change globals, e.g. the shared scales of the facets, as radio buttons ("type": "radio"), a drop down list ("type": "select") or a slider over the entries ("type": "slider", e.g. of years, the visualisation changes while it is moved). The selected entries are named below the subtitle of the PNG, "inImage": false leaves out a form element which only changes the presentation, which the image shows anyway, e.g. shared or separate scales of the facets. filter shows only the rows of values of mappings, e.g. of a global, a value or a list of them, the values are compared as the ones of the rows, e.g. dates:

"globals": { "year": "2025" },
"filter": { "year": "@year" },
"formElements": [{
    "id": "year", "name": "Jahr", "ref": "year", "type": "select",
    "values": [{ "id": "2024", "name": "2024", "value": "2024" }, { "id": "2025", "name": "2025", "value": "2025" }]
}]

values can also be the distinct values of a column of the data, in ascending order (numbers by their value), named as the values, so new values of the data are added, e.g. a new year. If the global is none of them, e.g. it is missing, it is the last one:

"filter": { "year": "@year" },
"formElements": [{ "id": "year", "name": "Jahr", "ref": "year", "type": "slider", "values": { "column": "year" } }]

An entry with a mapping also patches the mappings while it is selected, e.g. to switch the column of an axis:

"globals": { "column": "value" },
"formElements": [{
    "id": "column", "name": "Wert", "ref": "column", "type": "radio",
    "values": [
        { "id": "value", "name": "Wert", "value": "value" },
        { "id": "share", "name": "Anteil", "value": "share", "mapping": { "y": { "column": "share" } } }
    ]
}]

If the column depends on several form elements, it can be a template of the globals, e.g. the values and their shares in the columns twh, co2, twh.share and co2.share:

"globals": { "values": "twh", "share": "" },
"formElements": [{
    "id": "values", "name": "Werte", "ref": "values", "type": "radio",
    "values": [
        { "id": "twh", "name": "TWh", "value": "twh" },
        { "id": "co2", "name": "CO₂", "value": "co2" }
    ]
}, {
    "id": "share", "name": "Darstellung", "ref": "share", "type": "radio",
    "values": [
        { "id": "abs", "name": "Absolut", "value": "" },
        { "id": "rel", "name": "Anteil", "value": ".share", "mapping": { "y": { "axis": { "format": ".0%" } } } }
    ]
}],
"mapping": { "y": { "column": "{values}{share}" } }

{name} is replaced by the value of the global, unknown globals are kept and reported as warnings. All texts of a definition are templates as well: the title, the subtitle and the footer, the names of the mappings and of their categories (e.g. of the legends, the hover and the facets), the titles of the axes and the labels and texts of annotations, e.g. "subtitle": "Durchschnitt {base} = 100".

Checks outside the browser

@preschen/gen-vis/check merges and checks definitions in scripts, e.g. all definitions of a page before a deploy, load reads the file of an url:

import { readFileSync } from 'node:fs';
import { pathToFileURL } from 'node:url';
import { resolveParents, validateDef } from '@preschen/gen-vis/check';

const url = pathToFileURL('data/gas/price.json').href;
const def = await resolveParents(JSON.parse(readFileSync(new URL(url), 'utf8')), url,
    u => readFileSync(new URL(u), 'utf8'));
console.log(validateDef(def));  // the warnings, e.g. unknown plot types

Extensions

Plot types and coordinate systems can be registered, before the visualisations are loaded. A plot type draws the groups of rows of a plot, one per combination of its categories. A group has its rows, the keys of its categories (categories), their props (props) and the values of the props of the plot: of a row at(row), of one prop prop(name)(row) and the ones which are the same for all rows attrs, e.g. the color of a line, complete(row) is false if a mapping of the props has no value. The values are plain values, e.g. the position of "@x:scaled". pointwise draws an element per row (without the rows of missing values) with the values of its props as attributes, groupwise(groups, parent, d) a path per group with attrs and the path d(group), both move their elements with the transition of the options:

import { registerPlotType, pointwise } from '@preschen/gen-vis';

registerPlotType('my:tick', {
    render: (groups, parent, plot, ctx) => pointwise(groups, parent, 'rect', v => ({
        ...v, x: v.cx - 1, width: 2, height: ctx.innerHeight,
    })),
});

ctx is the facet: the store, the d3 selection inner of the plot area, the key of the facet, its rows, the scales by the names of the mappings, axis (the names of the mappings of the positions and of the values), stackOf(row) (the start and the end of a stacked value), scope (the names of the references of the facet, see props), innerWidth, innerHeight and dims (the sizes of the coordinate system, e.g. its radius), of maps also the projection and the path. curve: true passes the curve of the plot, coords limits a type to coordinate systems, e.g. cartesian:bar to cartesian. The types of PlotType and PlotGroup are in the package.

A new view, e.g. of a toggle of a legend, a form element or a new width, is drawn into the elements of the one before, only the attributes which change are set. With update: true a type draws into the elements of the draw before, as pointwise and groupwise do (the groups are the ones of the same categories, the elements per row the ones of the same rows or, of pointwise(groups, parent, type, translate, key), of the same key(values), e.g. geo:region the one of the same feature after a filter), the elements of other types are removed before they are drawn again. ctx.keep(key, create) is an element kept from one draw to the next, e.g. an axis of a coordinate system, create() appends a new one to ctx.inner.

A coordinate system (registerCoord(name, coord)) has the default ranges of the orientations of its scales, the axes and the geometry of the hover, see src/coords/index.js and e.g. src/coords/polar.js, load() of a coordinate system loads its parts with its first chart, e.g. the ones of maps (src/coords/geo.js), again with the next one if it failed. Both are known to validateDef, @preschen/gen-vis/check exports registerPlotType and registerCoord as well, the standalone script has them as GenVis.registerPlotType, GenVis.registerCoord, GenVis.pointwise and GenVis.groupwise. The schema only knows the built-in ones.

Upgrading from 1.x

Definitions:

  • facets.dim is the name of a mapping, not a list: "dim": "facet" instead of "dim": ["facet"].
  • The names of 0.9 are removed: svg:path, base:area, bar and stackedBar are cartesian:line, cartesian:area and cartesian:bar, @y:st:s, @y:st:e and @y:st:h:scaled are @y:start, @y:end and @y:height:scaled, see the readme of 1.x.
  • Values of globals are the same as strings, e.g. 2024 and "2024", but 0 and "" are different, they were the same.
  • All texts are templates of the globals, so {word} in a footer or a name is replaced if there is a global word, its value is text, also in the footer.
  • A single value of an annotation which is null is no value, e.g. a text at the start of the axis.
  • The categories of the props of a mapping are categories, they were manual, radio buttons are "type": "radio", they were "switch".
  • The hover without format has the decimal mark of the locale, e.g. 1234,5 in German, and of several vertical mappings the one with a hover has the values of the hover, it was the last one. Every hover prop of a category is a column of the hover.
  • Only the footer and the templates of the hover are HTML, the names of the legends and the hover are text, an entity is the character itself, e.g. "Ø\u00a01991\u00a0-\u00a02020" instead of "Ø&nbsp;1991&nbsp;-&nbsp;2020".
  • A date without a time, e.g. 2024-06-01, is the midnight of the time zone of the scale, local of a time scale, it was midnight UTC, so the day before west of UTC, also the values of the events hover and select differ, a utc scale has the one of UTC. Dates and times without a time zone, e.g. 2024-06-01 13:00, are UTC of a utc scale, they were local, dates of parquet and its timestamps without a time zone are strings.
  • The extension of a domain taken from the data (domainRel, 2% by default) is relative to the positions of log, sqrt and symlog scales, e.g. of the radius of a rose chart, it was relative to the values, except of log scales.

Pages:

  • The standalone script reads the data attributes as their props: data-download="false" and data-copy="false" are off (they were on, a download of a file named "false"), data-state is JSON. mountGenVisElement mounts an element which is mounted already again.
  • The charts fill the width of .vis inside its padding and border, a page which made .vis wider for its padding does not need to.
  • The package depends on the modules of d3 which it uses, not on d3, an application which imports d3 needs its own dependency.

Styles: all classes start with vis-, see styles:

| 1.x | 2.0 | |-----|-----| | .vis-header .title, .subtitle, .selection | vis-title, vis-subtitle, vis-selection | | formElement (.title, .entries, .slider, .value) | vis-form-element (vis-form-element-title, vis-radio, vis-slider, vis-slider-value) | | legend, color-legend (.title, .entries, .scale, .missing, .swatch) | vis-legend, vis-color-legend (vis-legend-title, vis-legend-entries, vis-legend-entry, vis-color-scale, vis-legend-missing, vis-swatch) | | facet-title, vis-inner, svg.facet | vis-facet-title, vis-facet, svg.vis-svg | | plotGroup, paths, group, highlight, features | vis-plot (data-plot), vis-plot, vis-group, vis-highlight, vis-features | | axis-name-x axis-position-left, axis-title, grid | vis-axis vis-axis-left (data-mapping="x"), vis-axis-title, vis-grid | | annotations below, annotation band, annotation-label, annotation-labels | vis-plot vis-annotations vis-below, vis-annotation vis-band, vis-annotation-label, vis-annotation-labels | | hover (.title, .entries, tr.entry, .nearest, td.y, .value), hoverMarker, events | vis-hover (vis-hover-title, vis-hover-entries, vis-hover-entry, vis-nearest, td[data-mapping="y"], vis-value), vis-hover-marker, vis-events | | #container-<id> of form elements and their entries | [data-id="<id>"] | | gen-vis-attached (mountGenVisByClass) | vis-mounted |

The default colors of annotations are styles, not attributes, they can be changed with the css variables. A facet with a hover is an application of screen readers (role="application"), not an image.

Extensions: a plot type gets plain values instead of the objects of the internal format, the groups have rows, at(row), prop(name), attrs and complete(row) instead of values and props, ctx has rows, scales (the d3 scales by the names of the mappings), axis, stackOf and scope instead of data, info and relativeBases, the nearest value of a scale is scale.nearest(position), see extensions. groupwise sets the path of its third argument, d(group), a type with update: true draws into its elements of the draw before, the elements of other types are removed before they are drawn again.

Development

npm install
npm run dev     # dev server with all definitions in data/, ?def=bev/def.json shows one with the prepared definition
npm test        # unit tests, rendering and schema check of all definitions in data/
npm run build   # es module for bundlers in dist/
npm run watch   # rebuilds dist/ on changes, e.g. for `npm link`
npm run lib     # standalone script in dist-lib/, gen-vis.js (of the package) and gen-vis-<version>.js
npm run deploy  # builds and uploads the standalone script to GEN_VIS_UPLOAD (user@host:/path) of .env
npm run site    # dev server of the page of the examples (site/), with the package built in dist/ and dist-lib/
npm run site:build  # the page of the examples in dist-site/

The page of the examples (site/) is the one of GitHub Pages, built and deployed by .github/workflows/pages.yml on every push to main and v2. It uses the package as it is published, the es module of dist/, so its styles are in the cascade layer as in the applications. Every example is a definition and the CSS of its look in site/examples/ (listed in site/examples/index.js), its data is in site/public/data/, the urls are relative to the page. The definitions and the CSS can be edited on the page, the editor completes the keys and values of the JSON Schema. The examples of the use are site/components/VueDemo.vue and site/public/standalone.html (with the module of the standalone script of dist-lib/esm/, copied to lib/esm/ of the page) of the chart site/public/charts/prices.json, the comparison with other libraries is site/components/Comparison.vue. The tests check and draw the examples, as the ones of data/.

tests/snapshots/ has the html of every definition in data/, drawn, hovered and with the first entry of the legend toggled, so changes of the code which change the charts are seen in the diff, npx vitest -u takes intended ones. The tests run in the time zone of Vienna (vite.config.js), the positions of the days of time scales differ a little in other zones, by their changes of the daylight saving time.

The page of the dev server (index.html, src/dev/) shows the definitions in data/, also of linked directories, e.g. of the pages using the package, without the mixins (_*.json). All definitions in data/ are rendered, hovered and checked by the tests, so they have to stay valid. data/energy/ is a selection of the energy dashboard, see its README.md.

To try changes in an application, npm install --no-save ../gen-vis links the directory (with npm run watch). The vue of the linked package would be the one of its own node_modules, a second Vue, so the bundler of the application has to resolve vue to its own, e.g. resolve.alias: { vue: path.resolve('node_modules/vue') } of webpack or resolve.dedupe: ['vue'] of vite. The changes of the versions are in CHANGELOG.md. npm publish runs the tests and builds dist/ and the standalone script of the package.

License

MIT, see LICENSE. The example data in data/ has its own sources, see the footers of the definitions and the readmes of data/geo/ and data/energy/.