ui fix
This commit is contained in:
parent
eb06476f38
commit
a4a11b2638
29
package-lock.json
generated
29
package-lock.json
generated
@ -66,21 +66,16 @@
|
||||
"svelte-check": "^3.6.9",
|
||||
"svelte-echarts": "^1.0.0-rc3",
|
||||
"svelte-french-toast": "^1.2.0",
|
||||
"svelte-intersection-observer": "^1.0.0",
|
||||
"svelte-intersection-observer-action": "^0.0.5",
|
||||
"svelte-inview": "^4.0.2",
|
||||
"svelte-lazy": "^1.2.11",
|
||||
"svelte-lightweight-charts": "^2.2.0",
|
||||
"svelte-loading-spinners": "^0.3.6",
|
||||
"svelte-preprocess": "^5.1.4",
|
||||
"svelte-progress-bar": "^3.0.2",
|
||||
"svelte-sonner": "^0.3.27",
|
||||
"svelte-tags-input": "^6.0.1",
|
||||
"svelte-tiny-virtual-list": "^2.1.2",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tailwind-variants": "^0.2.1",
|
||||
"tailwindcss": "^4.0.9",
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.4.5",
|
||||
"util": "^0.12.5",
|
||||
"uuid": "^10.0.0",
|
||||
@ -8692,18 +8687,6 @@
|
||||
"svelte": "^3.19.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-intersection-observer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-intersection-observer/-/svelte-intersection-observer-1.0.0.tgz",
|
||||
"integrity": "sha512-AoxSog8fSt9sSN8ajMYM1I48ndq+rmPlaZSkJFCRbZ7I3KO8IPX6pl5mewWxdYL1JDI06hHu/7epn3h5tlJV9w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svelte-intersection-observer-action": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/svelte-intersection-observer-action/-/svelte-intersection-observer-action-0.0.5.tgz",
|
||||
"integrity": "sha512-d5WVlE3Dpxx554cOYBm9BkIfba0+/r1O1yKuekQH/ScXW78mNB+R7A/LSfmGTFBNHvqjPZ9Vehfx1iIJti1saw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svelte-inview": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte-inview/-/svelte-inview-4.0.2.tgz",
|
||||
@ -8817,12 +8800,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-progress-bar": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte-progress-bar/-/svelte-progress-bar-3.0.2.tgz",
|
||||
"integrity": "sha512-OpHgSg7ebvCtzA1mnUzOSftG7qcJ6llbopGuLCQ3Usz8RnFMm5S4DcclaaSDHQ33NJ8s+SrN/sEVuKZIDF7asw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svelte-sonner": {
|
||||
"version": "0.3.27",
|
||||
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.27.tgz",
|
||||
@ -8832,12 +8809,6 @@
|
||||
"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-tags-input": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte-tags-input/-/svelte-tags-input-6.0.1.tgz",
|
||||
"integrity": "sha512-3X5qomFSXe6E8H7Lq0oce7096tr6u06pWvTNgNoeNVIXCrRLxRYOk4Ujkte7z5WaAit47EUsZZP1TSJ2HR9ixA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svelte-tiny-virtual-list": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte-tiny-virtual-list/-/svelte-tiny-virtual-list-2.1.2.tgz",
|
||||
|
||||
@ -66,21 +66,16 @@
|
||||
"svelte-check": "^3.6.9",
|
||||
"svelte-echarts": "^1.0.0-rc3",
|
||||
"svelte-french-toast": "^1.2.0",
|
||||
"svelte-intersection-observer": "^1.0.0",
|
||||
"svelte-intersection-observer-action": "^0.0.5",
|
||||
"svelte-inview": "^4.0.2",
|
||||
"svelte-lazy": "^1.2.11",
|
||||
"svelte-lightweight-charts": "^2.2.0",
|
||||
"svelte-loading-spinners": "^0.3.6",
|
||||
"svelte-preprocess": "^5.1.4",
|
||||
"svelte-progress-bar": "^3.0.2",
|
||||
"svelte-sonner": "^0.3.27",
|
||||
"svelte-tags-input": "^6.0.1",
|
||||
"svelte-tiny-virtual-list": "^2.1.2",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"tailwind-variants": "^0.2.1",
|
||||
"tailwindcss": "^4.0.9",
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.4.5",
|
||||
"util": "^0.12.5",
|
||||
"uuid": "^10.0.0",
|
||||
|
||||
@ -1,199 +0,0 @@
|
||||
<!--
|
||||
@component
|
||||
Generates an HTML circle pack chart using [d3-hierarchy](https://github.com/d3/d3-hierarchy).
|
||||
-->
|
||||
<script>
|
||||
import { stratify, pack, hierarchy } from "d3-hierarchy";
|
||||
import { getContext } from "svelte";
|
||||
import { format } from "d3-format";
|
||||
import { screenWidth } from "$lib/store";
|
||||
|
||||
const { width, height, data } = getContext("LayerCake");
|
||||
|
||||
/** @type {String} [idKey='id'] - The key on each object where the id value lives. */
|
||||
export let idKey = "id";
|
||||
|
||||
/** @type {String} [parentKey] - Set this if you want to define one parent circle. This will give you a [nested](https://layercake.graphics/example/CirclePackNested) graphic versus a [grouping of circles](https://layercake.graphics/example/CirclePack). */
|
||||
export let parentKey = undefined;
|
||||
|
||||
/** @type {String} [valueKey='value'] - The key on each object where the data value lives. */
|
||||
export let valueKey = "value";
|
||||
|
||||
/** @type {Function} [labelVisibilityThreshold=r => r > 25] - By default, only show the text inside a circle if its radius exceeds a certain size. Provide your own function for different behavior. */
|
||||
export let labelVisibilityThreshold = (r) => r > 25;
|
||||
|
||||
/** @type {String} [stroke='#999'] - The circle's stroke color. */
|
||||
export let stroke = "#000";
|
||||
|
||||
/** @type {String} [textColor='#333'] - The label text color. */
|
||||
export let textColor = "#000";
|
||||
|
||||
/** @type {String} [textStroke='#000'] - The label text's stroke color. */
|
||||
export let textStroke = "#000";
|
||||
|
||||
/** @type {Number} [textStrokeWidth=0] - The label text's stroke width, in pixels. */
|
||||
export let textStrokeWidth = 0;
|
||||
|
||||
/** @type {Function} [sortBy=(a, b) => b.value - a.value] - The order in which circle's are drawn. Sorting on the `depth` key is also a popular choice. */
|
||||
export let sortBy = (a, b) => b.value - a.value; // 'depth' is also a popular choice
|
||||
|
||||
/** @type {Number} [spacing=0] - Whitespace padding between each circle, in pixels. */
|
||||
export let spacing = 0;
|
||||
|
||||
/* --------------------------------------------
|
||||
* This component will automatically group your data
|
||||
* into one group if no `parentKey` was passed in.
|
||||
* Stash $data here so we can add our own parent
|
||||
* if there's no `parentKey`
|
||||
*/
|
||||
let parent = {};
|
||||
$: dataset = $data;
|
||||
|
||||
$: if (parentKey === undefined) {
|
||||
parent = { [idKey]: "all" };
|
||||
dataset = [...dataset, parent];
|
||||
}
|
||||
|
||||
$: stratifier = stratify()
|
||||
.id((d) => d[idKey])
|
||||
.parentId((d) => {
|
||||
if (d[idKey] === parent[idKey]) return "";
|
||||
return d[parentKey] || parent[idKey];
|
||||
});
|
||||
|
||||
$: packer = pack().size([$width, $height]).padding(spacing);
|
||||
|
||||
$: stratified = stratifier(dataset);
|
||||
|
||||
$: root = hierarchy(stratified)
|
||||
.sum((d, i) => {
|
||||
return d.data[valueKey] || 1;
|
||||
})
|
||||
.sort(sortBy);
|
||||
|
||||
$: packed = packer(root);
|
||||
|
||||
$: descendants = packed.descendants();
|
||||
|
||||
$: ballSize = $screenWidth < 1024 ? 2 : 3;
|
||||
|
||||
const titleCase = (d) => d.replace(/^\w/, (w) => w.toUpperCase());
|
||||
const commas = format(",");
|
||||
</script>
|
||||
|
||||
<div class="circle-pack" data-has-parent-key={parentKey !== undefined}>
|
||||
{#each descendants as d, index}
|
||||
<div
|
||||
class="circle-group"
|
||||
data-id={d.data.id}
|
||||
data-visible={labelVisibilityThreshold(d.r)}
|
||||
>
|
||||
<div
|
||||
class="circle"
|
||||
style="left:{d.x}px;top:{d.y}px;width:{d.r * ballSize}px;height:{d.r *
|
||||
ballSize}px;background-color:{index === 1 && d.data.id === 'puts'
|
||||
? '#FF2F1F'
|
||||
: index === 1 && d.data.id === 'calls'
|
||||
? '#00FC50'
|
||||
: '#1E1E1E'}; border: 0px solid #000;"
|
||||
/>
|
||||
<div
|
||||
class="text-group"
|
||||
style="
|
||||
color:{textColor};
|
||||
text-shadow:
|
||||
-{textStrokeWidth}px -{textStrokeWidth}px 0 {textStroke},
|
||||
{textStrokeWidth}px -{textStrokeWidth}px 0 {textStroke},
|
||||
-{textStrokeWidth}px {textStrokeWidth}px 0 {textStroke},
|
||||
{textStrokeWidth}px {textStrokeWidth}px 0 {textStroke};
|
||||
left:{d.x}px;
|
||||
top:{d.y - (labelVisibilityThreshold(d.r) ? 0 : d.r + 4)}px;
|
||||
"
|
||||
>
|
||||
<div class="flex flex-col items-center m-auto">
|
||||
{#if d.data.data[valueKey]}
|
||||
<div
|
||||
class="{index === 1
|
||||
? 'text-xl font-semibold text-black'
|
||||
: 'text-[1rem] font-semibold text-[#6B6C70]'} text-center"
|
||||
>
|
||||
{commas(d.data.data[valueKey])}%
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="{index === 1
|
||||
? 'text-xl font-semibold text-black'
|
||||
: 'text-sm font-semibold text-[#6B6C70]'} text-center"
|
||||
>
|
||||
{titleCase(d.data.id)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.circle-pack {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.circle,
|
||||
.text-group {
|
||||
position: absolute;
|
||||
}
|
||||
.circle {
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
/* Hide the root node if we want, useful if we are creating our own root */
|
||||
.circle-pack[data-has-parent-key="false"] .circle-group[data-id="all"] {
|
||||
display: none;
|
||||
}
|
||||
/* .circle-group:hover {
|
||||
z-index: 9999;
|
||||
} */
|
||||
.circle-group[data-visible="false"] .text-group {
|
||||
display: none;
|
||||
padding: 4px 7px;
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
transform: translate(-50%, -100%);
|
||||
top: -4px;
|
||||
}
|
||||
.circle-group[data-visible="false"]:hover .text-group {
|
||||
z-index: 999;
|
||||
display: block !important;
|
||||
/* On hover, set the text color to black and eliminate the shadow */
|
||||
text-shadow: none !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
.circle-group[data-visible="false"]:hover .circle {
|
||||
border-color: #000 !important;
|
||||
}
|
||||
.text-group {
|
||||
width: auto;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
cursor: pointer;
|
||||
line-height: 20px;
|
||||
}
|
||||
.text {
|
||||
width: 100%;
|
||||
font-size: 19px;
|
||||
/* text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff; */
|
||||
}
|
||||
.text.value {
|
||||
font-size: 15px;
|
||||
}
|
||||
.circle {
|
||||
border-radius: 50%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
@ -3,6 +3,7 @@
|
||||
import InfoModal from "$lib/components/InfoModal.svelte";
|
||||
import { abbreviateNumber, removeCompanyStrings } from "$lib/utils";
|
||||
import highcharts from "$lib/highcharts.ts";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
export let data;
|
||||
export let rawData = [];
|
||||
@ -293,7 +294,7 @@
|
||||
{#if data?.user?.tier !== "Pro" && i > 0}
|
||||
<button
|
||||
on:click={() => goto("/pricing")}
|
||||
class="group relative z-1 rounded-full w-1/2 min-w-24 md:w-auto px-5 py-1"
|
||||
class="cursor-pointer group relative z-1 rounded-full w-1/2 min-w-24 md:w-auto px-5 py-1"
|
||||
>
|
||||
<span class="relative text-sm block font-semibold">
|
||||
{item.title}
|
||||
@ -311,7 +312,7 @@
|
||||
{:else}
|
||||
<button
|
||||
on:click={() => changeTimePeriod(i)}
|
||||
class="group relative z-1 rounded-full w-1/2 min-w-24 md:w-auto px-5 py-1 {activeIdx ===
|
||||
class="cursor-pointer group relative z-1 rounded-full w-1/2 min-w-24 md:w-auto px-5 py-1 {activeIdx ===
|
||||
i
|
||||
? 'z-0'
|
||||
: ''} "
|
||||
@ -371,7 +372,7 @@
|
||||
<td
|
||||
class="text-white text-sm sm:text-[1rem] text-right whitespace-nowrap"
|
||||
>
|
||||
{item?.totalVolume}
|
||||
{abbreviateNumber(item?.totalVolume)}
|
||||
</td>
|
||||
|
||||
<td
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
let activeIdx = 0;
|
||||
const tabs = [
|
||||
{
|
||||
title: "Annual",
|
||||
title: "Daily",
|
||||
},
|
||||
{
|
||||
title: "Quarterly",
|
||||
@ -31,31 +31,18 @@
|
||||
(a, b) => new Date(b?.date) - new Date(a?.date),
|
||||
);
|
||||
|
||||
tableList = filterByPeriod([...tableList], activeIdx);
|
||||
|
||||
function changeTimePeriod(i) {
|
||||
activeIdx = i;
|
||||
tableList = rawData?.sort((a, b) => new Date(b?.date) - new Date(a?.date));
|
||||
|
||||
tableList = filterByPeriod([...tableList], i);
|
||||
if (activeIdx === 1) {
|
||||
tableList = filterByPeriod([...tableList]);
|
||||
}
|
||||
}
|
||||
|
||||
function filterByPeriod(data, period) {
|
||||
function filterByPeriod(data) {
|
||||
if (!Array.isArray(data) || data.length === 0) return [];
|
||||
|
||||
if (period === 0) {
|
||||
// Annual: one result per year.
|
||||
const seenYears = new Set();
|
||||
return data.filter((item) => {
|
||||
const dt = new Date(item.date);
|
||||
const year = dt.getFullYear();
|
||||
if (!seenYears.has(year)) {
|
||||
seenYears.add(year);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
} else if (period === 1) {
|
||||
// Quarterly: one result per year-quarter.
|
||||
const seenPeriods = new Set();
|
||||
return data.filter((item) => {
|
||||
@ -71,9 +58,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getPlotOptions() {
|
||||
let dates = [];
|
||||
let priceList = [];
|
||||
@ -293,7 +277,7 @@
|
||||
{#if data?.user?.tier !== "Pro" && i > 0}
|
||||
<button
|
||||
on:click={() => goto("/pricing")}
|
||||
class="group relative z-1 rounded-full w-1/2 min-w-24 md:w-auto px-5 py-1"
|
||||
class="cursor-pointer group relative z-1 rounded-full w-1/2 min-w-24 md:w-auto px-5 py-1"
|
||||
>
|
||||
<span class="relative text-sm block font-semibold">
|
||||
{item.title}
|
||||
@ -311,7 +295,7 @@
|
||||
{:else}
|
||||
<button
|
||||
on:click={() => changeTimePeriod(i)}
|
||||
class="group relative z-1 rounded-full w-1/2 min-w-24 md:w-auto px-5 py-1 {activeIdx ===
|
||||
class="cursor-pointer group relative z-1 rounded-full w-1/2 min-w-24 md:w-auto px-5 py-1 {activeIdx ===
|
||||
i
|
||||
? 'z-0'
|
||||
: ''} "
|
||||
@ -352,9 +336,7 @@
|
||||
<tbody>
|
||||
{#each tableList as item, index}
|
||||
<!-- row -->
|
||||
<tr
|
||||
class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] odd:bg-odd border-b border-gray-800"
|
||||
>
|
||||
<tr class="odd:bg-odd">
|
||||
<td
|
||||
class="text-white font-medium text-sm sm:text-[1rem] whitespace-nowrap"
|
||||
>
|
||||
|
||||
@ -1,119 +0,0 @@
|
||||
<!--
|
||||
@component
|
||||
Generates an SVG x-axis. This component is also configured to detect if your x-scale is an ordinal scale. If so, it will place the markers in the middle of the bandwidth.
|
||||
-->
|
||||
<script>
|
||||
import { getContext } from 'svelte';
|
||||
|
||||
const { width, height, xScale, yRange } = getContext('LayerCake');
|
||||
|
||||
/** @type {Boolean} [tickMarks=false] - Show a vertical mark for each tick. */
|
||||
export let tickMarks = false;
|
||||
|
||||
/** @type {Boolean} [gridlines=true] - Show gridlines extending into the chart area. */
|
||||
export let gridlines = true;
|
||||
|
||||
/** @type {Number} [tickMarkLength=6] - The length of the tick mark. */
|
||||
export let tickMarkLength = 6;
|
||||
|
||||
/** @type {Boolean} [baseline=false] – Show a solid line at the bottom. */
|
||||
export let baseline = false;
|
||||
|
||||
/** @type {Boolean} [snapLabels=false] - Instead of centering the text labels on the first and the last items, align them to the edges of the chart. */
|
||||
export let snapLabels = false;
|
||||
|
||||
/** @type {Function} [format=d => d] - A function that passes the current tick value and expects a nicely formatted value in return. */
|
||||
export let format = d => d;
|
||||
|
||||
/** @type {Number|Array|Function} [ticks] - If this is a number, it passes that along to the [d3Scale.ticks](https://github.com/d3/d3-scale) function. If this is an array, hardcodes the ticks to those values. If it's a function, passes along the default tick values and expects an array of tick values in return. If nothing, it uses the default ticks supplied by the D3 function. */
|
||||
export let ticks = undefined;
|
||||
|
||||
/** @type {Number} [tickGutter=0] - The amount of whitespace between the start of the tick and the chart drawing area (the yRange min). */
|
||||
export let tickGutter = 0;
|
||||
|
||||
/** @type {Number} [dx=0] - Any optional value passed to the `dx` attribute on the text label. */
|
||||
export let dx = 0;
|
||||
|
||||
/** @type {Number} [dy=12] - Any optional value passed to the `dy` attribute on the text label. */
|
||||
export let dy = 12;
|
||||
|
||||
function textAnchor(i, sl) {
|
||||
if (sl === true) {
|
||||
if (i === 0) {
|
||||
return 'start';
|
||||
}
|
||||
if (i === tickVals.length - 1) {
|
||||
return 'end';
|
||||
}
|
||||
}
|
||||
return 'middle';
|
||||
}
|
||||
|
||||
$: tickLen = tickMarks === true ? tickMarkLength ?? 6 : 0;
|
||||
|
||||
$: isBandwidth = typeof $xScale.bandwidth === 'function';
|
||||
|
||||
$: tickVals = Array.isArray(ticks)
|
||||
? ticks
|
||||
: isBandwidth
|
||||
? $xScale.domain()
|
||||
: typeof ticks === 'function'
|
||||
? ticks($xScale.ticks())
|
||||
: $xScale.ticks(ticks);
|
||||
|
||||
$: halfBand = isBandwidth ? $xScale.bandwidth() / 2 : 0;
|
||||
</script>
|
||||
|
||||
<g class="axis x-axis" class:snapLabels>
|
||||
{#each tickVals as tick, i (tick)}
|
||||
{#if baseline === true}
|
||||
<line class="baseline" y1={$height} y2={$height} x1="0" x2={$width} />
|
||||
{/if}
|
||||
|
||||
<g class="tick tick-{i}" transform="translate({$xScale(tick)},{Math.max(...$yRange)})">
|
||||
{#if gridlines === true}
|
||||
<line class="gridline" x1={halfBand} x2={halfBand} y1={-$height} y2="0" />
|
||||
{/if}
|
||||
{#if tickMarks === true}
|
||||
<line
|
||||
class="tick-mark"
|
||||
x1={halfBand}
|
||||
x2={halfBand}
|
||||
y1={tickGutter}
|
||||
y2={tickGutter + tickLen}
|
||||
/>
|
||||
{/if}
|
||||
<text x={halfBand} y={tickGutter + tickLen} {dx} {dy} text-anchor={textAnchor(i, snapLabels)}
|
||||
>{format(tick)}</text
|
||||
>
|
||||
</g>
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.tick {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
line,
|
||||
.tick line {
|
||||
stroke: #aaa;
|
||||
stroke-dasharray: 2;
|
||||
}
|
||||
|
||||
.tick text {
|
||||
fill: #666;
|
||||
}
|
||||
|
||||
.tick .tick-mark,
|
||||
.baseline {
|
||||
stroke-dasharray: 0;
|
||||
}
|
||||
/* This looks slightly better */
|
||||
.axis.snapLabels .tick:last-child text {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
.axis.snapLabels .tick.tick-0 text {
|
||||
transform: translateX(-3px);
|
||||
}
|
||||
</style>
|
||||
@ -1,119 +0,0 @@
|
||||
<!--
|
||||
@component
|
||||
Generates an SVG y-axis. This component is also configured to detect if your y-scale is an ordinal scale. If so, it will place the tickMarks in the middle of the bandwidth.
|
||||
-->
|
||||
<script>
|
||||
import { getContext } from 'svelte';
|
||||
|
||||
const { xRange, yScale, width } = getContext('LayerCake');
|
||||
|
||||
/** @type {Boolean} [tickMarks=false] - Show marks next to the tick label. */
|
||||
export let tickMarks = false;
|
||||
|
||||
/** @type {String} [labelPosition='even'] - Whether the label sits even with its value ('even') or sits on top ('above') the tick mark. Default is 'even'. */
|
||||
export let labelPosition = 'even';
|
||||
|
||||
/** @type {Boolean} [snapBaselineLabel=false] - When labelPosition='even', adjust the lowest label so that it sits above the tick mark. */
|
||||
export let snapBaselineLabel = false;
|
||||
|
||||
/** @type {Boolean} [gridlines=true] - Show gridlines extending into the chart area. */
|
||||
export let gridlines = true;
|
||||
|
||||
/** @type {Number} [tickMarkLength=undefined] - The length of the tick mark. If not set, becomes the length of the widest tick. */
|
||||
export let tickMarkLength = undefined;
|
||||
|
||||
/** @type {Function} [format=d => d] - A function that passes the current tick value and expects a nicely formatted value in return. */
|
||||
export let format = d => d;
|
||||
|
||||
/** @type {Number|Array|Function} [ticks=4] - If this is a number, it passes that along to the [d3Scale.ticks](https://github.com/d3/d3-scale) function. If this is an array, hardcodes the ticks to those values. If it's a function, passes along the default tick values and expects an array of tick values in return. */
|
||||
export let ticks = 4;
|
||||
|
||||
/** @type {Number} [tickGutter=0] - The amount of whitespace between the start of the tick and the chart drawing area (the xRange min). */
|
||||
export let tickGutter = 2;
|
||||
|
||||
/** @type {Number} [dx=0] - Any optional value passed to the `dx` attribute on the text label. */
|
||||
export let dx = 0;
|
||||
|
||||
/** @type {Number} [dy=0] - Any optional value passed to the `dy` attribute on the text label. */
|
||||
export let dy = 0;
|
||||
|
||||
/** @type {Number} [charPixelWidth=7.25] - Used to calculate the widest label length to offset labels. Adjust if the automatic tick length doesn't look right because you have a bigger font (or just set `tickMarkLength` to a pixel value). */
|
||||
export let charPixelWidth = 7.25;
|
||||
|
||||
$: isBandwidth = typeof $yScale.bandwidth === 'function';
|
||||
|
||||
$: tickVals = Array.isArray(ticks)
|
||||
? ticks
|
||||
: isBandwidth
|
||||
? $yScale.domain()
|
||||
: typeof ticks === 'function'
|
||||
? ticks($yScale.ticks())
|
||||
: $yScale.ticks(ticks);
|
||||
|
||||
function calcStringLength(sum, val) {
|
||||
if (val === ',' || val === '.') return sum + charPixelWidth * 0.5;
|
||||
return sum + charPixelWidth;
|
||||
}
|
||||
|
||||
$: tickLen =
|
||||
tickMarks === true
|
||||
? labelPosition === 'above'
|
||||
? tickMarkLength ?? widestTickLen
|
||||
: tickMarkLength ?? 6
|
||||
: 0;
|
||||
|
||||
$: widestTickLen = Math.max(
|
||||
10,
|
||||
Math.max(...tickVals.map(d => format(d).toString().split('').reduce(calcStringLength, 0)))
|
||||
);
|
||||
|
||||
$: x1 = -tickGutter - (labelPosition === 'above' ? widestTickLen : tickLen);
|
||||
$: y = isBandwidth ? $yScale.bandwidth() / 2 : 0;
|
||||
|
||||
$: maxTickValPx = Math.max(...tickVals.map($yScale));
|
||||
</script>
|
||||
|
||||
<g class="axis y-axis">
|
||||
{#each tickVals as tick (tick)}
|
||||
{@const tickValPx = $yScale(tick)}
|
||||
<g class="tick tick-{tick}" transform="translate({$xRange[0]}, {tickValPx})">
|
||||
{#if gridlines === true}
|
||||
<line class="gridline" {x1} x2={$width} y1={y} y2={y}></line>
|
||||
{/if}
|
||||
{#if tickMarks === true}
|
||||
<line class="tick-mark" {x1} x2={x1 + tickLen} y1={y} y2={y}></line>
|
||||
{/if}
|
||||
<text
|
||||
x={x1}
|
||||
{y}
|
||||
dx={dx + (labelPosition === 'even' ? -3 : 0)}
|
||||
text-anchor={labelPosition === 'above' ? 'start' : 'end'}
|
||||
dy={dy +
|
||||
(labelPosition === 'above' || (snapBaselineLabel === true && tickValPx === maxTickValPx)
|
||||
? -3
|
||||
: 4)}>{format(tick)}</text
|
||||
>
|
||||
</g>
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.tick {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tick line {
|
||||
stroke: #fff;
|
||||
}
|
||||
.tick .gridline {
|
||||
stroke-dasharray: 2;
|
||||
}
|
||||
|
||||
.tick text {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.tick.tick-0 line {
|
||||
stroke-dasharray: 0;
|
||||
}
|
||||
</style>
|
||||
@ -1,26 +0,0 @@
|
||||
<!--
|
||||
@component
|
||||
Generates an SVG bar chart.
|
||||
-->
|
||||
<script>
|
||||
import { getContext } from 'svelte';
|
||||
|
||||
const { data, xGet, yGet, xScale, yScale } = getContext('LayerCake');
|
||||
|
||||
/** @type {String} [fill='#3B82F6'] - The shape's fill color. This is technically optional because it comes with a default value but you'll likely want to replace it with your own color. */
|
||||
export let fill = '#fff';
|
||||
</script>
|
||||
|
||||
<g class="bar-group">
|
||||
{#each $data as d, i}
|
||||
<rect
|
||||
class="group-rect"
|
||||
data-id={i}
|
||||
x={$xScale.range()[0]}
|
||||
y={$yGet(d)}
|
||||
height={$yScale.bandwidth()}
|
||||
width={$xGet(d)}
|
||||
{fill}
|
||||
></rect>
|
||||
{/each}
|
||||
</g>
|
||||
Loading…
x
Reference in New Issue
Block a user