import React from 'react'
import ReactApexChart from 'react-apexcharts'
import './styles.css'
// This demo also loads: https://cdn.jsdelivr.net/npm/apexcharts/dist/unit-shapes.js
// Nothing here is in the catalog. `shapeFrom` and `strokeFrom` take an outline of
// your own and pack it with the same engine the 39 built-in shapes use, so a logo,
// a product silhouette or a national symbol needs no release from us.
// A paw print: one pad and four toes, drawn as five subpaths in a 0-100 box.
// Every subpath is wound the same way, so they union into one shape. Wind one the
// other way (flip an arc's sweep flag) and it would cut a hole instead.
var PAW =
'M 26 71 A 24 21 0 1 1 74 71 A 24 21 0 1 1 26 71 Z ' +
'M 10 48 A 9.5 12 -35 1 1 27 36 A 9.5 12 -35 1 1 10 48 Z ' +
'M 29 33 A 10 12.5 -12 1 1 48 29 A 10 12.5 -12 1 1 29 33 Z ' +
'M 52 29 A 10 12.5 12 1 1 71 33 A 10 12.5 12 1 1 52 29 Z ' +
'M 73 36 A 9.5 12 35 1 1 90 48 A 9.5 12 35 1 1 73 36 Z'
var paw = ApexUnitShapes.shapeFrom(PAW, {
name: 'paw',
// The count below which the toes stop reading as toes. Ask for fewer and the
// chart says so in the console rather than drawing mush.
minUnits: 180,
})
// A ridge line: for a thing with no interior, give a CENTRELINE and a width
// instead of drawing both sides of it by hand. `order: 'cols'` makes the season
// bands run along the walk rather than slicing it.
var ridge = ApexUnitShapes.strokeFrom(
'M 6 76 L 24 44 L 40 60 L 58 22 L 74 46 L 94 30',
{
name: 'ridge',
width: 12,
order: 'cols',
},
)
// No outline at all, and no import either: `positions` is just a function of the
// marks and the plot rectangle, so a rule that computes places is a shape too.
// This is the sunflower spiral - each mark one golden angle round from the last,
// its distance from the centre following the square root of its index so the
// density stays even.
function sunflower(objects, rect) {
var cx = rect.x + rect.width / 2
var cy = rect.y + rect.height / 2
var span = Math.min(rect.width, rect.height) / 2 - 6
var golden = Math.PI * (3 - Math.sqrt(5))
return objects.map(function (o, i) {
var t = Math.sqrt((i + 0.5) / objects.length)
var angle = i * golden
return {
id: o.id,
x: cx + Math.cos(angle) * span * t,
y: cy + Math.sin(angle) * span * t,
}
})
}
const ApexChart = () => {
const [state, setState] = React.useState({
series: [1840, 1260, 220, 140],
options: {
chart: {
type: 'unit',
height: 430,
animations: {
enabled: true,
speed: 900,
},
},
labels: ['Dogs', 'Cats', 'Rabbits', 'Others'],
colors: ['#008FFB', '#00A96E', '#C98A00', '#E8264F'],
plotOptions: {
unit: {
layout: 'custom',
// Defined in the head script from path data, not imported from the catalog.
positions: paw,
transition: 'flow',
unitValue: 5,
clusterLabels: {
show: false,
},
},
},
legend: {
position: 'bottom',
},
},
series1: [4200, 6800, 3100, 900],
options1: {
chart: {
type: 'unit',
height: 380,
animations: {
enabled: true,
speed: 900,
},
},
labels: ['Spring', 'Summer', 'Autumn', 'Winter'],
colors: ['#00A96E', '#7CB342', '#C98A00', '#0EB8C4'],
plotOptions: {
unit: {
layout: 'custom',
// A centreline plus a width, also from the head script.
positions: ridge,
transition: 'flow',
unitValue: 24,
clusterLabels: {
show: false,
},
},
},
legend: {
position: 'bottom',
},
},
series2: [520, 300, 120, 60],
options2: {
chart: {
type: 'unit',
height: 430,
animations: {
enabled: true,
speed: 900,
},
},
labels: ['Weekend', 'Day pass', 'Camping', 'Crew'],
colors: ['#008FFB', '#00A96E', '#C98A00', '#E8264F'],
plotOptions: {
unit: {
layout: 'custom',
// A function, not a shape object: this chart imports nothing from the kit.
positions: sunflower,
transition: 'flow',
clusterLabels: {
show: false,
},
},
},
legend: {
position: 'bottom',
},
},
})
return (
<div>
<div className="wrap">
<h1>A shape of your own</h1>
<p className="lede">
The collection ships 39 shapes, and the one you have in mind is
probably not among them. It does not have to be:{' '}
<code>plotOptions.unit.positions</code> is a function from the marks
and the plot rectangle to places, and everything in the kit is one of
those. Three routes in, depending on what you have.
</p>
<p className="lede">
None of the shapes on this page are built in. Each is defined in this
sample's own head script, in the lines shown under it.
</p>
<div className="card">
<p className="kind">Route 1 · you have an outline</p>
<h2>3,460 adoptions, in a paw</h2>
<p>
<code>shapeFrom(path)</code> takes SVG path data and returns a
shape. The packer does the rest: rows across the outline, each row
cut into the spans that fall inside it, and the gap between dots
bisected until the spans hold exactly the number of marks the data
asks for. Five subpaths here, all wound the same way, so the pad and
the toes union into one shape.
</p>
<div id="chart">
<ReactApexChart
options={state.options}
series={state.series}
type="unit"
height={430}
/>
</div>
<p className="note">1 dot = 5 adoptions</p>
</div>
<div className="card">
<p className="kind">Route 2 · your thing is a line</p>
<h2>15,000 trail visits, along the ridge</h2>
<p>
A ridge, a route or a signal has no interior, and drawing both sides
of one by hand means mitring every corner.{' '}
<code>strokeFrom(path, { width })</code> takes the
centreline instead and thickens it, so the outline is six points
long. A stroke also degrades kindly: run it short of dots and it
becomes a dotted line, which still reads as the same line.
</p>
<div id="chart2">
<ReactApexChart
options={state.options1}
series={state.series1}
type="unit"
height={380}
/>
</div>
<p className="note">
1 dot = 24 visits · bands ordered along the walk
</p>
</div>
<div className="card">
<p className="kind">
Route 3 · you have a rule, not a picture
</p>
<h2>1,000 wristbands, on a spiral</h2>
<p>
No outline, and no import from the kit at all: a plain function that
returns
<code>{ id, x, y }</code> per mark is already a valid
layout. This one is the sunflower spiral. Because the marks arrive
in series order and the spiral walks outward, the tiers land as
rings, which is worth knowing when you write your own: the order the
marks come in is the order your rule sees them.
</p>
<div id="chart3">
<ReactApexChart
options={state.options2}
series={state.series2}
type="unit"
height={430}
/>
</div>
<p className="note">1 dot = 1 wristband</p>
</div>
<div className="rules">
<b>Authoring an outline</b>
<ul>
<li>
Start each subpath with an absolute <code>M</code>, and close it
with <code>Z</code>.
</li>
<li>
Subpaths wound the same way union; one wound the other way cuts a
hole. That is the mistake to look for first when a shape comes out
inverted.
</li>
<li>
Keep a subpath from crossing itself. Two overlapping subpaths are
fine.
</li>
<li>
Any coordinate box works: the shape is fitted to the plot
rectangle, so 0-100 is as good as 0-1024.
</li>
<li>
Give a <code>minUnits</code> for the count below which your shape
stops being recognisable, and the chart will warn instead of
drawing mush.
</li>
</ul>
</div>
</div>
</div>
)
}
export default ApexChart