UNPKG

dimple-js

Version:

Dimple is an object-oriented API allowing you to create flexible axis-based charts using [d3.js](http://d3js.org "d3.js").

231 lines (208 loc) 11.6 kB
// Copyright: 2015 AlignAlytics // License: "https://github.com/PMSI-AlignAlytics/dimple/blob/master/MIT-LICENSE.txt" // Source: /src/objects/plot/line.js dimple.plot.line = { // By default the values are not stacked stacked: false, // This is a grouped plot meaning many points are treated as one series value grouped: true, // The axis positions affecting the line series supportedAxes: ["x", "y", "c"], // Draw the axis draw: function (chart, series, duration) { // Get the position data var data = series._positionData, lineData = [], theseShapes = null, className = "dimple-series-" + chart.series.indexOf(series), firstAgg = (series.x._hasCategories() || series.y._hasCategories() ? 0 : 1), interpolation, graded = false, i, j, k, key, keyString, rowIndex, updated, removed, orderedSeriesArray, onEnter = function () { return function (e, shape, chart, series) { d3.select(shape).style("opacity", 1); dimple._showPointTooltip(e, shape, chart, series); }; }, onLeave = function (lineData) { return function (e, shape, chart, series) { d3.select(shape).style("opacity", (series.lineMarkers || lineData.data.length < 2 ? dimple._helpers.opacity(e, chart, series) : 0)); dimple._removeTooltip(e, shape, chart, series); }; }, drawMarkers = function (d, context) { dimple._drawMarkers(d, chart, series, duration, className, graded, onEnter(d), onLeave(d), context); }, coord = function (position, datum) { var val; if (series.interpolation === "step" && series[position]._hasCategories()) { series.barGap = 0; series.clusterBarGap = 0; val = dimple._helpers[position](datum, chart, series) + (position === "y" ? dimple._helpers.height(datum, chart, series) : 0); } else { val = dimple._helpers["c" + position](datum, chart, series); } // Remove long decimals from the coordinates as this fills the dom up with noise and makes matching below less likely to work. It // shouldn't really matter but positioning to < 0.1 pixel is pretty pointless anyway. return parseFloat(val.toFixed(1)); }, getLine = function (inter, originProperty) { return d3.svg.line() .x(function (d) { return (series.x._hasCategories() || !originProperty ? d.x : series.x[originProperty]); }) .y(function (d) { return (series.y._hasCategories() || !originProperty ? d.y : series.y[originProperty]); }) .interpolate(inter); }; // Handle the special interpolation handling for step interpolation = (series.interpolation === "step" ? "step-after" : series.interpolation); // Get the array of ordered values orderedSeriesArray = dimple._getSeriesOrder(series.data || chart.data, series); if (series.c && ((series.x._hasCategories() && series.y._hasMeasure()) || (series.y._hasCategories() && series.x._hasMeasure()))) { graded = true; } // Create a set of line data grouped by the aggregation field for (i = 0; i < data.length; i += 1) { key = []; rowIndex = -1; // Skip the first category unless there is a category axis on x or y for (k = firstAgg; k < data[i].aggField.length; k += 1) { key.push(data[i].aggField[k]); } // Find the corresponding row in the lineData keyString = dimple._createClass(key); for (k = 0; k < lineData.length; k += 1) { if (lineData[k].keyString === keyString) { rowIndex = k; break; } } // Add a row to the line data if none was found if (rowIndex === -1) { rowIndex = lineData.length; lineData.push({ key: key, keyString: keyString, color: "white", data: [], markerData: [], points: [], line: {}, entry: {}, exit: {} }); } // Add this row to the relevant data lineData[rowIndex].data.push(data[i]); } // Sort the line data itself based on the order series array - this matters for stacked lines and default color // consistency with colors usually awarded in terms of prominence if (orderedSeriesArray) { lineData.sort(function (a, b) { return dimple._arrayIndexCompare(orderedSeriesArray, a.key, b.key); }); } // Create a set of line data grouped by the aggregation field for (i = 0; i < lineData.length; i += 1) { // Sort the points so that lines are connected in the correct order lineData[i].data.sort(dimple._getSeriesSortPredicate(chart, series, orderedSeriesArray)); // If this should have colour gradients, add them if (graded) { dimple._addGradient(lineData[i].key, "fill-line-gradient-" + lineData[i].keyString, (series.x._hasCategories() ? series.x : series.y), data, chart, duration, "fill"); } // Get points here, this is so that as well as drawing the line with them, we can also // use them for the baseline for (j = 0; j < lineData[i].data.length; j += 1) { lineData[i].points.push({ x: coord("x", lineData[i].data[j]), y: coord("y", lineData[i].data[j]) }); } // If this is a step interpolation we need to add in some extra points to the category axis // This is a little tricky but we need to add a new point duplicating the last category value. In order // to place the point we need to calculate the gap between the last x and the penultimate x and apply that // gap again. if (series.interpolation === "step" && lineData[i].points.length > 1) { if (series.x._hasCategories()) { lineData[i].points.push({ x : 2 * lineData[i].points[lineData[i].points.length - 1].x - lineData[i].points[lineData[i].points.length - 2].x, y : lineData[i].points[lineData[i].points.length - 1].y }); } else if (series.y._hasCategories()) { lineData[i].points = [{ x : lineData[i].points[0].x, y : 2 * lineData[i].points[0].y - lineData[i].points[1].y }].concat(lineData[i].points); } } // Get the points that this line will appear lineData[i].entry = getLine(interpolation, "_previousOrigin")(lineData[i].points); lineData[i].update = getLine(interpolation)(lineData[i].points); lineData[i].exit = getLine(interpolation, "_origin")(lineData[i].points); // Add the color in this loop, it can't be done during initialisation of the row because // the lines should be ordered first (to ensure standard distribution of colors lineData[i].color = chart.getColor(lineData[i].key.length > 0 ? lineData[i].key[lineData[i].key.length - 1] : "All"); lineData[i].css = chart.getClass(lineData[i].key.length > 0 ? lineData[i].key[lineData[i].key.length - 1] : "All"); } if (chart._tooltipGroup !== null && chart._tooltipGroup !== undefined) { chart._tooltipGroup.remove(); } if (series.shapes === null || series.shapes === undefined) { theseShapes = chart._group.selectAll("." + className).data(lineData); } else { theseShapes = series.shapes.data(lineData, function (d) { return d.key; }); } // Add theseShapes .enter() .append("path") .attr("id", function (d) { return dimple._createClass([d.key]); }) .attr("class", function (d) { return className + " dimple-line " + d.keyString + " " + chart.customClassList.lineSeries + " " + d.css; }) .attr("d", function (d) { return d.entry; }) .call(function () { // Apply formats optionally if (!chart.noFormats) { this.attr("opacity", function (d) { return (graded ? 1 : d.color.opacity); }) .style("fill", "none") .style("stroke", function (d) { return (graded ? "url(#fill-line-gradient-" + d.keyString + ")" : d.color.stroke); }) .style("stroke-width", series.lineWeight); } }) .each(function (d) { // Pass line data to markers d.markerData = d.data; drawMarkers(d, this); }); // Update updated = chart._handleTransition(theseShapes, duration, chart) .attr("d", function (d) { return d.update; }) .each(function (d) { // Pass line data to markers d.markerData = d.data; drawMarkers(d, this); }); // Remove removed = chart._handleTransition(theseShapes.exit(), duration, chart) .attr("d", function (d) { return d.exit; }) .each(function (d) { // Using all data for the markers fails because there are no exits in the markers // only the whole line, therefore we need to clear the points here d.markerData = []; drawMarkers(d, this); }); dimple._postDrawHandling(series, updated, removed, duration); // Save the shapes to the series array series.shapes = theseShapes; } };