- Narrative Chart
Narrative Chart is an open-source visualization library specialized for authoring charts that facilitate data storytelling with a high-level action-oriented declarative grammar. The library is implemented in JavaScript and compatible with most modern web browsers.
Unlike existing visualization libraries such as D3.js, Vega, and Apache ECharts, Narrative Chart is designed to meet the needs of data storytelling specifically and lower the barrier of creating such charts. The grammar of Narrative Chart is simple and intuitive to learn, even for non-expert users, as it mimics the real actions of designers. Besides, Narrative Chart has rich supportive features for visual narratives, which enables users to rapidly create expressive charts and inspires their creativity.
Use npm/yarn to install the libraries
npm install narchart
or
yarn add narchart
import {NarrativeChart} from "narchart";
(1) Create a DOM element that the visualization will be attached to.
<div id="vis"></div>
(2) Then, build your visualization specification.
var yourSpec = {...}
(3) Finally, visualize the chart with the specification.
const vis = new NarrativeChart();
vis.container('#vis');
vis.generate(yourSpec);
{
// Properties for data (Required)
"data": {
"url": ...,
"schema": [...]
},
// Action list for chart generation (Required)
"actions": [
...
]
}
Property | Type | Description |
---|---|---|
url | String | Required. A string describing the data source. For example: {"url": "data/cars.csv"} |
schema | Object[] | An array of field objects. Default: [] |
Property | Type | Description |
---|---|---|
values | Object[] | Required. The full data set, included inline. For example: {"values": [{"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43}]} |
schema | Object[] | An array of field objects. Default: [] |
Initializing the basic configuration of the chart.
{
"add": "config",
"mode": "light"/"dark", // (default: "light")
"emotion": "none"/"calm"/"exciting"/"positive"/"negative"/"serious"/"playful"/"trustworthy"/"disturbing", // (default: "none")
"background-image": {
"url" : image-url,
"opacity": float // (optional)
"grayscale": int // (optional)
},
"background-color": {
"color" : string,
"opacity": float // (optional)
},
"width": 640, // (optional)
"height": 640 // (optional)
}
Operating a SQL-like action to query data from the spreadsheet.
{
"select": [
{
"field": field,
"aggregate": none/sum/avg/max/min
},
...
],
"groupby": [
{
"field": field(categorical)
}
],
"filter": [
{
"field": field(categorical),
"value": value
},
{
"field": field(numerical),
"op": equal/inequal/greater/less
"value": value
},
...
]
}
Choosing a mark to initialize the chart.
Chart | Mark | Mark Style |
---|---|---|
Scatterplot | point | stroke; stroke-width; stroke-opacity; fill; fill-opacity; background-image; |
Bar Chart | bar | stroke; stroke-width; stroke-opacity; fill; fill-opacity; corner-radius; bin-spacing; background-image; |
Horizontal Bar Chart | bar | stroke; stroke-width; stroke-opacity; fill; fill-opacity; corner-radius; bin-spacing; background-image; |
Line Chart | line | stroke; stroke-width; point; point-radius; point-fill; point-stroke; point-stroke-width; background-image; |
Pie Chart | arc | inner-radius; outer-radius; text-radius; corner-radius; stroke; stroke-width; stroke-opacity; fill; fill-opacity; background-image; |
Unitvis | unit | stroke; stroke-width; stroke-opacity; fill; fill-opacity; background-image; |
Area Chart | area | stroke; stroke-width; point; point-radius; point-fill; point-stroke; point-stroke-width; background-image;area-fill;area-fill-opacity; |
Bubble Chart | bubble | stroke; stroke-width; stroke-opacity; fill; fill-opacity; background-image; |
{
"add": "chart",
"mark": {
"type": "point"/"line"/"bar"/"unit"/"arc",
"style": { ... }, // (optional)
"animation": { "type": type }, // (optional)
}
"style": {
"background-image": {
"url" : image-url,
"opacity": float, // (optional)
"grayscale": int // (optional)
} ,
"background-color": {
"color" : string,
"opacity": float // (optional)
},
"mask-image": image-url, // (optional)
},
"animation": { ... }
}
Encoding channels to design the chart.
Chart | Channel | Field Type |
---|---|---|
Scatterplot | x | numerical |
Scatterplot | y | numerical |
Scatterplot | size | numerical |
Scatterplot | color | categorical |
Unitvis | x | categorical |
Unitvis | y | categorical |
Unitvis | size | numerical |
Unitvis | color | categorical |
Bar Chart | x | categorical |
Bar Chart | y | numerical |
Bar Chart | color | categorical |
Horizontal Bar Chart | x | categorical |
Horizontal Bar Chart | y | numerical |
Horizontal Bar Chart | color | categorical |
Line Chart | x | temporal |
Line Chart | y | numerical |
Line Chart | color | categorical |
Pie Chart | theta | numerical |
Pie Chart | color | categorical |
Area Chart | x | temporal |
Area Chart | y | numerical |
Bubble Chart | size | numerical |
Bubble Chart | color | categorical |
Add Encoding
{
"add": "encoding",
"channel": "x"/"y"/"color"/"size"/"theta",
"field": field,
"axis": { ... }, // (optional)
"animation": { "duration": number } // (optional)
}
Axis Style | Description |
---|---|
labelAngle | number |
labelFontSize | number |
Modify Encoding
{
"modify": "encoding",
"channel": "x"/"y"/"color"/"size"/"theta",
"field": field,
"animation": { "duration": number }
}
Remove Encoding
{
"remove": "encoding",
"channel": "x"/"y"/"color"/"size"/"theta",
"animation": { "duration": number }
}
Annotating certain data marks or axis in the chart by changing the marks'/axis' graphical appearance or adding additional objects such as arrows and circles.
{
"add": "annotation",
"method": annotation_method,
"target": [
{
"field": field,
"value": value
}
],
"style": {
...
},
"animation": {
"type": "fade"/"fly"/"wipe" // (default: "fade"),
"duration": number
}
}
Annotation Methods | Style |
---|---|
Fill | color |
Glow | color |
Texture | background-image; |
Desaturate | \ |
Fade | opacity |
Contour | stroke-width; color |
Arrow | height; width; color |
Circle | color; width; height |
Label | font-size; font-family; font-color; font-weight; font-style |
Symbol | icon-url; width; height |
Tooltip | font-size; font-family; font-color; font-weight; font-style; tooltip-color |
Reference | stroke-width; color; stroke-dasharray; stroke-linecap |
Regression | stroke-width; color; stroke-dasharray; stroke-linecap |
{
"add": "annotation",
"method": "fill",
"target": [
{
"field": field,
"value": value
}
],
"style": {
"color": color
},
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "glow",
"target": [
{
"field": field,
"value": value
},
{
"axis": x/y
}
],
"style": {
"color": color
},
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "texture",
"target": [
{
"field": field,
"value": value
}
],
"style": {
"background-image": background-image-url
},
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "desaturate",
"target": [
{
"field": field,
"value": value
}
],
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "fade",
"target": [
{
"field": field,
"value": value
}
],
"style": {
"opacity": float
},
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "contour",
"target": [
{
"field": field,
"value": value
}
],
"style": {
"color": color
},
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "arrow",
"target": [
{
"field": field,
"value": value
}
],
"style": {
"color": color
},
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "circle",
"target": [
{
"field": field,
"value": value
}
],
"style": {
"color": color
},
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "label",
"target": [
{
"field": field,
"value": value
}
],
"text": text, // (optional)
"style": {
"font-size": int,
"color": color
},
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "symbol",
"target": [
{
"field": field,
"value": value
}
],
"style": {
"icon-url": icon-url
},
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "tooltip",
"target": [
{
"field": field,
"value": value
}
],
"text": text, // (optional)
"style": {
"font-size": int, // (optional)
"font-family": string, // (optional)
"font-color": string, // (optional)
"font-weight": string, // (optional)
"font-style": string, // (optional)
"tooltip-color": string // (optional)
},
"animation": { "duration": number, "type": string}
}
{
"add": "annotation",
"method": "reference",
"target": [
{
"field": field,
"value": value
}
],
"animation": { "duration": number }
}
{
"add": "annotation",
"method": "regression",
"target": [
{
"field": field,
"value": value
}
],
"style": {
"color": color
},
"animation": { "duration": number }
}
Adding title or caption.
{
"add": "title",
"text": string,
"style": {
"font-size": int, // (optional)
"font-family": string, // (optional)
"font-color": string, // (optional)
"font-weight": string, // (optional)
"font-style": string, // (optional)
"position": string, // (optional)
"background-color": string, // (optional)
"background-image": image-url, // (optional)
"border-width": int, // (optional)
"border-color": string, // (optional)
"divide-line-width": int, // (optional)
"divide-line-color": string, // (optional)
"left-padding": int, // (optional)
"top-padding": int, // (optional)
},
"animation": {"duration": int, "type": "fade"/"typewritter"/"wipe" (default: "fade")} // (optional)
}
{
"add": "caption",
"text": string,
"style": {
"font-size": int, // (optional)
"font-family": string, // (optional)
"font-color": string, // (optional)
"font-weight": string, // (optional)
"font-style": string, // (optional)
"position": string, // (optional)
"left-padding": int, // (optional)
"top-padding": int, // (optional)
},
"animation": { "duration": int, "type": "fade"/"typewritter"/"wipe" (default: "fade") } // (optional)
}
Adding an image anywhere on the canvas.
{
"add": "image",
"style": {
"image": image-url,
"x": number,
"y": number,
"width": number, // (optional)
"height": number // (optional)
},
"animation": { "duration": number }
}
Assigning multiple actions to a group.
{
"add": "group",
"actions": [
...
],
"animation": {
"sync": bool // (default: false)
"duration": int // (default: 0)
}
}
Narrative Chart supports animated transitions between actions by specifying "duration", which represents per-action duration in milliseconds.
{
"data": {
"url": "http://localhost:3000/spreadsheets/cars.csv",
"schema": [
{
"field": "Name",
"type": "categorical"
},
{
"field": "Miles per Gallon",
"type": "numerical"
},
{
"field": "Cylinders",
"type": "numerical"
},
{
"field": "Displacement",
"type": "numerical"
},
{
"field": "Horsepower",
"type": "numerical"
},
{
"field": "Weight",
"type": "numerical"
},
{
"field": "Acceleration",
"type": "numerical"
},
{
"field": "Year",
"type": "temporal"
},
{
"field": "Origin",
"type": "categorical"
}
]
},
"actions": [
{
"select": [
{
"field": "Horsepower",
"aggregate": "sum"
},
{
"field": "Miles per Gallon",
"aggregate": "sum"
},
{
"field": "Acceleration",
"aggregate": "sum"
},
{
"field": "Weight",
"aggregate": "sum"
},
{
"field": "Name"
},
{
"field": "Origin"
}
],
"groupby": [
{
"field": "Name"
}
],
"filter": []
},
{
"add": "chart",
"mark": "point"
},
{
"add": "encoding",
"channel": "x",
"field": "Horsepower"
},
{
"add": "encoding",
"channel": "y",
"field": "Miles per Gallon"
}
},
{
"add": "encoding",
"channel": "color",
"field": "Origin"
}
},
{
"add": "encoding",
"channel": "size",
"field": "Acceleration"
}
},
{
"add": "annotation",
"method": "fill",
"target": [
{
"field": "Origin",
"value": "Japan"
}
]
}
]
}
{
"data": {
"url": "http://localhost:3000/spreadsheets/cars.csv",
"schema": [
{
"field": "Name",
"type": "categorical"
},
{
"field": "Miles per Gallon",
"type": "numerical"
},
{
"field": "Cylinders",
"type": "categorical"
},
{
"field": "Displacement",
"type": "numerical"
},
{
"field": "Horsepower",
"type": "numerical"
},
{
"field": "Weight",
"type": "numerical"
},
{
"field": "Acceleration",
"type": "numerical"
},
{
"field": "Year",
"type": "categorical"
},
{
"field": "Origin",
"type": "categorical"
},
{
"field": "dataid",
"type": "categorical"
}
]
},
"actions": [
{
"select": [
{
"field": "Name"
},
{
"field": "Miles per Gallon",
"aggregate": "sum"
},
{
"field": "Cylinders"
},
{
"field": "Displacement",
"aggregate": "sum"
},
{
"field": "Horsepower",
"aggregate": "sum"
},
{
"field": "Weight",
"aggregate": "sum"
},
{
"field": "Acceleration",
"aggregate": "sum"
},
{
"field": "Year"
},
{
"field": "Origin"
},
{
"field": "dataid"
}
],
"groupby": [
{
"field": "dataid"
}
],
"filter": []
},
{
"add": "chart",
"mark": "unit"
},
{
"add": "encoding",
"channel": "x",
"field": "Year"
},
"animation": {
"duration": 1000
}
},
{
"add": "annotation",
"method": "fill",
"target": [{
"field": "Origin",
"value": "Japan"
}],
"animation": {
"duration": 1000
}
},
{
"add": "encoding",
"channel": "size",
"field": "Horsepower"
},
"animation": {
"duration": 1000
}
}
]
}
Save chart as an image.
{
"save": "chart",
"name": image_name, (optional)
"format": png (optional)
}
Clone repository
git clone https://github.com/narchart/narrative-chart.git
Switch to the Master branch
cd NarrativeChart
git checkout master
Use yarn to start the playground
yarn start
- Create a new folder named with the chart name (e.g.,
newchart
) in the directorysrc/vis/charts
. - Create a class
NewChart
from a parent class calledChart
in the folder. - Create a class
NewMark
from a parent class calledMark
in the folder. - Record parameters of channels in the
NewMark
, such as x, y, color, size. - Implement 4 methods in the
NewChart
, includingvisualize()
,addEncoding(channel, field, animation)
,modifyEncoding(channel, field, animation)
, andremoveEncoding(channel, field, animation)
. They are the essential methods to build a chart. - Export the class in
src/vis/charts/index.js
. - Import and setup the chart in
src/vis/actions/addchart.js
.
Please refer to src/vis/charts/scatterplot/index.js
as an example. Note: To support annotations, you should make sure all marks in the chart SVG are set to "mark" class. For example in Scatterplot:
content.append("g")
.selectAll("circle")
.data(processedData)
.enter()
.append("circle")
.attr("class", "mark")
...
- Create a new js file named with the annotation name (e.g.,
newannotation.js
) in the directorysrc/vis/actions/annotations
. - Create a class
NewAnnotation
from a parent class calledAnnotator
. - Implement a method
annotate(chart, target, style, animation)
, wherechart
is the chart object you can get the SVG,target
can locate the position to add annotation,style
is a dictionary with user-defined style, andanimation
is a dictionary with user-defined animation setting. - Import and setup the annotation in
src/vis/actions/addannotation.js
.
Please refer to src/vis/actions/annotations/fill.js
as an example.