@preschen/gen-vis
v2.0.0
Published
A declarative d3 visualisation library
Maintainers
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 vueStandalone 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.2024of2024-06-01.align: the same day of a date in the yearyear(2020 by default, a leap year), e.g. to compare the years of daily data on one axis, withyearas the categories.- The dates of
yearandalignare the ones of the time zone of the scale of the column (columnoras), local or UTC of autcscale. index: the values relative to the mean of the values of the rows ofbasein the group, timesscale(100 by default).baseis a column and its value, e.g.{"year": "{base}"}, or a range[from, to](nullis 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 ofsizerows 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 columnorder, 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 oforder, 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,dateorcategorical. Numeric and date values which are missing, invalid or infinite arenull: 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 aT, seconds and their fraction,24:00is the end of the day) or01.06.2024(the day first, also with a time), is the one of the time zone of the scale, local of atimescale (and the other ones) and UTC of autcscale, so it is the same day and time in every time zone (Date.parsereads a date without a time as midnight UTC, the day before west of UTC in local time, and01.06.2024as the 6th of January), the others are read byDate.parse, e.g.2024-06-01T13:00:00Z.scale:typeis a d3 scale (linear,time,log,point,band, ...), continuous scales need anumericordatetype (positions of dates atimeorutcone),pointandbandacategoricalone,ordinalscales are of any values, e.g. the colors of categories (range).orientation(horizontalorvertical) places the scale on the plot,angularorradialin polar plots.domainfixes the domain,nullentries 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 apointorbandscale are its categories in their order.domainRel(relative to the positions, e.g. of alogor asqrtscale) anddomainAbs(absolute) extend it, by default a domain of a position taken from the data is extended by 2%.nicerounds the ends taken from the data of a numeric mapping, e.g. 0.951 to 1, of alogscale to powers of 10,truefor steps of about a tenth of the domain or the number of steps, e.g. so the legend of colors ends at 100%.paddingfor categorical scales.insetis the space at the start and the end of the range in pixels (of positions), a number for both or[start, end], e.g.10so the first and the last point are not at the edges, of every type of scale, also of dates, e.g. instead of adomainAbsof milliseconds. A scale withoutorientationhas therangegiven, e.g. colors or the radius of points.Scales of colors:
sequentialanddivergingscales (a domain with a middle entry, e.g.[null, 0, null]) have aninterpolatorof d3, e.g.Blues,ViridisorRdYlGn,sequentialQuantilethe colors of the quantiles of the values.quantize,quantileandthresholdscales (thedomainare the values between the classes) are classes of colors, therangeor aschemeof d3, e.g.Blues, withclassescolors,ordinalscales 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 plotsangularandradial),ticks,tickSpacing(withoutticksa tick per this many pixels of the axis, e.g.80, so a narrow chart has fewer ticks, instead of arelativeprop ofticks),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) andpadding.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 ahover(or the first one) has the values of the hover, the other numeric ones with ahoverare 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.formatof 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 mappingspropsare the columns of the entries, thenameof the category first, e.g.{"unit": "@unit"}adds a column of the propunitof the categories,"name": nullleaves out the name.props: the categories and their props, e.g. colors.commonprops are used for allcategories,nameandvisibleare set by default. Only visible categories are shown. The order of thecategoriesis 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.ordersets 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.ranksare 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 overridecommon:"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": truethe 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}.schemesets the propcolorof the categories in their order, a d3 scheme, e.g.Tableau10orBlues(of the number of categories), acolorof the props is kept:"land": { "column": "Bundesland", "props": { "fromData": true, "scheme": "Tableau10", "categories": { "ÖSTERREICH": { "name": "Gesamt", "color": "#000" } } } }legend: a toggle for every category,symboldraws svgelements(with props) of the givensizebefore the name, or is a shortcut in the color of the category (@color):"line"(with thestroke-width,stroke-dasharrayandopacityof the categories if they have them, a width of 2 otherwise),"rect"or"circle"(with theiropacity), 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,formatof the values, by default the one of the hover,missingan entry beside them, e.g. of the regions of a map without a value ({"name": "keine Daten", "color": "#DADADA"}, the color ofgeo: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:startand@y:endare the start and the end of the stacked value,@y:start:scaledand@y:end:scaledtheir positions, e.g.y0andy1ofcartesian:bar,@y:height:scaledthe difference, e.g. the height of ansvg: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,dwithxandycartesian:area: an area per group,dwithx,y0andy1cartesian:bar: a bar per row fromy0toy1, 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 atx, in the middle of a band,widthdefaults to the width of a band or the step of a point scale, a continuous scale needs awidth. Withx1the bars are horizontal, fromx0(by default 0) tox1aty,heightis their thickness, e.g. of the countries of a band scale ofy.polar:*of polar plots andgeo:*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 withoutpropsonly group the rows, e.g. a line per id.id: the class and thedata-plotof 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 (andtext). Props starting withhighlight-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,rowonly 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,stepBeforeorstepAfter.monotoneXis smooth without overshooting the values, e.g. for monthly data,basisdoes not pass through the points.linearClosed,catmullRomClosedandbasisClosedconnect 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:firstorlast(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:belowdraws the plot below the axes and grid lines, e.g. a background,aboveabove 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-01to2021-01-01, a domain taken from the data is not extended. The categories ofpointandbandscales 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. angularaxes are around the circle,griddraws lines from the center.radialaxes go from the center outwards, at theangleof the axis (degrees, clockwise from the top, 0 by default),griddraws 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,dwithangleandradius),polar:area(an area per group,dwithangle,innerRadiusandouterRadius),polar:arc(a segment of a ring per row, propsangle,innerRadiusandouterRadius, optionalwidthin radians,padAngleandcornerRadius, e.g. the bars of a rose chart), andpolar:circleandpolar:text(an element per row atangleandradius). The angles of a band scale are in the center of the band. Thesvg:types draw with the center as origin, thecartesian:types are cartesian only. - Stacked values (
stackedof the radius) are stacked from the center, e.g."innerRadius": "@y:start:scaled", "outerRadius": "@y:end:scaled"ofpolar: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.datais the url of GeoJSON or TopoJSON (or the GeoJSON or TopoJSON itself),objectthe object of TopoJSON (the first one by default),keythe key of the features (id, the default, or a property, e.g.NUTS_ID) andnamethe property of their names (nameby default). Polygons of GeoJSON of both orders of their rings are drawn, e.g. of RFC 7946.includeare the keys of the only features,excludethe 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"].projectionis a d3 projection (type,mercatorby default, e.g.conicConformal,azimuthalEqualAreaorequalEarth) and its parameters. The projection is fitted to the facet, so the section of the map is the one offit: 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]ofazimuthalEqualAreais the common projection of Europe (EPSG:3035),parallelsare the ones of a conic projection, e.g."parallels": [46, 49]ofconicConformalfor Austria,rotateofmercatorits 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:circleandgeo:text(an element per row at the center of its feature or at thelonandlatof its props, e.g. proportional circles with asqrtscale ofr). - The hover shows the region under the mouse: its name (the
nameof the props of thejoinmapping, of the feature or the key) and the rows of it with the values of the first numeric mapping with ahover, 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,nullin a range is the edge of the domain, a category of abandorpointscale is its band,labelis its name.line: a value of the horizontal axis is a vertical line, of the vertical one a horizontal line, with alabelat its end.text(text) andcircleat the values, without one (or withnull) of an axis at the start of the other one.labelandtextare templates of the globals, e.g."Ziel {target}".propsare svg attributes, e.g.fillorstroke, the defaults are grey, their names are the ones of the facet and of the annotation, e.g.@y,abovedraws the annotation above the plots, by default it is below the plots and grid lines, labels are above them.facetis a key or a list of keys of the facets of the annotation, by default it is in all facets.- Polar plots: a
bandof the angle is a sector (also across the top, e.g. from November to February), of the radius a ring, alineof the angle a spoke, of the radius a circle. Maps:textandcircleatlonandlat. - The classes are
vis-annotationandvis-band,vis-line,vis-textorvis-circle, the labelsvis-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.
3or"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 typesExtensions
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.dimis 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,barandstackedBararecartesian:line,cartesian:areaandcartesian:bar,@y:st:s,@y:st:eand@y:st:h:scaledare@y:start,@y:endand@y:height:scaled, see the readme of 1.x. - Values of globals are the same as strings, e.g.
2024and"2024", but0and""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 globalword, its value is text, also in the footer. - A single value of an annotation which is
nullis no value, e.g. a text at the start of the axis. - The categories of the props of a mapping are
categories, they weremanual, radio buttons are"type": "radio", they were"switch". - The hover without format has the decimal mark of the locale, e.g.
1234,5in German, and of several vertical mappings the one with ahoverhas 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"Ø 1991 - 2020". - A date without a time, e.g.
2024-06-01, is the midnight of the time zone of the scale, local of atimescale, it was midnight UTC, so the day before west of UTC, also the values of the eventshoverandselectdiffer, autcscale has the one of UTC. Dates and times without a time zone, e.g.2024-06-01 13:00, are UTC of autcscale, 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 oflog,sqrtandsymlogscales, e.g. of the radius of a rose chart, it was relative to the values, except oflogscales.
Pages:
- The standalone script reads the data attributes as their props:
data-download="false"anddata-copy="false"are off (they were on, a download of a file named "false"),data-stateis JSON.mountGenVisElementmounts an element which is mounted already again. - The charts fill the width of
.visinside its padding and border, a page which made.viswider for its padding does not need to. - The package depends on the modules of d3 which it uses, not on
d3, an application which importsd3needs 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/.
