diff --git a/demos/forms.html b/demos/forms.html new file mode 100644 index 00000000..5d14443b --- /dev/null +++ b/demos/forms.html @@ -0,0 +1,295 @@ + + + + + + Forms customization + + + + + + + + + + + + diff --git a/demos/line-paths.html b/demos/line-paths.html index dd99090e..efdf68b5 100644 --- a/demos/line-paths.html +++ b/demos/line-paths.html @@ -171,7 +171,7 @@ cursor: { points: { size: (u, seriesIdx) => u.series[seriesIdx].points.size * 2.5, - width: (u, seriesIdx, size) => size / 4, + width: (u, seriesIdx, size) => size, stroke: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx) + '90', fill: (u, seriesIdx) => "#fff", }, diff --git a/src/dom.js b/src/dom.js index bc794fa4..1a4a1d5b 100644 --- a/src/dom.js +++ b/src/dom.js @@ -90,8 +90,10 @@ export function elColor(el, background, borderColor) { if (newColor != oldColor) { colorCache.set(el, newColor); - el.style.background = background; - el.style.borderColor = borderColor; + for (const element of el.getElementsByTagName('path')) { + element.setAttribute('fill', background); + element.setAttribute('stroke', borderColor); + } } } diff --git a/src/domClasses.js b/src/domClasses.js index d3cd4de3..da1045e1 100644 --- a/src/domClasses.js +++ b/src/domClasses.js @@ -17,6 +17,7 @@ export const LEGEND = pre + "legend" export const LEGEND_LIVE = pre + "live"; export const LEGEND_INLINE = pre + "inline"; export const LEGEND_SERIES = pre + "series"; -export const LEGEND_MARKER = pre + "marker"; +export const LEGEND_MARKERR = pre + "marker-r"; +export const LEGEND_MARKERL = pre + "marker-l"; export const LEGEND_LABEL = pre + "label"; export const LEGEND_VALUE = pre + "value"; \ No newline at end of file diff --git a/src/opts.js b/src/opts.js index f3d6172c..ef7fb301 100644 --- a/src/opts.js +++ b/src/opts.js @@ -400,7 +400,8 @@ export const legendOpts = { mount: noop, markers: { show: true, - width: 2, + before: true, + width: 10, stroke: legendStroke, fill: legendFill, dash: "solid", @@ -413,20 +414,29 @@ export const legendOpts = { function cursorPointShow(self, si) { let o = self.cursor.points; - let pt = placeDiv(); - - let size = o.size(self, si); - setStylePx(pt, WIDTH, size); - setStylePx(pt, HEIGHT, size); - - let mar = size / -2; - setStylePx(pt, "marginLeft", mar); - setStylePx(pt, "marginTop", mar); - - let width = o.width(self, si, size); - width && setStylePx(pt, "borderWidth", width); - - return pt; + const svgProp = self.series[si].points.form.svg; + const svgURI = 'http://www.w3.org/2000/svg'; + const svg = document.createElementNS(svgURI, 'svg'); + const path = document.createElementNS(svgURI, 'path'); + svg.appendChild(path); + path.setAttribute('d', svgProp.path); + + // At this point, we consider the viewBox to be a square + const fullSize = o.size(self, si); + let width = Math.min(o.width(self, si, fullSize), fullSize); + setStylePx(svg, WIDTH, fullSize); + setStylePx(svg, HEIGHT, fullSize); + + let mar = fullSize / -2; + setStylePx(svg, "marginLeft", mar); + setStylePx(svg, "marginTop", mar); + + const vb = svgProp.viewBox; + const svgHalfWidth = Math.ceil(Math.min(vb.width, vb.height) * width / (2 * fullSize)); + width && setStylePx(path, "stroke-width", svgHalfWidth * 2); + svg.setAttribute('viewBox', (vb.minX - svgHalfWidth) + ' ' + (vb.minY - svgHalfWidth) + ' ' + (vb.width + 2 * svgHalfWidth) + ' ' + (vb.height + 2 * svgHalfWidth)); + + return svg; } function cursorPointFill(self, si) { diff --git a/src/paths/points.js b/src/paths/points.js index 44b3a6b5..e59306b4 100644 --- a/src/paths/points.js +++ b/src/paths/points.js @@ -1,4 +1,4 @@ -import { orient, moveToH, moveToV, rectH, arcH, arcV, BAND_CLIP_FILL, BAND_CLIP_STROKE } from './utils'; +import { orient, rectH, BAND_CLIP_FILL, BAND_CLIP_STROKE } from './utils'; import { roundDec, PI } from '../utils'; // TODO: drawWrap(seriesIdx, drawPoints) (save, restore, translate, clip) @@ -7,21 +7,11 @@ export function points(opts) { // log("drawPoints()", arguments); let { pxRatio } = u; - return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => { + return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect, arc, bezier) => { let { pxRound, points } = series; - let moveTo, arc; - - if (scaleX.ori == 0) { - moveTo = moveToH; - arc = arcH; - } - else { - moveTo = moveToV; - arc = arcV; - } - const width = roundDec(points.width * pxRatio, 3); + const size = roundDec(points.size * pxRatio, 3); let rad = (points.size - points.width) / 2 * pxRatio; let dia = roundDec(rad * 2, 3); @@ -43,8 +33,7 @@ export function points(opts) { let x = pxRound(valToPosX(dataX[pi], scaleX, xDim, xOff)); let y = pxRound(valToPosY(dataY[pi], scaleY, yDim, yOff)); - moveTo(fill, x + rad, y); - arc(fill, x, y, rad, 0, PI * 2); + points.form.draw(fill, x, y, size, width, moveTo, lineTo, arc, bezier); } }; @@ -63,4 +52,17 @@ export function points(opts) { }; }); }; +} + +export const CIRCLE = { + name: 'CIRCLE', + svg: { + viewBox: { minX: 0, minY: 0, width: 100, height: 100 }, + path: 'M0 50A50 50 0 11100 50 50 50 0 110 50Z' + }, + draw: (path, centerX, centerY, size, strokeWidth, moveTo, lineTo, arc, bezier) => { + const dist = (size - strokeWidth) / 2; + moveTo(path, centerX + dist, centerY); + arc(path, centerX, centerY, dist, 0, 2 * Math.PI); + }, } \ No newline at end of file diff --git a/src/uPlot.css b/src/uPlot.css index 5282773e..a51d6922 100644 --- a/src/uPlot.css +++ b/src/uPlot.css @@ -69,13 +69,20 @@ display: inline-block; } -.u-legend .u-marker { +.u-legend .u-marker-l { width: 1em; height: 1em; margin-right: 4px; background-clip: padding-box !important; } +.u-marker-r { + width: 1em; + height: 1em; + margin-left: 4px; + background-clip: padding-box !important; +} + .u-inline.u-live th::after { content: ":"; vertical-align: middle; @@ -128,8 +135,6 @@ position: absolute; top: 0; left: 0; - border-radius: 50%; - border: 0 solid; pointer-events: none; will-change: transform; /* this has to be !important since we set inline "background" shorthand */ @@ -142,4 +147,8 @@ .u-cursor-y.u-off, .u-cursor-pt.u-off { display: none; +} + +svg { + paint-order: stroke; } \ No newline at end of file diff --git a/src/uPlot.js b/src/uPlot.js index f20d9545..5ab8c173 100644 --- a/src/uPlot.js +++ b/src/uPlot.js @@ -106,7 +106,8 @@ import { LEGEND_LIVE, LEGEND_INLINE, LEGEND_SERIES, - LEGEND_MARKER, + LEGEND_MARKERR, + LEGEND_MARKERL, LEGEND_LABEL, LEGEND_VALUE, } from './domClasses'; @@ -187,7 +188,7 @@ import { import { _sync } from './sync'; -import { points } from './paths/points'; +import { points, CIRCLE } from './paths/points'; import { linear } from './paths/linear'; import { stepped } from './paths/stepped'; import { bars } from './paths/bars'; @@ -614,6 +615,32 @@ export default function uPlot(opts, data, then) { const son = {show: true}; const soff = {show: false}; + function placeMarker(seriesIndex, label, before) { + const svgProp = series[seriesIndex].points.form.svg; + + const svgURI = 'http://www.w3.org/2000/svg'; + const svg = document.createElementNS(svgURI, 'svg'); + svg.classList.add(before ? LEGEND_MARKERL : LEGEND_MARKERR); + const path = document.createElementNS(svgURI, 'path'); + label.appendChild(svg); + svg.appendChild(path); + path.setAttribute('d', svgProp.path); + + const width = markers.width(self, seriesIndex); + const dw = ceil(width/2); + const vb = svgProp.viewBox; + // Adapting the viewBox to the stroke's width + svg.setAttribute('viewBox', (vb.minX - dw) + ' ' + (vb.minY - dw) + ' ' + (vb.width + 2 * dw) + ' ' + (vb.height + 2 * dw)); + if (width) { + path.setAttribute('stroke-width', width); + path.setAttribute('stroke', markers.stroke(self, seriesIndex)); + const dash = markers.dash(self, seriesIndex); + if (dash != 'solid') path.setAttribute('stroke-dasharray', Array.isArray(dash) ? dash.join(' ') : '35 15'); + } + const fill = markers.fill(self, seriesIndex); + path.setAttribute('fill', fill == null ? transparent : fill); + } + function initLegendRow(s, i) { if (i == 0 && (multiValLegend || !legend.live || mode == 2)) return nullNullTuple; @@ -629,20 +656,9 @@ export default function uPlot(opts, data, then) { let label = placeTag("th", null, row); - if (markers.show) { - let indic = placeDiv(LEGEND_MARKER, label); - - if (i > 0) { - let width = markers.width(self, i); - - if (width) - indic.style.border = width + "px " + markers.dash(self, i) + " " + markers.stroke(self, i); - - indic.style.background = markers.fill(self, i); - } - } - + if (markers.show && markers.before && i > 0) placeMarker(i, label, true); let text = placeDiv(LEGEND_LABEL, label); + if (markers.show && !markers.before && i > 0) placeMarker(i, label, false); if (s.label instanceof HTMLElement) text.appendChild(s.label); @@ -1004,7 +1020,7 @@ export default function uPlot(opts, data, then) { function initCursorPt(s, si) { let pt = points.show(self, si); - if (pt instanceof HTMLElement) { + if (pt instanceof SVGSVGElement) { addClass(pt, CURSOR_PT); addClass(pt, s.class); elTrans(pt, -10, -10, plotWidCss, plotHgtCss); @@ -1041,6 +1057,7 @@ export default function uPlot(opts, data, then) { stroke: s.stroke, space: _ptDia * 2, paths: pointsPath, + form: CIRCLE, _stroke: null, _fill: null, }, s.points);